diff --git a/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx b/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx index 2925f940..7fc3ec19 100644 --- a/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx @@ -2,7 +2,7 @@ import { useRef, useState, type FormEvent } from "react"; import { RotateCcw } from "lucide-react"; -import { UiButton, UiCopyAction, UiDisclosure, UiDownloadAction, UiQuantityField, UiRepeatableList, UiSelect, UiTextField, type UiOption } from "@/app/ui/controls"; +import { UiButton, UiCopyAction, UiDisclosure, UiDownloadAction, UiQuantityField, UiRepeatableList, UiSelect, UiShareAction, UiTextField, uiSetSummary, type UiOption } from "@/app/ui/controls"; import { UiActionRow, UiStatus } from "@/app/ui/display"; import { Example } from "../gallery"; import styles from "../components-ui.module.css"; @@ -23,6 +23,9 @@ type Drive = { id: string; capacity: number; unit: string }; export function ActionsAndFormsExamples() { const [hostname, setHostname] = useState(""); const [recordSize, setRecordSize] = useState("128K"); + const [windowDays, setWindowDays] = useState(""); + const [annualShare, setAnnualShare] = useState(""); + const advancedSet = [windowDays, annualShare].filter((value) => value.trim()).length; const [hostnameError, setHostnameError] = useState(null); const [status, setStatus] = useState("Enter a hostname, then check it."); const hostnameRef = useRef(null); @@ -112,8 +115,17 @@ export function ActionsAndFormsExamples() { /> - - option.id === recordSize)?.label} record size`}> + + +
+ + +
+
+
+ + + option.id === recordSize)?.label} record size`}>
+ + + + + + ); } diff --git a/app/(site)/(components-ui)/components-ui/examples/charts.tsx b/app/(site)/(components-ui)/components-ui/examples/charts.tsx index b11112b8..cccbdd75 100644 --- a/app/(site)/(components-ui)/components-ui/examples/charts.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/charts.tsx @@ -45,6 +45,20 @@ const visitors: UiChartSpec = { xFormat: "date", }; +// A paperback that costs $4.60 to print: 50% below $9.99, 60% from $9.99, so the line steps up there. +const royaltyCurve: UiChartSpec = (() => { + const royalty = (cents: number) => (Math.round(cents * (cents >= 999 ? 0.6 : 0.5)) - 460) / 100; + const prices = [920, 998, 999, ...Array.from({ length: 21 }, (_, index) => 1000 + index * 100)]; + return { + format: "currency", + kind: "line", + marks: [{ label: "Your price", x: 14.99, y: royalty(1499) }], + series: [{ id: "royalty", label: "Royalty per sale", points: prices.map((cents): UiChartPoint => [cents / 100, royalty(cents)]) }], + title: "Royalty by list price", + xFormat: "currency", + }; +})(); + const minute = 60_000; const rawLatency = (() => { const random = seeded(9); @@ -161,6 +175,79 @@ const signups: UiChartSpec = { xFormat: "date", }; +const trialFunnel: UiChartSpec = { + format: "number", + height: 280, + kind: "funnel", + stages: [ + { id: "visitors", label: "Visitors", value: 4800 }, + { id: "signups", label: "Signups", value: 640 }, + { id: "trials", label: "Trials", value: 200 }, + { id: "paid", label: "Paid", value: 22 }, + ], + title: "Visitors to paid customers", +}; + +const goal: UiChartSpec = { format: "currency", height: 240, kind: "gauge", label: "of $10,000 MRR goal", max: 10_000, title: "MRR goal", value: 6_400 }; + +const costs: UiChartSpec = { + format: "currency", + height: 280, + kind: "treemap", + parts: [ + { id: "hosting", label: "Hosting", value: 420 }, + { id: "database", label: "Database", value: 260 }, + { id: "email", label: "Email", value: 90 }, + { id: "analytics", label: "Analytics", value: 60 }, + { id: "monitoring", label: "Monitoring", value: 40 }, + { id: "backups", label: "Backups", value: 30 }, + { id: "domains", label: "Domains", value: 25 }, + ], + title: "Monthly costs", +}; + +const mrrFlow: UiChartSpec = { + format: "currency", + height: 300, + kind: "sankey", + links: [ + { source: "start", target: "kept", value: 8200 }, + { source: "start", target: "churned", value: 600 }, + { source: "start", target: "contracted", value: 200 }, + { source: "kept", target: "end", value: 8200 }, + { source: "new", target: "end", value: 1400 }, + { source: "expansion", target: "end", value: 500 }, + ], + nodes: [ + { id: "start", label: "Starting MRR" }, + { id: "new", label: "New" }, + { id: "expansion", label: "Expansion" }, + { id: "kept", label: "Kept" }, + { id: "churned", label: "Churned" }, + { id: "contracted", label: "Contraction" }, + { id: "end", label: "Ending MRR" }, + ], + title: "Where this month's MRR came from and went", +}; + +const plans: UiChartSpec = { + axes: [ + { label: "Price", max: 10 }, + { label: "Storage", max: 10 }, + { label: "Seats", max: 10 }, + { label: "Support", max: 10 }, + { label: "Integrations", max: 10 }, + ], + format: "number", + height: 320, + kind: "radar", + series: [ + { id: "starter", label: "Starter", values: [9, 4, 3, 5, 6] }, + { id: "team", label: "Team", values: [5, 8, 9, 8, 7] }, + ], + title: "Two plans compared", +}; + const kilobytes = (text: string) => `${(new TextEncoder().encode(text).length / 1000).toFixed(1)} KB`; /** What a chart adds to the page HTML: the server drawing, and the data handed to the browser. */ @@ -196,6 +283,11 @@ export function ChartsExamples() { + + + + + @@ -226,6 +318,31 @@ export function ChartsExamples() { + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx b/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx index a6cbe494..9b97311b 100644 --- a/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx @@ -8,6 +8,9 @@ import { PageHeader } from "@/app/ui/site/page-header"; import { PageMeta } from "@/app/ui/site/page-meta"; import { PageTitleIcon } from "@/app/ui/site/page-title-icon"; import { ThemeToggle } from "@/app/ui/site/theme-toggle"; +import { UiButton, UiTextField } from "@/app/ui/controls"; +import { UiActionRow, UiPrimaryResult } from "@/app/ui/display"; +import { ToolFieldRow, ToolResult, ToolWorkbench } from "@/app/ui/tools/tool-workbench"; import { Example } from "../gallery"; import styles from "../components-ui.module.css"; @@ -60,6 +63,25 @@ export function PageLayoutExamples() {
+ + + + + + + + Calculate capacity + Reset + + + + + + + + + +
    {fullLayouts.map((layout) => ( diff --git a/app/(site)/(components-ui)/components-ui/examples/results.tsx b/app/(site)/(components-ui)/components-ui/examples/results.tsx index 6834ab10..b7eb60a6 100644 --- a/app/(site)/(components-ui)/components-ui/examples/results.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/results.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { UiButton, UiCopyAction, UiRawOutput } from "@/app/ui/controls"; -import { UiActionRow, UiAllocationBar, UiCodeOutput, UiKeyValueList, UiMetricGroup, UiPrimaryResult, UiProgressBar, UiResultLead, UiStatusWord, UiSteps, UiTimeline } from "@/app/ui/display"; +import { UiActionRow, UiAllocationBar, UiCodeOutput, UiRangeMarker, UiKeyValueList, UiMetricGroup, UiPrimaryResult, UiProgressBar, UiResultLead, UiStatusWord, UiSteps, UiTimeline, UiUnitGrid } from "@/app/ui/display"; import { Example } from "../gallery"; import styles from "../components-ui.module.css"; @@ -21,12 +21,26 @@ export function ResultsExamples() { items={[ { id: "raw", label: "Raw", value: "32", unit: "TiB" }, { id: "protection", label: "Protection", value: "8", unit: "TiB" }, - { id: "reserve", label: "Reserve", value: "2.2", unit: "TiB" }, + { id: "reserve", label: "Reserve", value: "2.2", unit: "TiB", hint: "Free space kept back so the pool stays fast." }, ]} /> + +
    + +
    +
    +
    @@ -64,6 +78,40 @@ export function ResultsExamples() {
    + +
    + + +
    +
    + + +
    + + +
    +
    +
    diff --git a/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx b/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx index 0cc1f153..21a5c8e9 100644 --- a/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx @@ -33,6 +33,7 @@ export function TextAndChoicesExamples() { const [hourCycle, setHourCycle] = useState<12 | 24>(12); const [layout, setLayout] = useState("raidz1"); const [format, setFormat] = useState("paperback"); + const [trialType, setTrialType] = useState("no-card"); const [marketplaces, setMarketplaces] = useState(["us", "uk"]); const [recordTypes, setRecordTypes] = useState(["a"]); const [keepFree, setKeepFree] = useState(false); @@ -43,12 +44,29 @@ export function TextAndChoicesExamples() {
    - +
    + +
    + + +
    +
    +
    @@ -77,7 +95,7 @@ export function TextAndChoicesExamples() {
    - +