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.
Files changed (280) hide show
  1. package/README.md +1 -0
  2. package/dist/guidance/components/AILabel/README.md +35 -0
  3. package/dist/guidance/components/Accordion/README.md +25 -0
  4. package/dist/guidance/components/AgentFleet/README.md +48 -1
  5. package/dist/guidance/components/AgentLanes/README.md +32 -0
  6. package/dist/guidance/components/AgentRequests/README.md +47 -0
  7. package/dist/guidance/components/Alert/README.md +49 -0
  8. package/dist/guidance/components/AltitudeProvider/README.md +63 -0
  9. package/dist/guidance/components/AppShell/README.md +70 -0
  10. package/dist/guidance/components/AreaChart/README.md +58 -0
  11. package/dist/guidance/components/Artifact/README.md +69 -0
  12. package/dist/guidance/components/Avatar/README.md +41 -0
  13. package/dist/guidance/components/Await/README.md +48 -0
  14. package/dist/guidance/components/Badge/README.md +44 -0
  15. package/dist/guidance/components/BarChart/README.md +62 -0
  16. package/dist/guidance/components/BarList/README.md +58 -0
  17. package/dist/guidance/components/Board/README.md +67 -0
  18. package/dist/guidance/components/Breadcrumb/README.md +31 -0
  19. package/dist/guidance/components/Button/README.md +41 -0
  20. package/dist/guidance/components/Card/README.md +38 -0
  21. package/dist/guidance/components/ChartBrush/README.md +57 -0
  22. package/dist/guidance/components/ChartContainer/README.md +77 -0
  23. package/dist/guidance/components/ChartLegend/README.md +43 -0
  24. package/dist/guidance/components/ChartReference/README.md +74 -0
  25. package/dist/guidance/components/ChartTooltip/README.md +63 -0
  26. package/dist/guidance/components/Checkbox/README.md +35 -0
  27. package/dist/guidance/components/Checklist/README.md +66 -0
  28. package/dist/guidance/components/CodeBlock/README.md +44 -0
  29. package/dist/guidance/components/ColorPicker/README.md +39 -0
  30. package/dist/guidance/components/Combobox/README.md +35 -0
  31. package/dist/guidance/components/CommandPalette/README.md +177 -0
  32. package/dist/guidance/components/CommentThread/README.md +51 -0
  33. package/dist/guidance/components/Confirmation/README.md +60 -0
  34. package/dist/guidance/components/ContextMenu/README.md +51 -0
  35. package/dist/guidance/components/Conversation/README.md +124 -0
  36. package/dist/guidance/components/CopyButton/README.md +31 -0
  37. package/dist/guidance/components/DataGrid/README.md +75 -0
  38. package/dist/guidance/components/DatePicker/README.md +49 -0
  39. package/dist/guidance/components/Dialog/README.md +175 -0
  40. package/dist/guidance/components/DiffView/README.md +77 -0
  41. package/dist/guidance/components/DotPlot/README.md +44 -0
  42. package/dist/guidance/components/Drilldown/README.md +28 -0
  43. package/dist/guidance/components/DropZone/README.md +50 -0
  44. package/dist/guidance/components/DropdownMenu/README.md +44 -0
  45. package/dist/guidance/components/EditRegion/README.md +159 -0
  46. package/dist/guidance/components/EmptyState/README.md +49 -0
  47. package/dist/guidance/components/FeatureGate/README.md +39 -0
  48. package/dist/guidance/components/Field/README.md +36 -0
  49. package/dist/guidance/components/FilterBar/README.md +63 -0
  50. package/dist/guidance/components/FormattedNumber/README.md +44 -0
  51. package/dist/guidance/components/Heatmap/README.md +71 -0
  52. package/dist/guidance/components/HoldButton/README.md +32 -0
  53. package/dist/guidance/components/HoverCard/README.md +53 -0
  54. package/dist/guidance/components/InlineCitation/README.md +32 -0
  55. package/dist/guidance/components/InlineEdit/README.md +247 -1
  56. package/dist/guidance/components/Input/README.md +36 -0
  57. package/dist/guidance/components/InputOTP/README.md +46 -0
  58. package/dist/guidance/components/ItemGroup/README.md +100 -0
  59. package/dist/guidance/components/Kbd/README.md +27 -0
  60. package/dist/guidance/components/LineChart/README.md +62 -0
  61. package/dist/guidance/components/Link/README.md +41 -0
  62. package/dist/guidance/components/Markdown/README.md +81 -0
  63. package/dist/guidance/components/Meter/README.md +52 -0
  64. package/dist/guidance/components/NotificationCenter/README.md +39 -0
  65. package/dist/guidance/components/NumberInput/README.md +37 -0
  66. package/dist/guidance/components/PageHeader/README.md +45 -0
  67. package/dist/guidance/components/Pagination/README.md +33 -0
  68. package/dist/guidance/components/PasswordInput/README.md +36 -0
  69. package/dist/guidance/components/PermissionMatrix/README.md +40 -0
  70. package/dist/guidance/components/Pile/README.md +42 -0
  71. package/dist/guidance/components/PlanPicker/README.md +37 -0
  72. package/dist/guidance/components/Popover/README.md +36 -0
  73. package/dist/guidance/components/Progress/README.md +31 -0
  74. package/dist/guidance/components/PromptInput/README.md +98 -0
  75. package/dist/guidance/components/PropertyList/README.md +80 -0
  76. package/dist/guidance/components/Question/README.md +54 -0
  77. package/dist/guidance/components/RadioGroup/README.md +37 -0
  78. package/dist/guidance/components/RelativeTime/README.md +47 -0
  79. package/dist/guidance/components/RemovalReceipt/README.md +77 -0
  80. package/dist/guidance/components/Resizable/README.md +29 -0
  81. package/dist/guidance/components/SaveBar/README.md +38 -0
  82. package/dist/guidance/components/SavedViews/README.md +58 -0
  83. package/dist/guidance/components/ScatterChart/README.md +63 -0
  84. package/dist/guidance/components/Schedule/README.md +66 -0
  85. package/dist/guidance/components/ScrollArea/README.md +28 -0
  86. package/dist/guidance/components/SecretField/README.md +46 -0
  87. package/dist/guidance/components/Select/README.md +70 -0
  88. package/dist/guidance/components/Separator/README.md +44 -0
  89. package/dist/guidance/components/Sheet/README.md +94 -0
  90. package/dist/guidance/components/Skeleton/README.md +36 -0
  91. package/dist/guidance/components/Slider/README.md +35 -0
  92. package/dist/guidance/components/SortableList/README.md +34 -0
  93. package/dist/guidance/components/Sparkline/README.md +58 -0
  94. package/dist/guidance/components/SplitButton/README.md +39 -0
  95. package/dist/guidance/components/Stat/README.md +61 -0
  96. package/dist/guidance/components/Stepper/README.md +61 -0
  97. package/dist/guidance/components/Suggestions/README.md +58 -0
  98. package/dist/guidance/components/Surface/README.md +28 -0
  99. package/dist/guidance/components/Switch/README.md +29 -0
  100. package/dist/guidance/components/SyncIndicator/README.md +54 -0
  101. package/dist/guidance/components/Table/README.md +62 -0
  102. package/dist/guidance/components/Tabs/README.md +55 -0
  103. package/dist/guidance/components/TagInput/README.md +50 -0
  104. package/dist/guidance/components/Terminal/README.md +42 -0
  105. package/dist/guidance/components/ThreadList/README.md +73 -0
  106. package/dist/guidance/components/TimeInput/README.md +37 -0
  107. package/dist/guidance/components/Timeline/README.md +55 -0
  108. package/dist/guidance/components/Toaster/README.md +38 -0
  109. package/dist/guidance/components/Toggle/README.md +52 -0
  110. package/dist/guidance/components/Toolbar/README.md +69 -0
  111. package/dist/guidance/components/Tooltip/README.md +29 -0
  112. package/dist/guidance/components/Tour/README.md +44 -0
  113. package/dist/guidance/components/Trace/README.md +92 -0
  114. package/dist/guidance/components/Tree/README.md +85 -0
  115. package/dist/guidance/index.json +117 -117
  116. package/dist/guidance/llms.txt +1 -1
  117. package/dist/guidance/manifest.json +120 -120
  118. package/dist/guidance/visual/ActionsInPlace.md +5 -5
  119. package/dist/guidance/visual/Distinctiveness.md +6 -2
  120. package/dist/guidance/visual/Research.md +14 -2
  121. package/dist/guidance/visual/Theming.md +1 -1
  122. package/dist/types/agent-fleet.d.ts +28 -0
  123. package/dist/types/ai-label.d.ts +7 -0
  124. package/dist/types/app-shell.d.ts +23 -0
  125. package/dist/types/area-chart.d.ts +8 -0
  126. package/dist/types/artifact.d.ts +14 -0
  127. package/dist/types/bar-chart.d.ts +6 -0
  128. package/dist/types/bar-list.d.ts +6 -0
  129. package/dist/types/breadcrumb.d.ts +6 -0
  130. package/dist/types/chart-brush.d.ts +6 -0
  131. package/dist/types/chart.d.ts +29 -1
  132. package/dist/types/checklist.d.ts +6 -0
  133. package/dist/types/code-block.d.ts +6 -0
  134. package/dist/types/collections.d.ts +15 -0
  135. package/dist/types/color-picker.d.ts +6 -0
  136. package/dist/types/command.d.ts +13 -0
  137. package/dist/types/comment-thread.d.ts +11 -0
  138. package/dist/types/confirmation.d.ts +8 -0
  139. package/dist/types/context-menu.d.ts +11 -0
  140. package/dist/types/controls.d.ts +174 -10
  141. package/dist/types/conversation.d.ts +21 -0
  142. package/dist/types/copy-button.d.ts +6 -0
  143. package/dist/types/core.d.ts +31 -0
  144. package/dist/types/data-grid.d.ts +25 -0
  145. package/dist/types/date-picker.d.ts +6 -0
  146. package/dist/types/diff-view.d.ts +8 -0
  147. package/dist/types/dot-plot.d.ts +6 -0
  148. package/dist/types/edit-region.d.ts +23 -0
  149. package/dist/types/empty.d.ts +9 -0
  150. package/dist/types/feature-gate.d.ts +8 -0
  151. package/dist/types/filter-bar.d.ts +8 -0
  152. package/dist/types/formatted-number.d.ts +6 -0
  153. package/dist/types/heatmap.d.ts +6 -0
  154. package/dist/types/inline-citation.d.ts +6 -0
  155. package/dist/types/inline-edit.d.ts +7 -0
  156. package/dist/types/input-otp.d.ts +11 -0
  157. package/dist/types/item-group.d.ts +29 -0
  158. package/dist/types/line-chart.d.ts +6 -0
  159. package/dist/types/link.d.ts +8 -1
  160. package/dist/types/markdown.d.ts +6 -0
  161. package/dist/types/meter.d.ts +6 -0
  162. package/dist/types/nav.d.ts +6 -0
  163. package/dist/types/notifications.d.ts +6 -0
  164. package/dist/types/number-input.d.ts +6 -0
  165. package/dist/types/overlays.d.ts +61 -0
  166. package/dist/types/page-header.d.ts +14 -0
  167. package/dist/types/pagination.d.ts +6 -0
  168. package/dist/types/password-input.d.ts +6 -0
  169. package/dist/types/permission-matrix.d.ts +7 -0
  170. package/dist/types/pile.d.ts +6 -0
  171. package/dist/types/plan-picker.d.ts +6 -0
  172. package/dist/types/prompt-input.d.ts +11 -0
  173. package/dist/types/property-list.d.ts +17 -0
  174. package/dist/types/question.d.ts +11 -0
  175. package/dist/types/relative-time.d.ts +8 -1
  176. package/dist/types/removal-receipt.d.ts +18 -1
  177. package/dist/types/save-bar.d.ts +6 -0
  178. package/dist/types/saved-views.d.ts +12 -0
  179. package/dist/types/scatter-chart.d.ts +8 -0
  180. package/dist/types/schedule.d.ts +21 -0
  181. package/dist/types/secret-field.d.ts +6 -0
  182. package/dist/types/select.d.ts +12 -0
  183. package/dist/types/separator.d.ts +8 -1
  184. package/dist/types/sheet.d.ts +19 -0
  185. package/dist/types/sparkline.d.ts +6 -0
  186. package/dist/types/split-button.d.ts +7 -0
  187. package/dist/types/stat.d.ts +7 -0
  188. package/dist/types/stepper.d.ts +12 -0
  189. package/dist/types/suggestions.d.ts +10 -0
  190. package/dist/types/sync-indicator.d.ts +8 -0
  191. package/dist/types/tag-input.d.ts +8 -0
  192. package/dist/types/terminal.d.ts +6 -0
  193. package/dist/types/thread-list.d.ts +16 -0
  194. package/dist/types/time-input.d.ts +6 -0
  195. package/dist/types/timeline.d.ts +9 -0
  196. package/dist/types/toggle.d.ts +6 -0
  197. package/dist/types/toolbar.d.ts +20 -0
  198. package/dist/types/tour.d.ts +10 -0
  199. package/dist/types/trace.d.ts +16 -0
  200. package/dist/types/tree.d.ts +21 -0
  201. package/dist/types-cjs/agent-fleet.d.cts +28 -0
  202. package/dist/types-cjs/ai-label.d.cts +7 -0
  203. package/dist/types-cjs/app-shell.d.cts +23 -0
  204. package/dist/types-cjs/area-chart.d.cts +8 -0
  205. package/dist/types-cjs/artifact.d.cts +14 -0
  206. package/dist/types-cjs/bar-chart.d.cts +6 -0
  207. package/dist/types-cjs/bar-list.d.cts +6 -0
  208. package/dist/types-cjs/breadcrumb.d.cts +6 -0
  209. package/dist/types-cjs/chart-brush.d.cts +6 -0
  210. package/dist/types-cjs/chart.d.cts +29 -1
  211. package/dist/types-cjs/checklist.d.cts +6 -0
  212. package/dist/types-cjs/code-block.d.cts +6 -0
  213. package/dist/types-cjs/collections.d.cts +15 -0
  214. package/dist/types-cjs/color-picker.d.cts +6 -0
  215. package/dist/types-cjs/command.d.cts +13 -0
  216. package/dist/types-cjs/comment-thread.d.cts +11 -0
  217. package/dist/types-cjs/confirmation.d.cts +8 -0
  218. package/dist/types-cjs/context-menu.d.cts +11 -0
  219. package/dist/types-cjs/controls.d.cts +174 -10
  220. package/dist/types-cjs/conversation.d.cts +21 -0
  221. package/dist/types-cjs/copy-button.d.cts +6 -0
  222. package/dist/types-cjs/core.d.cts +31 -0
  223. package/dist/types-cjs/data-grid.d.cts +25 -0
  224. package/dist/types-cjs/date-picker.d.cts +6 -0
  225. package/dist/types-cjs/diff-view.d.cts +8 -0
  226. package/dist/types-cjs/dot-plot.d.cts +6 -0
  227. package/dist/types-cjs/edit-region.d.cts +23 -0
  228. package/dist/types-cjs/empty.d.cts +9 -0
  229. package/dist/types-cjs/feature-gate.d.cts +8 -0
  230. package/dist/types-cjs/filter-bar.d.cts +8 -0
  231. package/dist/types-cjs/formatted-number.d.cts +6 -0
  232. package/dist/types-cjs/heatmap.d.cts +6 -0
  233. package/dist/types-cjs/inline-citation.d.cts +6 -0
  234. package/dist/types-cjs/inline-edit.d.cts +7 -0
  235. package/dist/types-cjs/input-otp.d.cts +11 -0
  236. package/dist/types-cjs/item-group.d.cts +29 -0
  237. package/dist/types-cjs/line-chart.d.cts +6 -0
  238. package/dist/types-cjs/link.d.cts +8 -1
  239. package/dist/types-cjs/markdown.d.cts +6 -0
  240. package/dist/types-cjs/meter.d.cts +6 -0
  241. package/dist/types-cjs/nav.d.cts +6 -0
  242. package/dist/types-cjs/notifications.d.cts +6 -0
  243. package/dist/types-cjs/number-input.d.cts +6 -0
  244. package/dist/types-cjs/overlays.d.cts +61 -0
  245. package/dist/types-cjs/page-header.d.cts +14 -0
  246. package/dist/types-cjs/pagination.d.cts +6 -0
  247. package/dist/types-cjs/password-input.d.cts +6 -0
  248. package/dist/types-cjs/permission-matrix.d.cts +7 -0
  249. package/dist/types-cjs/pile.d.cts +6 -0
  250. package/dist/types-cjs/plan-picker.d.cts +6 -0
  251. package/dist/types-cjs/prompt-input.d.cts +11 -0
  252. package/dist/types-cjs/property-list.d.cts +17 -0
  253. package/dist/types-cjs/question.d.cts +11 -0
  254. package/dist/types-cjs/relative-time.d.cts +8 -1
  255. package/dist/types-cjs/removal-receipt.d.cts +18 -1
  256. package/dist/types-cjs/save-bar.d.cts +6 -0
  257. package/dist/types-cjs/saved-views.d.cts +12 -0
  258. package/dist/types-cjs/scatter-chart.d.cts +8 -0
  259. package/dist/types-cjs/schedule.d.cts +21 -0
  260. package/dist/types-cjs/secret-field.d.cts +6 -0
  261. package/dist/types-cjs/select.d.cts +12 -0
  262. package/dist/types-cjs/separator.d.cts +8 -1
  263. package/dist/types-cjs/sheet.d.cts +19 -0
  264. package/dist/types-cjs/sparkline.d.cts +6 -0
  265. package/dist/types-cjs/split-button.d.cts +7 -0
  266. package/dist/types-cjs/stat.d.cts +7 -0
  267. package/dist/types-cjs/stepper.d.cts +12 -0
  268. package/dist/types-cjs/suggestions.d.cts +10 -0
  269. package/dist/types-cjs/sync-indicator.d.cts +8 -0
  270. package/dist/types-cjs/tag-input.d.cts +8 -0
  271. package/dist/types-cjs/terminal.d.cts +6 -0
  272. package/dist/types-cjs/thread-list.d.cts +16 -0
  273. package/dist/types-cjs/time-input.d.cts +6 -0
  274. package/dist/types-cjs/timeline.d.cts +9 -0
  275. package/dist/types-cjs/toggle.d.cts +6 -0
  276. package/dist/types-cjs/toolbar.d.cts +20 -0
  277. package/dist/types-cjs/tour.d.cts +10 -0
  278. package/dist/types-cjs/trace.d.cts +16 -0
  279. package/dist/types-cjs/tree.d.cts +21 -0
  280. 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 -->