altitude-design 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/guidance/components/AILabel/README.md +35 -0
- package/dist/guidance/components/Accordion/README.md +25 -0
- package/dist/guidance/components/AgentFleet/README.md +48 -1
- package/dist/guidance/components/AgentLanes/README.md +32 -0
- package/dist/guidance/components/AgentRequests/README.md +47 -0
- package/dist/guidance/components/Alert/README.md +49 -0
- package/dist/guidance/components/AltitudeProvider/README.md +63 -0
- package/dist/guidance/components/AppShell/README.md +70 -0
- package/dist/guidance/components/AreaChart/README.md +58 -0
- package/dist/guidance/components/Artifact/README.md +69 -0
- package/dist/guidance/components/Avatar/README.md +41 -0
- package/dist/guidance/components/Await/README.md +48 -0
- package/dist/guidance/components/Badge/README.md +44 -0
- package/dist/guidance/components/BarChart/README.md +62 -0
- package/dist/guidance/components/BarList/README.md +58 -0
- package/dist/guidance/components/Board/README.md +67 -0
- package/dist/guidance/components/Breadcrumb/README.md +31 -0
- package/dist/guidance/components/Button/README.md +41 -0
- package/dist/guidance/components/Card/README.md +38 -0
- package/dist/guidance/components/ChartBrush/README.md +57 -0
- package/dist/guidance/components/ChartContainer/README.md +77 -0
- package/dist/guidance/components/ChartLegend/README.md +43 -0
- package/dist/guidance/components/ChartReference/README.md +74 -0
- package/dist/guidance/components/ChartTooltip/README.md +63 -0
- package/dist/guidance/components/Checkbox/README.md +35 -0
- package/dist/guidance/components/Checklist/README.md +66 -0
- package/dist/guidance/components/CodeBlock/README.md +44 -0
- package/dist/guidance/components/ColorPicker/README.md +39 -0
- package/dist/guidance/components/Combobox/README.md +35 -0
- package/dist/guidance/components/CommandPalette/README.md +177 -0
- package/dist/guidance/components/CommentThread/README.md +51 -0
- package/dist/guidance/components/Confirmation/README.md +60 -0
- package/dist/guidance/components/ContextMenu/README.md +51 -0
- package/dist/guidance/components/Conversation/README.md +124 -0
- package/dist/guidance/components/CopyButton/README.md +31 -0
- package/dist/guidance/components/DataGrid/README.md +75 -0
- package/dist/guidance/components/DatePicker/README.md +49 -0
- package/dist/guidance/components/Dialog/README.md +175 -0
- package/dist/guidance/components/DiffView/README.md +77 -0
- package/dist/guidance/components/DotPlot/README.md +44 -0
- package/dist/guidance/components/Drilldown/README.md +28 -0
- package/dist/guidance/components/DropZone/README.md +50 -0
- package/dist/guidance/components/DropdownMenu/README.md +44 -0
- package/dist/guidance/components/EditRegion/README.md +159 -0
- package/dist/guidance/components/EmptyState/README.md +49 -0
- package/dist/guidance/components/FeatureGate/README.md +39 -0
- package/dist/guidance/components/Field/README.md +36 -0
- package/dist/guidance/components/FilterBar/README.md +63 -0
- package/dist/guidance/components/FormattedNumber/README.md +44 -0
- package/dist/guidance/components/Heatmap/README.md +71 -0
- package/dist/guidance/components/HoldButton/README.md +32 -0
- package/dist/guidance/components/HoverCard/README.md +53 -0
- package/dist/guidance/components/InlineCitation/README.md +32 -0
- package/dist/guidance/components/InlineEdit/README.md +247 -1
- package/dist/guidance/components/Input/README.md +36 -0
- package/dist/guidance/components/InputOTP/README.md +46 -0
- package/dist/guidance/components/ItemGroup/README.md +100 -0
- package/dist/guidance/components/Kbd/README.md +27 -0
- package/dist/guidance/components/LineChart/README.md +62 -0
- package/dist/guidance/components/Link/README.md +41 -0
- package/dist/guidance/components/Markdown/README.md +81 -0
- package/dist/guidance/components/Meter/README.md +52 -0
- package/dist/guidance/components/NotificationCenter/README.md +39 -0
- package/dist/guidance/components/NumberInput/README.md +37 -0
- package/dist/guidance/components/PageHeader/README.md +45 -0
- package/dist/guidance/components/Pagination/README.md +33 -0
- package/dist/guidance/components/PasswordInput/README.md +36 -0
- package/dist/guidance/components/PermissionMatrix/README.md +40 -0
- package/dist/guidance/components/Pile/README.md +42 -0
- package/dist/guidance/components/PlanPicker/README.md +37 -0
- package/dist/guidance/components/Popover/README.md +36 -0
- package/dist/guidance/components/Progress/README.md +31 -0
- package/dist/guidance/components/PromptInput/README.md +98 -0
- package/dist/guidance/components/PropertyList/README.md +80 -0
- package/dist/guidance/components/Question/README.md +54 -0
- package/dist/guidance/components/RadioGroup/README.md +37 -0
- package/dist/guidance/components/RelativeTime/README.md +47 -0
- package/dist/guidance/components/RemovalReceipt/README.md +77 -0
- package/dist/guidance/components/Resizable/README.md +29 -0
- package/dist/guidance/components/SaveBar/README.md +38 -0
- package/dist/guidance/components/SavedViews/README.md +58 -0
- package/dist/guidance/components/ScatterChart/README.md +63 -0
- package/dist/guidance/components/Schedule/README.md +66 -0
- package/dist/guidance/components/ScrollArea/README.md +28 -0
- package/dist/guidance/components/SecretField/README.md +46 -0
- package/dist/guidance/components/Select/README.md +70 -0
- package/dist/guidance/components/Separator/README.md +44 -0
- package/dist/guidance/components/Sheet/README.md +94 -0
- package/dist/guidance/components/Skeleton/README.md +36 -0
- package/dist/guidance/components/Slider/README.md +35 -0
- package/dist/guidance/components/SortableList/README.md +34 -0
- package/dist/guidance/components/Sparkline/README.md +58 -0
- package/dist/guidance/components/SplitButton/README.md +39 -0
- package/dist/guidance/components/Stat/README.md +61 -0
- package/dist/guidance/components/Stepper/README.md +61 -0
- package/dist/guidance/components/Suggestions/README.md +58 -0
- package/dist/guidance/components/Surface/README.md +28 -0
- package/dist/guidance/components/Switch/README.md +29 -0
- package/dist/guidance/components/SyncIndicator/README.md +54 -0
- package/dist/guidance/components/Table/README.md +62 -0
- package/dist/guidance/components/Tabs/README.md +55 -0
- package/dist/guidance/components/TagInput/README.md +50 -0
- package/dist/guidance/components/Terminal/README.md +42 -0
- package/dist/guidance/components/ThreadList/README.md +73 -0
- package/dist/guidance/components/TimeInput/README.md +37 -0
- package/dist/guidance/components/Timeline/README.md +55 -0
- package/dist/guidance/components/Toaster/README.md +38 -0
- package/dist/guidance/components/Toggle/README.md +52 -0
- package/dist/guidance/components/Toolbar/README.md +69 -0
- package/dist/guidance/components/Tooltip/README.md +29 -0
- package/dist/guidance/components/Tour/README.md +44 -0
- package/dist/guidance/components/Trace/README.md +92 -0
- package/dist/guidance/components/Tree/README.md +85 -0
- package/dist/guidance/index.json +117 -117
- package/dist/guidance/llms.txt +1 -1
- package/dist/guidance/manifest.json +120 -120
- package/dist/guidance/visual/ActionsInPlace.md +5 -5
- package/dist/guidance/visual/Distinctiveness.md +6 -2
- package/dist/guidance/visual/Research.md +14 -2
- package/dist/guidance/visual/Theming.md +1 -1
- package/dist/types/agent-fleet.d.ts +28 -0
- package/dist/types/ai-label.d.ts +7 -0
- package/dist/types/app-shell.d.ts +23 -0
- package/dist/types/area-chart.d.ts +8 -0
- package/dist/types/artifact.d.ts +14 -0
- package/dist/types/bar-chart.d.ts +6 -0
- package/dist/types/bar-list.d.ts +6 -0
- package/dist/types/breadcrumb.d.ts +6 -0
- package/dist/types/chart-brush.d.ts +6 -0
- package/dist/types/chart.d.ts +29 -1
- package/dist/types/checklist.d.ts +6 -0
- package/dist/types/code-block.d.ts +6 -0
- package/dist/types/collections.d.ts +15 -0
- package/dist/types/color-picker.d.ts +6 -0
- package/dist/types/command.d.ts +13 -0
- package/dist/types/comment-thread.d.ts +11 -0
- package/dist/types/confirmation.d.ts +8 -0
- package/dist/types/context-menu.d.ts +11 -0
- package/dist/types/controls.d.ts +174 -10
- package/dist/types/conversation.d.ts +21 -0
- package/dist/types/copy-button.d.ts +6 -0
- package/dist/types/core.d.ts +31 -0
- package/dist/types/data-grid.d.ts +25 -0
- package/dist/types/date-picker.d.ts +6 -0
- package/dist/types/diff-view.d.ts +8 -0
- package/dist/types/dot-plot.d.ts +6 -0
- package/dist/types/edit-region.d.ts +23 -0
- package/dist/types/empty.d.ts +9 -0
- package/dist/types/feature-gate.d.ts +8 -0
- package/dist/types/filter-bar.d.ts +8 -0
- package/dist/types/formatted-number.d.ts +6 -0
- package/dist/types/heatmap.d.ts +6 -0
- package/dist/types/inline-citation.d.ts +6 -0
- package/dist/types/inline-edit.d.ts +7 -0
- package/dist/types/input-otp.d.ts +11 -0
- package/dist/types/item-group.d.ts +29 -0
- package/dist/types/line-chart.d.ts +6 -0
- package/dist/types/link.d.ts +8 -1
- package/dist/types/markdown.d.ts +6 -0
- package/dist/types/meter.d.ts +6 -0
- package/dist/types/nav.d.ts +6 -0
- package/dist/types/notifications.d.ts +6 -0
- package/dist/types/number-input.d.ts +6 -0
- package/dist/types/overlays.d.ts +61 -0
- package/dist/types/page-header.d.ts +14 -0
- package/dist/types/pagination.d.ts +6 -0
- package/dist/types/password-input.d.ts +6 -0
- package/dist/types/permission-matrix.d.ts +7 -0
- package/dist/types/pile.d.ts +6 -0
- package/dist/types/plan-picker.d.ts +6 -0
- package/dist/types/prompt-input.d.ts +11 -0
- package/dist/types/property-list.d.ts +17 -0
- package/dist/types/question.d.ts +11 -0
- package/dist/types/relative-time.d.ts +8 -1
- package/dist/types/removal-receipt.d.ts +18 -1
- package/dist/types/save-bar.d.ts +6 -0
- package/dist/types/saved-views.d.ts +12 -0
- package/dist/types/scatter-chart.d.ts +8 -0
- package/dist/types/schedule.d.ts +21 -0
- package/dist/types/secret-field.d.ts +6 -0
- package/dist/types/select.d.ts +12 -0
- package/dist/types/separator.d.ts +8 -1
- package/dist/types/sheet.d.ts +19 -0
- package/dist/types/sparkline.d.ts +6 -0
- package/dist/types/split-button.d.ts +7 -0
- package/dist/types/stat.d.ts +7 -0
- package/dist/types/stepper.d.ts +12 -0
- package/dist/types/suggestions.d.ts +10 -0
- package/dist/types/sync-indicator.d.ts +8 -0
- package/dist/types/tag-input.d.ts +8 -0
- package/dist/types/terminal.d.ts +6 -0
- package/dist/types/thread-list.d.ts +16 -0
- package/dist/types/time-input.d.ts +6 -0
- package/dist/types/timeline.d.ts +9 -0
- package/dist/types/toggle.d.ts +6 -0
- package/dist/types/toolbar.d.ts +20 -0
- package/dist/types/tour.d.ts +10 -0
- package/dist/types/trace.d.ts +16 -0
- package/dist/types/tree.d.ts +21 -0
- package/dist/types-cjs/agent-fleet.d.cts +28 -0
- package/dist/types-cjs/ai-label.d.cts +7 -0
- package/dist/types-cjs/app-shell.d.cts +23 -0
- package/dist/types-cjs/area-chart.d.cts +8 -0
- package/dist/types-cjs/artifact.d.cts +14 -0
- package/dist/types-cjs/bar-chart.d.cts +6 -0
- package/dist/types-cjs/bar-list.d.cts +6 -0
- package/dist/types-cjs/breadcrumb.d.cts +6 -0
- package/dist/types-cjs/chart-brush.d.cts +6 -0
- package/dist/types-cjs/chart.d.cts +29 -1
- package/dist/types-cjs/checklist.d.cts +6 -0
- package/dist/types-cjs/code-block.d.cts +6 -0
- package/dist/types-cjs/collections.d.cts +15 -0
- package/dist/types-cjs/color-picker.d.cts +6 -0
- package/dist/types-cjs/command.d.cts +13 -0
- package/dist/types-cjs/comment-thread.d.cts +11 -0
- package/dist/types-cjs/confirmation.d.cts +8 -0
- package/dist/types-cjs/context-menu.d.cts +11 -0
- package/dist/types-cjs/controls.d.cts +174 -10
- package/dist/types-cjs/conversation.d.cts +21 -0
- package/dist/types-cjs/copy-button.d.cts +6 -0
- package/dist/types-cjs/core.d.cts +31 -0
- package/dist/types-cjs/data-grid.d.cts +25 -0
- package/dist/types-cjs/date-picker.d.cts +6 -0
- package/dist/types-cjs/diff-view.d.cts +8 -0
- package/dist/types-cjs/dot-plot.d.cts +6 -0
- package/dist/types-cjs/edit-region.d.cts +23 -0
- package/dist/types-cjs/empty.d.cts +9 -0
- package/dist/types-cjs/feature-gate.d.cts +8 -0
- package/dist/types-cjs/filter-bar.d.cts +8 -0
- package/dist/types-cjs/formatted-number.d.cts +6 -0
- package/dist/types-cjs/heatmap.d.cts +6 -0
- package/dist/types-cjs/inline-citation.d.cts +6 -0
- package/dist/types-cjs/inline-edit.d.cts +7 -0
- package/dist/types-cjs/input-otp.d.cts +11 -0
- package/dist/types-cjs/item-group.d.cts +29 -0
- package/dist/types-cjs/line-chart.d.cts +6 -0
- package/dist/types-cjs/link.d.cts +8 -1
- package/dist/types-cjs/markdown.d.cts +6 -0
- package/dist/types-cjs/meter.d.cts +6 -0
- package/dist/types-cjs/nav.d.cts +6 -0
- package/dist/types-cjs/notifications.d.cts +6 -0
- package/dist/types-cjs/number-input.d.cts +6 -0
- package/dist/types-cjs/overlays.d.cts +61 -0
- package/dist/types-cjs/page-header.d.cts +14 -0
- package/dist/types-cjs/pagination.d.cts +6 -0
- package/dist/types-cjs/password-input.d.cts +6 -0
- package/dist/types-cjs/permission-matrix.d.cts +7 -0
- package/dist/types-cjs/pile.d.cts +6 -0
- package/dist/types-cjs/plan-picker.d.cts +6 -0
- package/dist/types-cjs/prompt-input.d.cts +11 -0
- package/dist/types-cjs/property-list.d.cts +17 -0
- package/dist/types-cjs/question.d.cts +11 -0
- package/dist/types-cjs/relative-time.d.cts +8 -1
- package/dist/types-cjs/removal-receipt.d.cts +18 -1
- package/dist/types-cjs/save-bar.d.cts +6 -0
- package/dist/types-cjs/saved-views.d.cts +12 -0
- package/dist/types-cjs/scatter-chart.d.cts +8 -0
- package/dist/types-cjs/schedule.d.cts +21 -0
- package/dist/types-cjs/secret-field.d.cts +6 -0
- package/dist/types-cjs/select.d.cts +12 -0
- package/dist/types-cjs/separator.d.cts +8 -1
- package/dist/types-cjs/sheet.d.cts +19 -0
- package/dist/types-cjs/sparkline.d.cts +6 -0
- package/dist/types-cjs/split-button.d.cts +7 -0
- package/dist/types-cjs/stat.d.cts +7 -0
- package/dist/types-cjs/stepper.d.cts +12 -0
- package/dist/types-cjs/suggestions.d.cts +10 -0
- package/dist/types-cjs/sync-indicator.d.cts +8 -0
- package/dist/types-cjs/tag-input.d.cts +8 -0
- package/dist/types-cjs/terminal.d.cts +6 -0
- package/dist/types-cjs/thread-list.d.cts +16 -0
- package/dist/types-cjs/time-input.d.cts +6 -0
- package/dist/types-cjs/timeline.d.cts +9 -0
- package/dist/types-cjs/toggle.d.cts +6 -0
- package/dist/types-cjs/toolbar.d.cts +20 -0
- package/dist/types-cjs/tour.d.cts +10 -0
- package/dist/types-cjs/trace.d.cts +16 -0
- package/dist/types-cjs/tree.d.cts +21 -0
- package/package.json +1 -1
|
@@ -78,3 +78,51 @@ A `Card`'s parts land together, and a `RevealGroup` takes one place in the atten
|
|
|
78
78
|
- Don't use it for errors: a rejected promise throws to your nearest error boundary, where an `Alert` belongs.
|
|
79
79
|
- Don't use `null` for "still loading"; it means there's nothing, and renders empty.
|
|
80
80
|
- `Skeleton` with `loaded` is still the way for layouts of your own.
|
|
81
|
+
|
|
82
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
83
|
+
## Example
|
|
84
|
+
|
|
85
|
+
The full preview, as rendered in Storybook and checked in CI (`card`, `who`, `id`, `col`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
import * as React from 'react';
|
|
89
|
+
import { Avatar, Badge, Card, CardContent, CardDescription, CardHeader, CardTitle } from 'altitude-design';
|
|
90
|
+
|
|
91
|
+
const USER = { name: 'Mara Quist', role: 'Platform engineer, Lisbon', bio: 'Merged “Faster cold starts” into main. Cold starts are down 38% across all regions.' };
|
|
92
|
+
// Stands in for useQuery or useSWR: undefined until the data arrives.
|
|
93
|
+
function useLoaded(value, ms) {
|
|
94
|
+
const [data, setData] = React.useState(undefined);
|
|
95
|
+
React.useEffect(() => { const t = setTimeout(() => setData(value), ms); return () => clearTimeout(t); }, []);
|
|
96
|
+
return data;
|
|
97
|
+
}
|
|
98
|
+
function Member() {
|
|
99
|
+
const user = useLoaded(USER, 1200);
|
|
100
|
+
const status = useLoaded('On call', 1800);
|
|
101
|
+
return (
|
|
102
|
+
<Card className="card">
|
|
103
|
+
<CardHeader>
|
|
104
|
+
<div className="who">
|
|
105
|
+
<Avatar name={user?.name} size={44} status="active" />
|
|
106
|
+
<div className="id">
|
|
107
|
+
<CardTitle>{user?.name}</CardTitle>
|
|
108
|
+
<CardDescription skeleton={{ lines: 1, width: '70%' }}>{user?.role}</CardDescription>
|
|
109
|
+
</div>
|
|
110
|
+
<Badge tone="positive">{status}</Badge>
|
|
111
|
+
</div>
|
|
112
|
+
</CardHeader>
|
|
113
|
+
<CardContent skeleton={{ lines: 2 }}>{user?.bio}</CardContent>
|
|
114
|
+
</Card>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
export default function App() {
|
|
118
|
+
const [round, setRound] = React.useState(0);
|
|
119
|
+
React.useEffect(() => { const t = setTimeout(() => setRound((r) => r + 1), 4400); return () => clearTimeout(t); }, [round]);
|
|
120
|
+
return (
|
|
121
|
+
<div className="col">
|
|
122
|
+
<Member key={round} />
|
|
123
|
+
<p className="cap">No skeleton code: while user is undefined, each part imprints itself at its own size, and rises out of it when the data arrives.</p>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
<!-- /example -->
|
|
@@ -20,3 +20,47 @@ Labels status or metadata. A badge is an **indicator plate**: part of the record
|
|
|
20
20
|
- Do pair status colour with the word: "Outage", never a lamp alone.
|
|
21
21
|
- Don't make badges clickable; if it filters or navigates, it's a ghost Button.
|
|
22
22
|
- Don't use `count` for totals that aren't asking for attention; use a plate or plain text.
|
|
23
|
+
|
|
24
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
25
|
+
## Example
|
|
26
|
+
|
|
27
|
+
`Badge` as first used in its preview:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Badge } from 'altitude-design';
|
|
31
|
+
|
|
32
|
+
<Badge lamp>Draft</Badge>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `row`, `anchor`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import * as React from 'react';
|
|
39
|
+
import { Badge, Button } from 'altitude-design';
|
|
40
|
+
|
|
41
|
+
export default function App() {
|
|
42
|
+
const [deploying, setDeploying] = React.useState(true);
|
|
43
|
+
const [n, setN] = React.useState(3);
|
|
44
|
+
React.useEffect(() => { const t = setInterval(() => { setDeploying((d) => !d); setN((x) => (x % 5) + 1); }, 2400); return () => clearInterval(t); }, []);
|
|
45
|
+
return (
|
|
46
|
+
<div className="col">
|
|
47
|
+
<div className="row">
|
|
48
|
+
<Badge lamp>Draft</Badge>
|
|
49
|
+
<Badge tone="accent">Beta</Badge>
|
|
50
|
+
<Badge tone="positive">Operational</Badge>
|
|
51
|
+
<Badge tone="caution">Degraded</Badge>
|
|
52
|
+
<Badge tone="critical">Outage</Badge>
|
|
53
|
+
<Badge tone={deploying ? 'accent' : 'positive'} lamp={deploying ? 'pulse' : true}>{deploying ? 'Deploying' : 'Live'}</Badge>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="row">
|
|
56
|
+
<Badge tone="positive" variant="tinted">Operational</Badge>
|
|
57
|
+
<Badge tone="critical" variant="tinted">Outage</Badge>
|
|
58
|
+
<Badge variant="outline">v2.4.0</Badge>
|
|
59
|
+
<span className="anchor"><Button>Inbox</Button><Badge variant="count" tone="critical">{n}</Badge></span>
|
|
60
|
+
</div>
|
|
61
|
+
<p className="cap">Labels are stamped into the record; status wears a lamp that relights when it changes. A count is the one badge that floats, because it wants you.</p>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
<!-- /example -->
|
|
@@ -45,3 +45,65 @@ The y axis always includes zero, whatever the container's `zero` says: a truncat
|
|
|
45
45
|
- Do use `stack="expand"` only when the shares matter more than the totals, and say so in the title.
|
|
46
46
|
- Don't label every bar: label the one the chart is about, and leave the rest to the readout and the table.
|
|
47
47
|
- Don't colour bars by their value or give them a gradient; for polarity, a diverging ramp on a separate chart says it better.
|
|
48
|
+
|
|
49
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
50
|
+
## Example
|
|
51
|
+
|
|
52
|
+
`BarChart` as first used in its preview:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import { BarChart } from 'altitude-design';
|
|
56
|
+
|
|
57
|
+
<BarChart data={teams} x="team" sort="desc" labels="max" focus={(d) => d.team === 'Cedar' || d.team === 'Delta'} />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The full preview, as rendered in Storybook and checked in CI (`bc` is the preview page's own layout class, not Altitude's):
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { BarChart, Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer } from 'altitude-design';
|
|
64
|
+
|
|
65
|
+
const teams = [{ team: 'Atlas', deploys: 42 }, { team: 'Birch', deploys: 35 }, { team: 'Cedar', deploys: 51 }, { team: 'Delta', deploys: 19 }, { team: 'Ember', deploys: 28 }];
|
|
66
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, i) => ({ month, api: 20 + ((i * 7) % 11), web: 14 + ((i * 5) % 9), jobs: 6 + ((i * 3) % 7) }));
|
|
67
|
+
const net = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, i) => ({ month, change: [12, -8, 5, -15, 9, 18][i] }));
|
|
68
|
+
const regions = [{ region: 'North America', seats: 1240, before: 1120 }, { region: 'Europe, Middle East and Africa', seats: 980, before: 1040 }, { region: 'Asia Pacific', seats: 610, before: 470 }, { region: 'Latin America', seats: 270, before: 240 }];
|
|
69
|
+
const stack3 = { api: { label: 'API' }, web: { label: 'Web' }, jobs: { label: 'Jobs' } };
|
|
70
|
+
function Chart({ id, title, sub, children, ...props }) {
|
|
71
|
+
// With a takeaway the figure names itself: the title becomes the label over the takeaway, the heading.
|
|
72
|
+
if (props.takeaway) return (
|
|
73
|
+
<Card>
|
|
74
|
+
<CardContent style={{ paddingTop: 'var(--space-5)' }}><ChartContainer title={title} description={sub} aspect={1.45} {...props}>{children}</ChartContainer></CardContent>
|
|
75
|
+
</Card>
|
|
76
|
+
);
|
|
77
|
+
return (
|
|
78
|
+
<Card>
|
|
79
|
+
<CardHeader><CardTitle id={id}>{title}</CardTitle><CardDescription>{sub}</CardDescription></CardHeader>
|
|
80
|
+
<CardContent><ChartContainer aria-labelledby={id} aspect={1.45} {...props}>{children}</ChartContainer></CardContent>
|
|
81
|
+
</Card>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
export default function App() {
|
|
85
|
+
return (
|
|
86
|
+
<div className="bc">
|
|
87
|
+
<Chart id="b1" title="Deploys by team" takeaway="Cedar deployed nearly three times as often as Delta" sub='One series, sorted, labels="max"' config={{ deploys: { label: 'Deploys' } }} summary="Cedar deployed most, 51 times; Delta least, 19.">
|
|
88
|
+
<BarChart data={teams} x="team" sort="desc" labels="max" focus={(d) => d.team === 'Cedar' || d.team === 'Delta'} />
|
|
89
|
+
</Chart>
|
|
90
|
+
<Chart id="b2" title="Requests by service" sub="Grouped: three series side by side" config={stack3}>
|
|
91
|
+
<BarChart data={months.slice(3)} x="month" />
|
|
92
|
+
</Chart>
|
|
93
|
+
<Chart id="b3" title="Requests, all services" sub='Stacked, labels="last", focus={["web"]}' config={stack3}>
|
|
94
|
+
<BarChart data={months} x="month" stack labels="last" focus={['web']} />
|
|
95
|
+
</Chart>
|
|
96
|
+
<Chart id="b4" title="Share of requests" sub='stack="expand": parts of 100%' config={stack3}>
|
|
97
|
+
<BarChart data={months} x="month" stack="expand" />
|
|
98
|
+
</Chart>
|
|
99
|
+
<Chart id="b5" title="Net new customers" sub="Negative bars hang below the ground line" config={{ change: { label: 'Net change', format: (v) => (v > 0 ? '+' : '') + v } }}>
|
|
100
|
+
<BarChart data={net} x="month" />
|
|
101
|
+
</Chart>
|
|
102
|
+
<Chart id="b6" title="Seats by region" sub='layout="horizontal", against last quarter' config={{ seats: { label: 'Seats', format: (v) => v.toLocaleString('en') } }}>
|
|
103
|
+
<BarChart data={regions} x="region" layout="horizontal" compare={{ key: 'before', label: 'Q2' }} labels="max" />
|
|
104
|
+
</Chart>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
<!-- /example -->
|
|
@@ -27,3 +27,61 @@ Top pages, top referrers, top customers by spend: a ranked list where each row i
|
|
|
27
27
|
- Do fold a long tail into "Other" with `limit` rather than scrolling a hundred rows.
|
|
28
28
|
- Don't colour bars by status or give each row its own colour: colour is identity, and these rows are one series.
|
|
29
29
|
- Don't set the bars' lengths to share of total yourself; `share` says it in words, and the bars compare rows with each other.
|
|
30
|
+
|
|
31
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
`BarList` as first used in its preview:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { BarList } from 'altitude-design';
|
|
38
|
+
|
|
39
|
+
<BarList aria-labelledby="bl-pages" data={pages.map((p) => ({ ...p, href: '#' + p.name }))} limit={7} share noun="pages" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The full preview, as rendered in Storybook and checked in CI (`bl`, `bl-tools` are the preview page's own layout classes, not Altitude's):
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import * as React from 'react';
|
|
46
|
+
import { BarList, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input } from 'altitude-design';
|
|
47
|
+
|
|
48
|
+
const pages = [
|
|
49
|
+
{ name: '/pricing', value: 4210 }, { name: '/', value: 3880 }, { name: '/docs/getting-started', value: 2410 }, { name: '/blog/height-is-the-interface-why-we-stopped-using-shadows-for-everything', value: 1630 },
|
|
50
|
+
{ name: '/changelog', value: 1210 }, { name: '/docs/components/chart-container', value: 980 }, { name: '/careers', value: 610 },
|
|
51
|
+
...Array.from({ length: 14 }, (_, i) => ({ name: `/docs/page-${i + 1}`, value: 420 - i * 25 })),
|
|
52
|
+
];
|
|
53
|
+
const referrers0 = [{ name: 'google.com', value: 5120 }, { name: 'github.com', value: 2310 }, { name: 'news.ycombinator.com', value: 1980 }, { name: 'x.com', value: 760 }, { name: 'Direct', value: 3400 }];
|
|
54
|
+
const referrers1 = [{ name: 'google.com', value: 5240 }, { name: 'github.com', value: 2390 }, { name: 'news.ycombinator.com', value: 6120 }, { name: 'x.com', value: 1310 }, { name: 'Direct', value: 3460 }];
|
|
55
|
+
export default function App() {
|
|
56
|
+
const [spike, setSpike] = React.useState(false);
|
|
57
|
+
const [q, setQ] = React.useState('');
|
|
58
|
+
const [later, setLater] = React.useState(undefined);
|
|
59
|
+
React.useEffect(() => { const t = setTimeout(() => setLater([{ name: 'Acme Corp', value: 48200 }, { name: 'Globex', value: 31900 }, { name: 'Initech', value: 22750 }, { name: 'Umbrella', value: 9400 }]), 2400); return () => clearTimeout(t); }, []);
|
|
60
|
+
const usd = new Intl.NumberFormat('en', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
|
|
61
|
+
return (
|
|
62
|
+
<div className="bl">
|
|
63
|
+
<Card>
|
|
64
|
+
<CardHeader><CardTitle id="bl-pages">Top pages</CardTitle><CardDescription>Last 7 days · views</CardDescription></CardHeader>
|
|
65
|
+
<CardContent><BarList aria-labelledby="bl-pages" data={pages.map((p) => ({ ...p, href: '#' + p.name }))} limit={7} share noun="pages" /></CardContent>
|
|
66
|
+
</Card>
|
|
67
|
+
<div style={{ display: 'grid', gap: 16, alignContent: 'start' }}>
|
|
68
|
+
<Card>
|
|
69
|
+
<CardHeader><CardTitle id="bl-ref">Top referrers</CardTitle><CardDescription>Static rows: nothing lifts</CardDescription></CardHeader>
|
|
70
|
+
<CardContent>
|
|
71
|
+
<div className="bl-tools">
|
|
72
|
+
<Button size="sm" variant="secondary" onClick={() => setSpike((s) => !s)}>{spike ? 'Yesterday' : 'Today'}</Button>
|
|
73
|
+
<Input size="sm" placeholder="Filter" aria-label="Filter referrers" value={q} onChange={(e) => setQ(e.target.value)} />
|
|
74
|
+
</div>
|
|
75
|
+
<BarList aria-labelledby="bl-ref" data={spike ? referrers1 : referrers0} relevance={q ? (d) => (d.name.includes(q) ? 'match' : 'miss') : undefined} />
|
|
76
|
+
</CardContent>
|
|
77
|
+
</Card>
|
|
78
|
+
<Card>
|
|
79
|
+
<CardHeader><CardTitle id="bl-cust">Top customers</CardTitle><CardDescription>Spend this quarter</CardDescription></CardHeader>
|
|
80
|
+
<CardContent><BarList aria-labelledby="bl-cust" data={later} format={usd.format} limit={4} /></CardContent>
|
|
81
|
+
</Card>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
<!-- /example -->
|
|
@@ -29,3 +29,70 @@ Columns of cards you move between, a kanban: columns are receptacles that open w
|
|
|
29
29
|
- Do keep buttons and links inside cards: pressing them doesn't pick the card up.
|
|
30
30
|
- Do collapse finished columns rather than hiding them; the pile keeps their weight visible.
|
|
31
31
|
- Don't use a board for a single list you reorder; that's SortableList.
|
|
32
|
+
|
|
33
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
34
|
+
## Example
|
|
35
|
+
|
|
36
|
+
`Board` as first used in its preview:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
import { Board, BoardCard, BoardColumn, Field, Input } from 'altitude-design';
|
|
40
|
+
|
|
41
|
+
<Board onMove={move} aria-label="Launch board">
|
|
42
|
+
{Object.keys(titles).map(k => <BoardColumn key={k} id={k} title={titles[k]} collapsible={k === "done"} defaultCollapsed={k === "done"} onCreate={k === "done" ? undefined : create(k)}>
|
|
43
|
+
{cols[k].map(c => <BoardCard key={c.id} id={c.id} label={c.t} heldBy={c.held} onRename={rename(k, c.id)}
|
|
44
|
+
{...(k === "review" ? { value: { title: c.t, meta: c.m }, onValueChange: update(k, c.id),
|
|
45
|
+
renderEditor: field => <><Field label="Title"><Input {...field("title")} size="sm" /></Field><Field label="Details"><Input {...field("meta")} size="sm" /></Field></>,
|
|
46
|
+
onSave: async draft => { await wait(200); return refuse(draft.title) || { ok: true, value: draft }; } } : {})}>
|
|
47
|
+
{c.t}<small>{c.m}</small>
|
|
48
|
+
</BoardCard>)}
|
|
49
|
+
</BoardColumn>)}
|
|
50
|
+
</Board>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import * as React from 'react';
|
|
57
|
+
import { Board, BoardCard, BoardColumn, Field, Input } from 'altitude-design';
|
|
58
|
+
|
|
59
|
+
const start = {
|
|
60
|
+
todo: [{ id: "c1", t: "Write release notes", m: "Docs \xB7 Jon" }, { id: "c2", t: "Fix cold-start regression", m: "Runtime \xB7 Lena" }, { id: "c3", t: "Plan Q1 roadmap", m: "Planning \xB7 Ada" }],
|
|
61
|
+
doing: [{ id: "c4", t: "Pricing page copy", m: "Marketing \xB7 Mara", held: { name: "Mara", presence: 1 } }, { id: "c5", t: "Support macros", m: "Support \xB7 Ivy" }],
|
|
62
|
+
review: [{ id: "c6", t: "Brand voice", m: "Design \xB7 Ada" }],
|
|
63
|
+
done: Array.from({ length: 12 }, (_, i) => ({ id: "d" + i, t: "Shipped item " + (i + 1), m: "Done" }))
|
|
64
|
+
};
|
|
65
|
+
const titles = { todo: "To do", doing: "In progress", review: "Review", done: "Done" };
|
|
66
|
+
export default function App() {
|
|
67
|
+
const [cols, setCols] = React.useState(start);
|
|
68
|
+
const [last, setLast] = React.useState(null);
|
|
69
|
+
const seq = React.useRef(0);
|
|
70
|
+
const move = ({ cardId, from, to }) => setCols((c) => {
|
|
71
|
+
const next = Object.fromEntries(Object.entries(c).map(([k, v]) => [k, v.slice()]));
|
|
72
|
+
const [card] = next[from.column].splice(from.index, 1);
|
|
73
|
+
next[to.column].splice(to.index, 0, card);
|
|
74
|
+
setLast(`${card.t} \u2192 ${titles[to.column]}`);
|
|
75
|
+
return next;
|
|
76
|
+
});
|
|
77
|
+
const wait = ms => new Promise(r => setTimeout(r, ms));
|
|
78
|
+
// The demo service refuses titles with “TBD” in them.
|
|
79
|
+
const refuse = t => /tbd/i.test(t) ? { ok: false, error: "Write the task, not \u201cTBD\u201d." } : null;
|
|
80
|
+
const create = k => async t => { await wait(250); const no = refuse(t); if (no) return no; const card = { id: "n" + ++seq.current, t, m: "New" }; setCols(c => ({ ...c, [k]: [...c[k], card] })); return { ok: true, value: { id: card.id } }; };
|
|
81
|
+
const rename = (k, id) => async t => { await wait(200); const no = refuse(t); if (no) return no; setCols(c => ({ ...c, [k]: c[k].map(x => x.id === id ? { ...x, t } : x) })); return { ok: true, value: t }; };
|
|
82
|
+
const update = (k, id) => v => setCols(c => ({ ...c, [k]: c[k].map(x => x.id === id ? { ...x, t: v.title, m: v.meta } : x) }));
|
|
83
|
+
return <div className="col" style={{ gap: 4 }}>
|
|
84
|
+
<Board onMove={move} aria-label="Launch board">
|
|
85
|
+
{Object.keys(titles).map(k => <BoardColumn key={k} id={k} title={titles[k]} collapsible={k === "done"} defaultCollapsed={k === "done"} onCreate={k === "done" ? undefined : create(k)}>
|
|
86
|
+
{cols[k].map(c => <BoardCard key={c.id} id={c.id} label={c.t} heldBy={c.held} onRename={rename(k, c.id)}
|
|
87
|
+
{...(k === "review" ? { value: { title: c.t, meta: c.m }, onValueChange: update(k, c.id),
|
|
88
|
+
renderEditor: field => <><Field label="Title"><Input {...field("title")} size="sm" /></Field><Field label="Details"><Input {...field("meta")} size="sm" /></Field></>,
|
|
89
|
+
onSave: async draft => { await wait(200); return refuse(draft.title) || { ok: true, value: draft }; } } : {})}>
|
|
90
|
+
{c.t}<small>{c.m}</small>
|
|
91
|
+
</BoardCard>)}
|
|
92
|
+
</BoardColumn>)}
|
|
93
|
+
</Board>
|
|
94
|
+
<p className="cap">{last ? `Moved: ${last}.` : "Drag a card, or focus one and press Space, then the arrow keys. F2 renames a card; each column adds cards at its foot. Done is collapsed into a pile and still takes cards."}</p>
|
|
95
|
+
</div>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
<!-- /example -->
|
|
@@ -33,3 +33,34 @@ Where you are in an app with routes: Settings › Members › Jane Doe, or Proje
|
|
|
33
33
|
- Do use it for location in a hierarchy, not for history ("back").
|
|
34
34
|
- Don't add separators between steps.
|
|
35
35
|
- Don't use it inside a `Drilldown`, which has its own.
|
|
36
|
+
|
|
37
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
`Breadcrumb` as first used in its preview:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { Breadcrumb } from 'altitude-design';
|
|
44
|
+
|
|
45
|
+
<Breadcrumb items={[{ label: 'Settings', href: '#/settings' }, { label: 'Members', href: '#/settings/members' }, { label: 'Jane Doe' }]} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import * as React from 'react';
|
|
52
|
+
import { Breadcrumb } from 'altitude-design';
|
|
53
|
+
|
|
54
|
+
const RouterLink = React.forwardRef(({ to, ...rest }, ref) => <a ref={ref} href={'#' + to} {...rest} />);
|
|
55
|
+
export default function App() {
|
|
56
|
+
return (
|
|
57
|
+
<div className="col">
|
|
58
|
+
<Breadcrumb items={[{ label: 'Settings', href: '#/settings' }, { label: 'Members', href: '#/settings/members' }, { label: 'Jane Doe' }]} />
|
|
59
|
+
<Breadcrumb maxItems={4} renderLink={(item, props) => <RouterLink to={item.href.slice(1)} {...props}>{item.label}</RouterLink>}
|
|
60
|
+
items={[{ label: 'Projects', href: '#/projects' }, { label: 'Atlas', href: '#/projects/atlas' }, { label: 'Q4 launch', href: '#/projects/atlas/q4' }, { label: 'Issues', href: '#/projects/atlas/q4/issues' }, { label: 'Billing page', href: '#/projects/atlas/q4/issues/212' }, { label: 'Comments' }]} />
|
|
61
|
+
<p className="cap">Each step you can go back to lifts when pointed at; the current page is the tallest step and doesn't press. A deep path keeps its first and last steps, with the middle behind "…".</p>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
<!-- /example -->
|
|
@@ -15,3 +15,44 @@ The wall is the face colour darkened, so it reads in every scheme and with any c
|
|
|
15
15
|
|
|
16
16
|
- Don't put two primary buttons in one view. Promote one, demote the other to secondary.
|
|
17
17
|
- Leave room below a row of buttons: the wall extends up to 5px under the face.
|
|
18
|
+
|
|
19
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
20
|
+
## Example
|
|
21
|
+
|
|
22
|
+
`Button` as first used in its preview:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { Button } from 'altitude-design';
|
|
26
|
+
|
|
27
|
+
<Button variant="primary">Publish changes</Button>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `row`, `cap` are the preview page's own layout classes, not Altitude's):
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import * as React from 'react';
|
|
34
|
+
import { Button, Icons } from 'altitude-design';
|
|
35
|
+
|
|
36
|
+
export default function App() {
|
|
37
|
+
const [busy, setBusy] = React.useState(false);
|
|
38
|
+
return (
|
|
39
|
+
<div className="col">
|
|
40
|
+
<div className="row">
|
|
41
|
+
<Button variant="primary">Publish changes</Button>
|
|
42
|
+
<Button>Preview</Button>
|
|
43
|
+
<Button variant="ghost">Cancel</Button>
|
|
44
|
+
<Button variant="danger">Delete project</Button>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="row">
|
|
47
|
+
<Button variant="primary" size="sm">Small</Button>
|
|
48
|
+
<Button variant="primary" size="lg">Large</Button>
|
|
49
|
+
<Button square aria-label="Close"><Icons.X /></Button>
|
|
50
|
+
<Button variant="primary" loading={busy} onClick={() => { setBusy(true); setTimeout(() => setBusy(false), 1600); }}>Save</Button>
|
|
51
|
+
<Button disabled>Disabled</Button>
|
|
52
|
+
</div>
|
|
53
|
+
<p className="cap">Primary rests at level 2, secondary at 1, ghost at 0. Hover climbs one level; pressing returns to the ground. Click Save: a loading button stays pressed.</p>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
<!-- /example -->
|
|
@@ -12,3 +12,41 @@ Cards take the same depth signals as `Surface`: `settle`, `status`, `heldBy` and
|
|
|
12
12
|
|
|
13
13
|
- Do make the entire card the target when it navigates somewhere (a project tile).
|
|
14
14
|
- Don't make a card interactive *and* put buttons inside it: nested targets break the press metaphor. Keep the card static and let its buttons lift.
|
|
15
|
+
|
|
16
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
`Card` as first used in its preview:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'altitude-design';
|
|
23
|
+
|
|
24
|
+
<Card>
|
|
25
|
+
<CardHeader><CardTitle>Usage this month</CardTitle><CardDescription>Static: a grouping surface, it never lifts.</CardDescription></CardHeader>
|
|
26
|
+
<CardContent>18,240 requests · 62% of plan</CardContent>
|
|
27
|
+
</Card>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The full preview, as rendered in Storybook and checked in CI (`grid` is the preview page's own layout class, not Altitude's):
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import * as React from 'react';
|
|
34
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from 'altitude-design';
|
|
35
|
+
|
|
36
|
+
export default function App() {
|
|
37
|
+
const [n, setN] = React.useState(0);
|
|
38
|
+
return (
|
|
39
|
+
<div className="grid">
|
|
40
|
+
<Card>
|
|
41
|
+
<CardHeader><CardTitle>Usage this month</CardTitle><CardDescription>Static: a grouping surface, it never lifts.</CardDescription></CardHeader>
|
|
42
|
+
<CardContent>18,240 requests · 62% of plan</CardContent>
|
|
43
|
+
</Card>
|
|
44
|
+
<Card interactive onClick={() => setN(n + 1)}>
|
|
45
|
+
<CardHeader><CardTitle>Open project</CardTitle><CardDescription>Interactive: the whole card is the target, so it lifts and presses.</CardDescription></CardHeader>
|
|
46
|
+
<CardContent>{n ? `Opened ${n}×` : 'Click or press Enter'}</CardContent>
|
|
47
|
+
</Card>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
<!-- /example -->
|
|
@@ -39,3 +39,60 @@ const [range, setRange] = React.useState({ start: '2026-03-01', end: '2026-04-30
|
|
|
39
39
|
- Do give the window a typed twin (a `DateRangePicker`) when exact dates matter; dragging is for finding, typing for stating.
|
|
40
40
|
- Don't use a brush for a few dozen records: the chart can show them all.
|
|
41
41
|
- Don't put a brush on a chart of categories; it windows records in their order, which only means something over time.
|
|
42
|
+
|
|
43
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
`ChartBrush` as first used in its preview:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { ChartBrush } from 'altitude-design';
|
|
50
|
+
|
|
51
|
+
<ChartBrush />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The full preview, as rendered in Storybook and checked in CI (`cb`, `col`, `pick` are the preview page's own layout classes, not Altitude's):
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import * as React from 'react';
|
|
58
|
+
import { AreaChart, BarChart, Card, CardContent, CardDescription, CardHeader, CardTitle, ChartBrush, ChartContainer, DateRangePicker, Field } from 'altitude-design';
|
|
59
|
+
|
|
60
|
+
// A year of daily active users, with a dip in August and a launch in late September.
|
|
61
|
+
const cbYear = Array.from({ length: 365 }, (_, i) => {
|
|
62
|
+
const day = new Date(Date.UTC(2026, 0, 1 + i)).toISOString().slice(0, 10);
|
|
63
|
+
const trend = 4200 + i * 9, week = [0.82, 1, 1.04, 1.05, 1.03, 0.97, 0.78][i % 7];
|
|
64
|
+
const summer = i > 212 && i < 243 ? 0.8 : 1, launch = i > 265 ? 1.18 : 1;
|
|
65
|
+
return { day, active: Math.round((trend + 260 * Math.sin(i / 9)) * week * summer * launch) };
|
|
66
|
+
});
|
|
67
|
+
const cbWeeks = Array.from({ length: 52 }, (_, i) => ({ week: new Date(Date.UTC(2026, 0, 5 + i * 7)).toISOString().slice(0, 10), signups: Math.round(310 + i * 6 + 90 * Math.sin(i / 3) + (i > 37 ? 160 : 0)) }));
|
|
68
|
+
const cbK = new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 });
|
|
69
|
+
export default function App() {
|
|
70
|
+
const [range, setRange] = React.useState({ start: '2026-03-01', end: '2026-04-30' });
|
|
71
|
+
return (
|
|
72
|
+
<div className="cb">
|
|
73
|
+
<Card>
|
|
74
|
+
<CardHeader><CardTitle id="cb1">Daily active users</CardTitle><CardDescription>Drag the lens to pan, a grip to resize, or press the overview to centre it there. The picker is the same state, typed.</CardDescription></CardHeader>
|
|
75
|
+
<CardContent className="col">
|
|
76
|
+
<Field label="Window" className="pick"><DateRangePicker value={range} onValueChange={setRange} min="2026-01-01" max="2026-12-31" locale="en-GB" /></Field>
|
|
77
|
+
<ChartContainer aria-labelledby="cb1" config={{ active: { label: 'Active users', format: cbK.format } }} aspect={3.4} locale="en-GB">
|
|
78
|
+
<AreaChart data={cbYear} x="day" window={range} onWindowChange={setRange}>
|
|
79
|
+
<ChartBrush />
|
|
80
|
+
</AreaChart>
|
|
81
|
+
</ChartContainer>
|
|
82
|
+
</CardContent>
|
|
83
|
+
</Card>
|
|
84
|
+
<Card>
|
|
85
|
+
<CardHeader><CardTitle id="cb2">Weekly sign-ups</CardTitle><CardDescription>Bars over time with a brush that keeps its own window</CardDescription></CardHeader>
|
|
86
|
+
<CardContent>
|
|
87
|
+
<ChartContainer aria-labelledby="cb2" config={{ signups: { label: 'Sign-ups' } }} aspect={4} locale="en-GB">
|
|
88
|
+
<BarChart data={cbWeeks} x="week">
|
|
89
|
+
<ChartBrush />
|
|
90
|
+
</BarChart>
|
|
91
|
+
</ChartContainer>
|
|
92
|
+
</CardContent>
|
|
93
|
+
</Card>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
<!-- /example -->
|
|
@@ -150,3 +150,80 @@ function Target({ value }) {
|
|
|
150
150
|
- Don't give marks a shadow, a wall, a sheen, a gradient or a lift.
|
|
151
151
|
- Don't colour text with a series colour; labels and ticks wear `ink` and `ink-muted`.
|
|
152
152
|
- Don't draw more than about 2,000 marks: SVG gets slow. Aggregate, or use a canvas library with Altitude's theme (#77).
|
|
153
|
+
|
|
154
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
155
|
+
## Example
|
|
156
|
+
|
|
157
|
+
`ChartContainer` as first used in its preview:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { ChartContainer } from 'altitude-design';
|
|
161
|
+
|
|
162
|
+
<ChartContainer aria-labelledby="rc" data={months} x="month" aspect={1.7} summary="Revenue rose 44% while costs held flat."
|
|
163
|
+
config={{ revenue: { label: 'Revenue', format: usd.format }, costs: { label: 'Costs', format: usd.format } }}>
|
|
164
|
+
<Lines />
|
|
165
|
+
</ChartContainer>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The full preview, as rendered in Storybook and checked in CI (`charts` is the preview page's own layout class, not Altitude's):
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer, ChartMarker, useChart } from 'altitude-design';
|
|
172
|
+
|
|
173
|
+
// Until the chart types land, these marks are drawn by hand with useChart(): the same scales the types will use.
|
|
174
|
+
const usd = new Intl.NumberFormat('en', { style: 'currency', currency: 'USD', notation: 'compact' });
|
|
175
|
+
const months = [
|
|
176
|
+
{ month: '2026-01-01', revenue: 8200, costs: 5100 }, { month: '2026-02-01', revenue: 8900, costs: 5000 },
|
|
177
|
+
{ month: '2026-03-01', revenue: 9400, costs: 5300 }, { month: '2026-04-01', revenue: 9100, costs: 5200 },
|
|
178
|
+
{ month: '2026-05-01', revenue: 10600, costs: 5400 }, { month: '2026-06-01', revenue: 11800, costs: 5300 },
|
|
179
|
+
];
|
|
180
|
+
const regions = [
|
|
181
|
+
{ region: 'North', q2: 42, q3: 51 }, { region: 'South', q2: 35, q3: 31 }, { region: 'East', q2: 28, q3: 37 }, { region: 'West', q2: 19, q3: 24 },
|
|
182
|
+
];
|
|
183
|
+
// encode() draws a series by its slot: colour, and a dash, marker or hatch whenever patterns are on (forced colours, print).
|
|
184
|
+
function Lines() {
|
|
185
|
+
const { series, data, xOf, y, encode, patterns, strokeWidth } = useChart();
|
|
186
|
+
return series.map((s) => {
|
|
187
|
+
const e = encode(s.key);
|
|
188
|
+
return <g key={s.key}>
|
|
189
|
+
<polyline fill="none" stroke={e.stroke} strokeDasharray={e.dash} strokeWidth={strokeWidth} strokeLinejoin="round" strokeLinecap="round"
|
|
190
|
+
points={data.map((d) => `${xOf(d)},${y(d[s.key])}`).join(' ')} />
|
|
191
|
+
{patterns && data.map((d, i) => <ChartMarker key={i} shape={e.marker} x={xOf(d)} y={y(d[s.key])} color={e.stroke} />)}
|
|
192
|
+
</g>;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function Bars() {
|
|
196
|
+
const { series, data, x, y, xKey, ground, alpha, encode, gap } = useChart();
|
|
197
|
+
const w = (x.bandwidth - gap * (series.length - 1)) / series.length;
|
|
198
|
+
return data.map((d) => series.map((s, i) => {
|
|
199
|
+
const v = d[s.key], top = Math.min(y(v), ground), e = encode(s.key);
|
|
200
|
+
return <rect key={d[xKey] + s.key} x={x(d[xKey]) + i * (w + gap)} y={top} width={w} height={Math.abs(y(v) - ground)}
|
|
201
|
+
fill={e.fill} stroke={e.edge} strokeWidth={e.edge ? 1 : undefined} opacity={alpha(d, s.key)} />;
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
export default function App() {
|
|
205
|
+
return (
|
|
206
|
+
<div className="charts">
|
|
207
|
+
<Card>
|
|
208
|
+
<CardHeader><CardTitle id="rc">Revenue and costs</CardTitle><CardDescription>Jan to Jun 2026</CardDescription></CardHeader>
|
|
209
|
+
<CardContent>
|
|
210
|
+
<ChartContainer aria-labelledby="rc" data={months} x="month" aspect={1.7} summary="Revenue rose 44% while costs held flat."
|
|
211
|
+
config={{ revenue: { label: 'Revenue', format: usd.format }, costs: { label: 'Costs', format: usd.format } }}>
|
|
212
|
+
<Lines />
|
|
213
|
+
</ChartContainer>
|
|
214
|
+
</CardContent>
|
|
215
|
+
</Card>
|
|
216
|
+
<Card>
|
|
217
|
+
<CardHeader><CardTitle id="dr">Deals closed by region</CardTitle><CardDescription>Q2 and Q3</CardDescription></CardHeader>
|
|
218
|
+
<CardContent>
|
|
219
|
+
<ChartContainer aria-labelledby="dr" data={regions} x="region" aspect={1.7}
|
|
220
|
+
config={{ q2: { label: 'Q2' }, q3: { label: 'Q3' } }}>
|
|
221
|
+
<Bars />
|
|
222
|
+
</ChartContainer>
|
|
223
|
+
</CardContent>
|
|
224
|
+
</Card>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
<!-- /example -->
|
|
@@ -33,3 +33,46 @@ Names each series, and optionally lets people hide and show series. Every chart
|
|
|
33
33
|
- Do use `toggle` when people need to compare a few series out of many; otherwise a static legend is quieter.
|
|
34
34
|
- Don't let colour be the only link between a key and its series: under forced colours the keys show each series' pattern.
|
|
35
35
|
- Don't put the legend somewhere the chart's readout or summary can't be read alongside it.
|
|
36
|
+
|
|
37
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
`ChartLegend` as first used in its preview:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ChartLegend, ChartLegendContent } from 'altitude-design';
|
|
44
|
+
|
|
45
|
+
<ChartLegend content={<ChartLegendContent />} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The full preview, as rendered in Storybook and checked in CI (`cl` is the preview page's own layout class, not Altitude's):
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { BarChart, Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer, ChartLegend, ChartLegendContent, LineChart } from 'altitude-design';
|
|
52
|
+
|
|
53
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'].map((month, i) => ({ month, api: 20 + ((i * 7) % 11), web: 14 + ((i * 5) % 9), jobs: 6 + ((i * 3) % 7) }));
|
|
54
|
+
const plans = ['2026-01-01', '2026-02-01', '2026-03-01', '2026-04-01', '2026-05-01', '2026-06-01'].map((month, i) => ({ month, free: 40 + i * 2, team: 34 + i * 3, business: 30 + i * 2.6, enterprise: 12 + i }));
|
|
55
|
+
export default function App() {
|
|
56
|
+
return (
|
|
57
|
+
<div className="cl">
|
|
58
|
+
<Card>
|
|
59
|
+
<CardHeader><CardTitle id="g1">Requests, all services</CardTitle><CardDescription>A legend that only labels</CardDescription></CardHeader>
|
|
60
|
+
<CardContent>
|
|
61
|
+
<ChartContainer aria-labelledby="g1" aspect={1.8} config={{ api: { label: 'API' }, web: { label: 'Web' }, jobs: { label: 'Jobs' } }}>
|
|
62
|
+
<BarChart data={months} x="month" stack><ChartLegend content={<ChartLegendContent />} /></BarChart>
|
|
63
|
+
</ChartContainer>
|
|
64
|
+
</CardContent>
|
|
65
|
+
</Card>
|
|
66
|
+
<Card>
|
|
67
|
+
<CardHeader><CardTitle id="g2">Seats by plan</CardTitle><CardDescription>toggle: each key is a switch; Enterprise starts hidden</CardDescription></CardHeader>
|
|
68
|
+
<CardContent>
|
|
69
|
+
<ChartContainer aria-labelledby="g2" aspect={1.8} config={{ free: { label: 'Free' }, team: { label: 'Team' }, business: { label: 'Business' }, enterprise: { label: 'Enterprise' } }}>
|
|
70
|
+
<LineChart data={plans} x="month"><ChartLegend content={<ChartLegendContent toggle defaultHidden={['enterprise']} />} /></LineChart>
|
|
71
|
+
</ChartContainer>
|
|
72
|
+
</CardContent>
|
|
73
|
+
</Card>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
<!-- /example -->
|