altitude-design 0.3.2 → 0.4.1

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 (294) hide show
  1. package/README.md +2 -0
  2. package/dist/cli/altitude.mjs +120 -20
  3. package/dist/guidance/README.md +4 -0
  4. package/dist/guidance/agent-setup.md +3 -2
  5. package/dist/guidance/components/AILabel/README.md +60 -0
  6. package/dist/guidance/components/Accordion/README.md +32 -0
  7. package/dist/guidance/components/AgentFleet/README.md +81 -0
  8. package/dist/guidance/components/AgentLanes/README.md +53 -0
  9. package/dist/guidance/components/AgentRequests/README.md +76 -0
  10. package/dist/guidance/components/Alert/README.md +71 -0
  11. package/dist/guidance/components/AltitudeProvider/README.md +111 -0
  12. package/dist/guidance/components/AppShell/README.md +121 -0
  13. package/dist/guidance/components/AreaChart/README.md +101 -0
  14. package/dist/guidance/components/Artifact/README.md +99 -0
  15. package/dist/guidance/components/Avatar/README.md +75 -0
  16. package/dist/guidance/components/Await/README.md +128 -0
  17. package/dist/guidance/components/Badge/README.md +66 -0
  18. package/dist/guidance/components/BarChart/README.md +109 -0
  19. package/dist/guidance/components/BarList/README.md +87 -0
  20. package/dist/guidance/components/Board/README.md +98 -0
  21. package/dist/guidance/components/Breadcrumb/README.md +66 -0
  22. package/dist/guidance/components/Button/README.md +58 -0
  23. package/dist/guidance/components/Card/README.md +52 -0
  24. package/dist/guidance/components/ChartBrush/README.md +98 -0
  25. package/dist/guidance/components/ChartContainer/README.md +229 -0
  26. package/dist/guidance/components/ChartLegend/README.md +78 -0
  27. package/dist/guidance/components/ChartReference/README.md +115 -0
  28. package/dist/guidance/components/ChartTooltip/README.md +101 -0
  29. package/dist/guidance/components/Checkbox/README.md +46 -0
  30. package/dist/guidance/components/Checklist/README.md +137 -0
  31. package/dist/guidance/components/CodeBlock/README.md +72 -0
  32. package/dist/guidance/components/ColorPicker/README.md +69 -0
  33. package/dist/guidance/components/Combobox/README.md +51 -0
  34. package/dist/guidance/components/CommandPalette/README.md +204 -0
  35. package/dist/guidance/components/CommentThread/README.md +81 -0
  36. package/dist/guidance/components/Confirmation/README.md +94 -0
  37. package/dist/guidance/components/ContextMenu/README.md +81 -0
  38. package/dist/guidance/components/Conversation/README.md +169 -0
  39. package/dist/guidance/components/CopyButton/README.md +54 -0
  40. package/dist/guidance/components/DataGrid/README.md +157 -0
  41. package/dist/guidance/components/DatePicker/README.md +116 -0
  42. package/dist/guidance/components/Dialog/README.md +204 -0
  43. package/dist/guidance/components/DiffView/README.md +107 -0
  44. package/dist/guidance/components/DotPlot/README.md +82 -0
  45. package/dist/guidance/components/Drilldown/README.md +43 -0
  46. package/dist/guidance/components/DropZone/README.md +71 -0
  47. package/dist/guidance/components/DropdownMenu/README.md +63 -0
  48. package/dist/guidance/components/EditRegion/README.md +263 -0
  49. package/dist/guidance/components/EmptyState/README.md +66 -0
  50. package/dist/guidance/components/FeatureGate/README.md +67 -0
  51. package/dist/guidance/components/Field/README.md +46 -0
  52. package/dist/guidance/components/FilterBar/README.md +132 -0
  53. package/dist/guidance/components/FormattedNumber/README.md +81 -0
  54. package/dist/guidance/components/Heatmap/README.md +116 -0
  55. package/dist/guidance/components/HoldButton/README.md +48 -0
  56. package/dist/guidance/components/HoverCard/README.md +71 -0
  57. package/dist/guidance/components/InlineCitation/README.md +56 -0
  58. package/dist/guidance/components/InlineEdit/README.md +379 -0
  59. package/dist/guidance/components/Input/README.md +63 -0
  60. package/dist/guidance/components/InputOTP/README.md +76 -0
  61. package/dist/guidance/components/ItemGroup/README.md +193 -0
  62. package/dist/guidance/components/Kbd/README.md +36 -0
  63. package/dist/guidance/components/LineChart/README.md +109 -0
  64. package/dist/guidance/components/Link/README.md +69 -0
  65. package/dist/guidance/components/Markdown/README.md +113 -0
  66. package/dist/guidance/components/Meter/README.md +81 -0
  67. package/dist/guidance/components/NotificationCenter/README.md +67 -0
  68. package/dist/guidance/components/NumberInput/README.md +66 -0
  69. package/dist/guidance/components/PageHeader/README.md +74 -0
  70. package/dist/guidance/components/Pagination/README.md +58 -0
  71. package/dist/guidance/components/PasswordInput/README.md +70 -0
  72. package/dist/guidance/components/PermissionMatrix/README.md +68 -0
  73. package/dist/guidance/components/Pile/README.md +54 -0
  74. package/dist/guidance/components/PlanPicker/README.md +62 -0
  75. package/dist/guidance/components/Popover/README.md +48 -0
  76. package/dist/guidance/components/Progress/README.md +41 -0
  77. package/dist/guidance/components/PromptInput/README.md +138 -0
  78. package/dist/guidance/components/PropertyList/README.md +137 -0
  79. package/dist/guidance/components/Question/README.md +81 -0
  80. package/dist/guidance/components/RadioGroup/README.md +47 -0
  81. package/dist/guidance/components/RelativeTime/README.md +73 -0
  82. package/dist/guidance/components/RemovalReceipt/README.md +111 -0
  83. package/dist/guidance/components/Resizable/README.md +65 -0
  84. package/dist/guidance/components/SaveBar/README.md +65 -0
  85. package/dist/guidance/components/SavedViews/README.md +82 -0
  86. package/dist/guidance/components/ScatterChart/README.md +104 -0
  87. package/dist/guidance/components/Schedule/README.md +142 -0
  88. package/dist/guidance/components/ScrollArea/README.md +35 -0
  89. package/dist/guidance/components/SecretField/README.md +74 -0
  90. package/dist/guidance/components/Select/README.md +97 -0
  91. package/dist/guidance/components/Separator/README.md +70 -0
  92. package/dist/guidance/components/Sheet/README.md +126 -0
  93. package/dist/guidance/components/Skeleton/README.md +50 -0
  94. package/dist/guidance/components/Slider/README.md +60 -0
  95. package/dist/guidance/components/SortableList/README.md +45 -0
  96. package/dist/guidance/components/Sparkline/README.md +87 -0
  97. package/dist/guidance/components/SplitButton/README.md +65 -0
  98. package/dist/guidance/components/Stat/README.md +92 -0
  99. package/dist/guidance/components/Stepper/README.md +90 -0
  100. package/dist/guidance/components/Suggestions/README.md +87 -0
  101. package/dist/guidance/components/Surface/README.md +48 -0
  102. package/dist/guidance/components/Switch/README.md +38 -0
  103. package/dist/guidance/components/SyncIndicator/README.md +78 -0
  104. package/dist/guidance/components/Table/README.md +138 -0
  105. package/dist/guidance/components/Tabs/README.md +73 -0
  106. package/dist/guidance/components/TagInput/README.md +116 -0
  107. package/dist/guidance/components/Terminal/README.md +69 -0
  108. package/dist/guidance/components/ThreadList/README.md +110 -0
  109. package/dist/guidance/components/TimeInput/README.md +63 -0
  110. package/dist/guidance/components/Timeline/README.md +87 -0
  111. package/dist/guidance/components/Toaster/README.md +54 -0
  112. package/dist/guidance/components/Toggle/README.md +85 -0
  113. package/dist/guidance/components/Toolbar/README.md +106 -0
  114. package/dist/guidance/components/Tooltip/README.md +41 -0
  115. package/dist/guidance/components/Tour/README.md +73 -0
  116. package/dist/guidance/components/Trace/README.md +129 -0
  117. package/dist/guidance/components/Tree/README.md +171 -0
  118. package/dist/guidance/evidence.json +18 -0
  119. package/dist/guidance/index.json +811 -7
  120. package/dist/guidance/llms.txt +153 -2
  121. package/dist/guidance/manifest.json +133 -12
  122. package/dist/guidance/patterns/screen-layout.md +1 -1
  123. package/dist/guidance/references/build/checks/agent-setup.mjs +7 -0
  124. package/dist/guidance/references/build/checks/guidance.mjs +8 -2
  125. package/dist/guidance/references/build/checks/source-check.mjs +101 -0
  126. package/dist/guidance/rules.json +155 -0
  127. package/dist/guidance/visual/ActionsInPlace.md +463 -0
  128. package/dist/guidance/visual/Charts.md +119 -0
  129. package/dist/guidance/visual/Distinctiveness.md +106 -0
  130. package/dist/guidance/visual/Elevation.md +1 -1
  131. package/dist/guidance/visual/Mobile.md +64 -0
  132. package/dist/guidance/visual/Motion.md +225 -0
  133. package/dist/guidance/visual/README.md +98 -0
  134. package/dist/guidance/visual/Research.md +1 -1
  135. package/dist/guidance/visual/Theming.md +349 -0
  136. package/dist/types/agent-fleet.d.ts +28 -0
  137. package/dist/types/ai-label.d.ts +7 -0
  138. package/dist/types/app-shell.d.ts +23 -0
  139. package/dist/types/area-chart.d.ts +8 -0
  140. package/dist/types/artifact.d.ts +14 -0
  141. package/dist/types/bar-chart.d.ts +6 -0
  142. package/dist/types/bar-list.d.ts +6 -0
  143. package/dist/types/breadcrumb.d.ts +6 -0
  144. package/dist/types/chart-brush.d.ts +6 -0
  145. package/dist/types/chart.d.ts +29 -1
  146. package/dist/types/checklist.d.ts +6 -0
  147. package/dist/types/code-block.d.ts +6 -0
  148. package/dist/types/collections.d.ts +15 -0
  149. package/dist/types/color-picker.d.ts +6 -0
  150. package/dist/types/command.d.ts +13 -0
  151. package/dist/types/comment-thread.d.ts +11 -0
  152. package/dist/types/confirmation.d.ts +8 -0
  153. package/dist/types/context-menu.d.ts +11 -0
  154. package/dist/types/controls.d.ts +174 -10
  155. package/dist/types/conversation.d.ts +21 -0
  156. package/dist/types/copy-button.d.ts +6 -0
  157. package/dist/types/core.d.ts +31 -0
  158. package/dist/types/data-grid.d.ts +25 -0
  159. package/dist/types/date-picker.d.ts +6 -0
  160. package/dist/types/diff-view.d.ts +8 -0
  161. package/dist/types/dot-plot.d.ts +6 -0
  162. package/dist/types/edit-region.d.ts +23 -0
  163. package/dist/types/empty.d.ts +9 -0
  164. package/dist/types/feature-gate.d.ts +8 -0
  165. package/dist/types/filter-bar.d.ts +8 -0
  166. package/dist/types/formatted-number.d.ts +6 -0
  167. package/dist/types/heatmap.d.ts +6 -0
  168. package/dist/types/inline-citation.d.ts +6 -0
  169. package/dist/types/inline-edit.d.ts +7 -0
  170. package/dist/types/input-otp.d.ts +11 -0
  171. package/dist/types/item-group.d.ts +29 -0
  172. package/dist/types/line-chart.d.ts +6 -0
  173. package/dist/types/link.d.ts +8 -1
  174. package/dist/types/markdown.d.ts +6 -0
  175. package/dist/types/meter.d.ts +6 -0
  176. package/dist/types/nav.d.ts +6 -0
  177. package/dist/types/notifications.d.ts +6 -0
  178. package/dist/types/number-input.d.ts +6 -0
  179. package/dist/types/overlays.d.ts +61 -0
  180. package/dist/types/page-header.d.ts +14 -0
  181. package/dist/types/pagination.d.ts +6 -0
  182. package/dist/types/password-input.d.ts +6 -0
  183. package/dist/types/permission-matrix.d.ts +7 -0
  184. package/dist/types/pile.d.ts +6 -0
  185. package/dist/types/plan-picker.d.ts +6 -0
  186. package/dist/types/prompt-input.d.ts +11 -0
  187. package/dist/types/property-list.d.ts +17 -0
  188. package/dist/types/question.d.ts +11 -0
  189. package/dist/types/relative-time.d.ts +8 -1
  190. package/dist/types/removal-receipt.d.ts +18 -1
  191. package/dist/types/save-bar.d.ts +6 -0
  192. package/dist/types/saved-views.d.ts +12 -0
  193. package/dist/types/scatter-chart.d.ts +8 -0
  194. package/dist/types/schedule.d.ts +21 -0
  195. package/dist/types/secret-field.d.ts +6 -0
  196. package/dist/types/select.d.ts +12 -0
  197. package/dist/types/separator.d.ts +8 -1
  198. package/dist/types/sheet.d.ts +19 -0
  199. package/dist/types/sparkline.d.ts +6 -0
  200. package/dist/types/split-button.d.ts +7 -0
  201. package/dist/types/stat.d.ts +7 -0
  202. package/dist/types/stepper.d.ts +12 -0
  203. package/dist/types/suggestions.d.ts +10 -0
  204. package/dist/types/sync-indicator.d.ts +8 -0
  205. package/dist/types/tag-input.d.ts +8 -0
  206. package/dist/types/terminal.d.ts +6 -0
  207. package/dist/types/thread-list.d.ts +16 -0
  208. package/dist/types/time-input.d.ts +6 -0
  209. package/dist/types/timeline.d.ts +9 -0
  210. package/dist/types/toggle.d.ts +6 -0
  211. package/dist/types/toolbar.d.ts +20 -0
  212. package/dist/types/tour.d.ts +10 -0
  213. package/dist/types/trace.d.ts +16 -0
  214. package/dist/types/tree.d.ts +21 -0
  215. package/dist/types-cjs/agent-fleet.d.cts +28 -0
  216. package/dist/types-cjs/ai-label.d.cts +7 -0
  217. package/dist/types-cjs/app-shell.d.cts +23 -0
  218. package/dist/types-cjs/area-chart.d.cts +8 -0
  219. package/dist/types-cjs/artifact.d.cts +14 -0
  220. package/dist/types-cjs/bar-chart.d.cts +6 -0
  221. package/dist/types-cjs/bar-list.d.cts +6 -0
  222. package/dist/types-cjs/breadcrumb.d.cts +6 -0
  223. package/dist/types-cjs/chart-brush.d.cts +6 -0
  224. package/dist/types-cjs/chart.d.cts +29 -1
  225. package/dist/types-cjs/checklist.d.cts +6 -0
  226. package/dist/types-cjs/code-block.d.cts +6 -0
  227. package/dist/types-cjs/collections.d.cts +15 -0
  228. package/dist/types-cjs/color-picker.d.cts +6 -0
  229. package/dist/types-cjs/command.d.cts +13 -0
  230. package/dist/types-cjs/comment-thread.d.cts +11 -0
  231. package/dist/types-cjs/confirmation.d.cts +8 -0
  232. package/dist/types-cjs/context-menu.d.cts +11 -0
  233. package/dist/types-cjs/controls.d.cts +174 -10
  234. package/dist/types-cjs/conversation.d.cts +21 -0
  235. package/dist/types-cjs/copy-button.d.cts +6 -0
  236. package/dist/types-cjs/core.d.cts +31 -0
  237. package/dist/types-cjs/data-grid.d.cts +25 -0
  238. package/dist/types-cjs/date-picker.d.cts +6 -0
  239. package/dist/types-cjs/diff-view.d.cts +8 -0
  240. package/dist/types-cjs/dot-plot.d.cts +6 -0
  241. package/dist/types-cjs/edit-region.d.cts +23 -0
  242. package/dist/types-cjs/empty.d.cts +9 -0
  243. package/dist/types-cjs/feature-gate.d.cts +8 -0
  244. package/dist/types-cjs/filter-bar.d.cts +8 -0
  245. package/dist/types-cjs/formatted-number.d.cts +6 -0
  246. package/dist/types-cjs/heatmap.d.cts +6 -0
  247. package/dist/types-cjs/inline-citation.d.cts +6 -0
  248. package/dist/types-cjs/inline-edit.d.cts +7 -0
  249. package/dist/types-cjs/input-otp.d.cts +11 -0
  250. package/dist/types-cjs/item-group.d.cts +29 -0
  251. package/dist/types-cjs/line-chart.d.cts +6 -0
  252. package/dist/types-cjs/link.d.cts +8 -1
  253. package/dist/types-cjs/markdown.d.cts +6 -0
  254. package/dist/types-cjs/meter.d.cts +6 -0
  255. package/dist/types-cjs/nav.d.cts +6 -0
  256. package/dist/types-cjs/notifications.d.cts +6 -0
  257. package/dist/types-cjs/number-input.d.cts +6 -0
  258. package/dist/types-cjs/overlays.d.cts +61 -0
  259. package/dist/types-cjs/page-header.d.cts +14 -0
  260. package/dist/types-cjs/pagination.d.cts +6 -0
  261. package/dist/types-cjs/password-input.d.cts +6 -0
  262. package/dist/types-cjs/permission-matrix.d.cts +7 -0
  263. package/dist/types-cjs/pile.d.cts +6 -0
  264. package/dist/types-cjs/plan-picker.d.cts +6 -0
  265. package/dist/types-cjs/prompt-input.d.cts +11 -0
  266. package/dist/types-cjs/property-list.d.cts +17 -0
  267. package/dist/types-cjs/question.d.cts +11 -0
  268. package/dist/types-cjs/relative-time.d.cts +8 -1
  269. package/dist/types-cjs/removal-receipt.d.cts +18 -1
  270. package/dist/types-cjs/save-bar.d.cts +6 -0
  271. package/dist/types-cjs/saved-views.d.cts +12 -0
  272. package/dist/types-cjs/scatter-chart.d.cts +8 -0
  273. package/dist/types-cjs/schedule.d.cts +21 -0
  274. package/dist/types-cjs/secret-field.d.cts +6 -0
  275. package/dist/types-cjs/select.d.cts +12 -0
  276. package/dist/types-cjs/separator.d.cts +8 -1
  277. package/dist/types-cjs/sheet.d.cts +19 -0
  278. package/dist/types-cjs/sparkline.d.cts +6 -0
  279. package/dist/types-cjs/split-button.d.cts +7 -0
  280. package/dist/types-cjs/stat.d.cts +7 -0
  281. package/dist/types-cjs/stepper.d.cts +12 -0
  282. package/dist/types-cjs/suggestions.d.cts +10 -0
  283. package/dist/types-cjs/sync-indicator.d.cts +8 -0
  284. package/dist/types-cjs/tag-input.d.cts +8 -0
  285. package/dist/types-cjs/terminal.d.cts +6 -0
  286. package/dist/types-cjs/thread-list.d.cts +16 -0
  287. package/dist/types-cjs/time-input.d.cts +6 -0
  288. package/dist/types-cjs/timeline.d.cts +9 -0
  289. package/dist/types-cjs/toggle.d.cts +6 -0
  290. package/dist/types-cjs/toolbar.d.cts +20 -0
  291. package/dist/types-cjs/tour.d.cts +10 -0
  292. package/dist/types-cjs/trace.d.cts +16 -0
  293. package/dist/types-cjs/tree.d.cts +21 -0
  294. package/package.json +1 -1
@@ -0,0 +1,71 @@
1
+ # Alert
2
+
3
+ Tells the person something about the current context. An alert is **a sheet laid on the page**: it rests on the ground, you can see its paper edge and the contact shadow under it, and it never floats. When it asks the person to act it becomes a raised card.
4
+
5
+ **Page banners.** `placement="page"` lays the alert edge to edge across the top of the app or page: "Your trial ends in 3 days", "Scheduled maintenance Sunday 02:00 UTC", "We're seeing delays in email delivery". It has no side rounding and no side edges, and its text wraps on narrow screens. The elevation rule is the same at page scope: one that informs is a flat sheet, and one that asks for action is raised. `sticky` keeps it at the top while the page scrolls. It sits in a `region` named by its title, so it can be found and skipped. `dismissLabel` gives the dismiss button words ("Remind me tomorrow"), and dismissing moves focus to the page's `main`. Several page banners go in `AlertStack placement="page"`, most severe first. Put a banner above `AppShell`, or at the top of the page.
6
+
7
+ - `variant="flat"` (default): a thin sheet in the tone's `-soft` colour (neutral is `surface-raised`), with a tone-tinted hairline, a 1px paper edge and a contact shadow. It informs.
8
+ - `variant="raised"`: a card at level 2 with the sheen of something you can act on, the icon in the tone colour. Use it when the alert has an `action` or blocks progress.
9
+ - `onDismiss`: adds a dismiss button. Dismissing **lifts the sheet off the page**: it rises 8px as its shadow spreads, fades, and then the gap closes; `onDismiss` is called once it's gone.
10
+ - `settle`: an afterglow when the alert first appears or changes.
11
+
12
+ `critical` renders `role="alert"`; the others use `role="status"`.
13
+
14
+ ## AlertStack
15
+
16
+ Several alerts as a **stack of sheets**, the same language as `Pile`. The first alert lies on top; up to two more peek out beneath it, each tinted by its own tone, so you can see what kind of news is waiting without reading it. A floating "+N more" sits on the stack's edge. Opening it fans the alerts out into a column, one after another; "Show less" tucks them back. Dismiss the top alert and it lifts off while the next one comes up from under it.
17
+
18
+ **You provide:** for `Alert`, `tone`, `title`, optional body as children, optional `action` (a small Button), `variant`, `onDismiss`. For `AlertStack`, keyed `Alert` children in order of importance, and optionally `expanded`/`onExpandedChange`, `moreLabel`, `lessLabel`.
19
+
20
+ - Keep it to a title and one sentence.
21
+ - Generic action-completion messages belong in a Toast. When an action removes an item from a list (‘Brand guidelines.pdf archived’ with Undo), don't add an Alert below the list: show the result in the item's own slot with [RemovalReceipt](../RemovalReceipt/README.md), then hand Undo to a Toast. Keep a durable recovery destination such as Archived → Restore; a timed notification must not be the only recovery path. See [Actions in place](../../visual/ActionsInPlace.md).
22
+ - An Alert still suits a failure or a changed record that can't be shown beside its item (it's outside the current view). Key it by its operation ID, and let its delayed `onDismiss` clear state only when that ID is still current. Dismissing an old Alert must not hide a newer action's feedback.
23
+
24
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
25
+ ## Example
26
+
27
+ `Alert` as first used in its preview:
28
+
29
+ ```tsx
30
+ import { Alert, Button } from 'altitude-design';
31
+
32
+ <Alert placement="page" tone="caution" variant="raised" title="Your trial ends in 3 days" action={<Button variant="primary" size="sm">Choose a plan</Button>}
33
+ onDismiss={() => setTrial(false)} dismissLabel="Remind me tomorrow" />
34
+ ```
35
+
36
+ The full preview, as rendered in Storybook and checked in CI (`col`, `frame`, `cap`, `wrap` are the preview page's own layout classes, not Altitude's):
37
+
38
+ ```tsx
39
+ import * as React from 'react';
40
+ import { Alert, AlertStack, Button } from 'altitude-design';
41
+
42
+ const queue = [
43
+ { id: 'b', tone: 'critical', title: 'Build failed', body: 'Missing environment variable DATABASE_URL.' },
44
+ { id: 'c', tone: 'caution', title: 'Certificate renews in 3 days', body: 'Renewal is automatic; nothing to do.' },
45
+ { id: 'd', tone: 'accent', title: 'New region available', body: 'Frankfurt (eu-central-2) is now open.' },
46
+ ];
47
+ export default function App() {
48
+ const [items, setItems] = React.useState(queue);
49
+ React.useEffect(() => { if (!items.length) { const t = setTimeout(() => setItems(queue), 900); return () => clearTimeout(t); } }, [items]);
50
+ const [trial, setTrial] = React.useState(true);
51
+ return (
52
+ <div className="col">
53
+ <div className="frame">
54
+ {trial && <Alert placement="page" tone="caution" variant="raised" title="Your trial ends in 3 days" action={<Button variant="primary" size="sm">Choose a plan</Button>}
55
+ onDismiss={() => setTrial(false)} dismissLabel="Remind me tomorrow" />}
56
+ <Alert placement="page" title="We're seeing delays in email delivery">Messages may take up to 15 minutes to arrive.</Alert>
57
+ <main tabIndex={-1}><p className="cap">Page banners lie edge to edge across the top. One that asks for action is raised; one that informs is a flat sheet.</p></main>
58
+ </div>
59
+ <div className="col wrap">
60
+ <Alert title="Scheduled maintenance">The API will be read-only on Sunday from 02:00 to 02:30 UTC.</Alert>
61
+ <Alert tone="positive" title="Domain verified">acme.dev now points at this project.</Alert>
62
+ <Alert tone="caution" variant="raised" title="You're at 90% of your plan" action={<Button size="sm" variant="primary">Upgrade</Button>} />
63
+ <AlertStack>
64
+ {items.map((a) => <Alert key={a.id} tone={a.tone} title={a.title} onDismiss={() => setItems((xs) => xs.filter((x) => x.id !== a.id))}>{a.body}</Alert>)}
65
+ </AlertStack>
66
+ </div>
67
+ </div>
68
+ );
69
+ }
70
+ ```
71
+ <!-- /example -->
@@ -0,0 +1,111 @@
1
+ # AltitudeProvider
2
+
3
+ Sets the color scheme, accent and motion preference for everything inside it, and hosts the layers that rise above the page.
4
+
5
+ Wrap the app once, near the root. It renders a themed root, a **stage** (your app, which recedes behind dialogs) and a **layer host** (where dialogs and toasts render, so they stay inside the theme but outside the receding stage).
6
+
7
+ It also sets the high-contrast rendering: with `contrast="more"` (or the system's `prefers-contrast: more`), shadows become hard ink slabs whose thickness is the height.
8
+
9
+ It runs the **approach** tracker too, which lets openings react to a pointer before it arrives: an empty field deepens to receive, and a bottom sheet's finger pull opens. Raised things (buttons, cards, piles, steps) never react before the pointer is on them and never follow it. It only follows a mouse or pen, never touch, and reduced motion turns every approach effect off. `approach={false}` turns it off for everything inside.
10
+
11
+ **Your own scheme.** `scheme` also takes a custom scheme: `{ name, base, tokens }`, your colour and shadow tokens laid over a built-in scheme (`base`, which supplies anything you leave out and the light or dark handling). The provider scopes them to itself in a stylesheet, so they nest like the built-in schemes and still give way to high contrast. If the scheme breaks the invariants in [Theming](../../visual/Theming.md) (the surface ladder, contrast on every surface, the data palette's gates), it says which in a console warning; `checkScheme(el, { include: ['core', 'data'] })` runs the same test in your own tests. A plain string names your own `[data-theme="…"]` block instead.
12
+
13
+ **You provide:** `scheme` (a built-in name, your own, a custom scheme, or nothing, to inherit `data-theme` from the page), optionally `accent` as a hex color, `motion`, `approach`, `loading="breath"` to try the experimental breathing imprints (see `Await`), and `recede={false}` if your layout can't tolerate a transform on the app root (for example a `position: fixed` header that must not scale).
14
+
15
+ - Do nest a provider to theme a region differently: a `nightfall` sidebar in a `daylight` app.
16
+ - Do pass your brand color as `accent`; legibility is derived for you, and the chart palette's first series and magnitude ramp follow it (the other series re-order so neighbours stay apart for colour-blind readers).
17
+ - Do reach for a custom scheme only when the brand needs its own grounds and ink, and keep `checkScheme` passing in your tests.
18
+ - Don't render `Dialog` or `Toaster` outside a provider unless you accept them portaling to `document.body` without the receding page.
19
+
20
+ Modal ownership is coordinated privately per document. A nested themed Dialog keeps the nearest provider's portal host and blocks background branches throughout the document. Ancestors containing that portal are never transformed or made inert as a whole. Each provider's own `recede` setting remains independent. A stationary Dialog suppresses recession and blur on participating lower modal layers until it finishes closing.
21
+
22
+ Isolation preserves application-owned inertness and restores the original inline scroll style after the last modal exits. Reopening during exit retains the same ownership; route/provider unmount releases it immediately. Applications do not need a competing inert wrapper. The legacy `useAltitude().pushLayer()` helper remains compatible as a visual recession vote with an idempotent release. It does not acquire modal isolation or focus ownership; use Dialog and modal Sheet for those behaviors. `useAltitude()` supplies the nearest portal `host`, and the ownership registry remains private.
23
+
24
+ **Density.** `density` sets how large controls are. `'auto'` (default) uses the mouse sizes and steps up under a coarse pointer, so controls are big enough to hit with a finger; `'comfortable'` uses the larger sizes everywhere; `'compact'` keeps the mouse sizes on touch. Hit areas reach 44px under a coarse pointer either way. See [Theming](../../visual/Theming.md), "Density and target size".
25
+
26
+ ## App modes
27
+
28
+ `mode` marks the whole app as being in a state people must never mistake for the normal app: Stripe-style **test mode**, a support agent **viewing as a customer**, a **read-only** workspace, a **preview** of unpublished content.
29
+
30
+ ```tsx
31
+ <AltitudeProvider mode={{ tone: 'caution', label: 'Test mode', description: 'Payments are simulated.',
32
+ action: <Button size="sm" onClick={exit}>Leave test mode</Button> }}>
33
+ ```
34
+
35
+ **Elevation: a word and a border.** The page is the ground, so height can't say "this whole ground is different". The mode is carried by the non-height cues instead (law 7 as the primary channel):
36
+ - **A flush strip** at the top, on the tone's soft colour, naming the mode, with your way out. It can't be dismissed.
37
+ - **A 2px frame around the viewport** in the mode's tone. It stays while scrolling, and above dialogs and sheets.
38
+ - **A plate in every `Dialog` and `Sheet` header** naming the mode, so a refund confirmed in test mode says so.
39
+
40
+ Nothing moves: a mode is there from load. Entering it is announced once, politely ("Test mode on"). The strip is a region named by the mode, first in the tab order. Under forced colours the frame is a 4px double `CanvasText` border. Under `contrast="more"` it's 3px. `ModeContext` gives your own floating parts the mode.
41
+
42
+ - Do use a mode for anything that changes what actions do (simulated payments, acting as someone else).
43
+ - Don't use a page banner for a mode; a banner can be dismissed and scrolls away.
44
+
45
+ ## One primary per view
46
+
47
+ Law 1 says the primary action rests at level 2, one per view. With `devChecks` (on by default when `process.env.NODE_ENV` isn't `'production'`), a `Button variant="primary"` registers with the provider while it's mounted. When more than one *visible* primary sits in the same view, one grouped warning names them and law 1. The page is one view, and each `Dialog`, `Sheet` and `Popover` is its own. Wrap independent parts (cards on a dashboard, each with its own task) in `<PrimaryScope>` to make them views of their own. The check is a warning only, it's gone from production builds, and the rule is `ALT-VISUAL-002` (`altitude explain ALT-VISUAL-002`). `PageHeader`'s `primary` slot is where a page's one primary belongs.
48
+
49
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
50
+ ## Example
51
+
52
+ `AltitudeProvider` as first used in its preview:
53
+
54
+ ```tsx
55
+ import { AltitudeProvider, Badge, Button, Input, Switch } from 'altitude-design';
56
+
57
+ <AltitudeProvider key={i} scheme={s} accent={accent} className="shell"><div className="scheme">
58
+ <h4>{typeof s === 'string' ? s[0].toUpperCase() + s.slice(1) : 'Custom'}</h4>
59
+ <small>{accent ? 'accent="' + accent + '"' : typeof s === 'string' ? 'scheme="' + s + '"' : 'scheme={paper}'}</small>
60
+ <Button variant="primary" size="sm">Publish</Button>
61
+ <Input size="sm" placeholder="Search" aria-label="Search" />
62
+ <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}><Switch defaultChecked aria-label="Enabled" /><Badge tone="accent">Beta</Badge></div>
63
+ <div className="series">{[1, 2, 3, 4, 5, 6].map((n) => <span key={n} style={{ background: `var(--data-${n})` }} />)}<small>{accent ? 'data-1…6, led by the accent' : 'data-1…6'}</small></div>
64
+ </div></AltitudeProvider>
65
+ ```
66
+
67
+ The full preview, as rendered in Storybook and checked in CI (`schemes`, `shell`, `scheme`, `series`, `density` are the preview page's own layout classes, not Altitude's):
68
+
69
+ ```tsx
70
+ import { AltitudeProvider, Badge, Button, Input, Switch, Tabs, TabsList, TabsTrigger } from 'altitude-design';
71
+
72
+ // A custom scheme: warm paper grounds and green ink laid over daylight. checkScheme() passes it.
73
+ const paper = { name: 'paper', base: 'daylight', tokens: {
74
+ 'surface-trench': '#e5e1d2', 'surface-well': '#edeadf', 'surface-ground': '#f5f3ea', 'surface-raised': '#fffdf7',
75
+ 'surface-lifted': '#fffdf8', 'surface-floating': '#fffefa', 'surface-overlay': '#fffefb', 'surface-modal': '#fffefc',
76
+ ink: '#1f3329', 'ink-muted': '#4b5d53', 'ink-faint': '#949b8e', line: '#e2ded0', 'line-control': '#76806f',
77
+ accent: '#284d39', 'on-accent': '#ffffff', 'accent-ink': '#284d39', 'accent-soft': '#e1e9dc', positive: '#2c6e3f',
78
+ } };
79
+ const list = [['daylight'], ['nightfall'], ['daylight', '#0f8a6b'], [paper]];
80
+ export default function App() {
81
+ return (
82
+ <>
83
+ <div className="schemes">
84
+ {list.map(([s, accent], i) => (
85
+ <AltitudeProvider key={i} scheme={s} accent={accent} className="shell"><div className="scheme">
86
+ <h4>{typeof s === 'string' ? s[0].toUpperCase() + s.slice(1) : 'Custom'}</h4>
87
+ <small>{accent ? 'accent="' + accent + '"' : typeof s === 'string' ? 'scheme="' + s + '"' : 'scheme={paper}'}</small>
88
+ <Button variant="primary" size="sm">Publish</Button>
89
+ <Input size="sm" placeholder="Search" aria-label="Search" />
90
+ <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}><Switch defaultChecked aria-label="Enabled" /><Badge tone="accent">Beta</Badge></div>
91
+ <div className="series">{[1, 2, 3, 4, 5, 6].map((n) => <span key={n} style={{ background: `var(--data-${n})` }} />)}<small>{accent ? 'data-1…6, led by the accent' : 'data-1…6'}</small></div>
92
+ </div></AltitudeProvider>
93
+ ))}
94
+ </div>
95
+ {/* Density (#245): 'auto' follows the pointer; 'compact' pins the mouse sizes, 'comfortable' the touch sizes. */}
96
+ <div className="density">
97
+ {['compact', 'comfortable'].map((d) => (
98
+ <AltitudeProvider key={d} density={d} className="shell"><div className="scheme">
99
+ <h4>{d === 'compact' ? 'Compact' : 'Comfortable'}</h4>
100
+ <small>density="{d}"</small>
101
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}><Button variant="primary">Save</Button><Button>Cancel</Button></div>
102
+ <Input placeholder="Search" aria-label="Search" />
103
+ <Tabs defaultValue="a"><TabsList aria-label={'Sections, ' + d}><TabsTrigger value="a">General</TabsTrigger><TabsTrigger value="b">Members</TabsTrigger></TabsList></Tabs>
104
+ </div></AltitudeProvider>
105
+ ))}
106
+ </div>
107
+ </>
108
+ );
109
+ }
110
+ ```
111
+ <!-- /example -->
@@ -0,0 +1,121 @@
1
+ # AppShell and Sidebar
2
+
3
+ The frame of an app: a sidebar of destinations (grouped, collapsible), a top bar with search and the user's menu, and the page.
4
+
5
+ **Elevation: the frame lies on the ground.** Where you are is part of the record, so the sidebar, top bar and page are all `surface-ground`, divided by `line`. Don't float the sidebar as a raised card. Only the things in it that act are live:
6
+ - **A destination** lifts one level when pointed at, because it navigates.
7
+ - **The current one** sits on a raised pill (segmented `Tabs`' build) that glides from destination to destination as you move.
8
+ - **Only a count floats** (`Badge variant="count"`). A status ("Beta") would be a flush plate.
9
+ - **Collapsing to a rail** changes width on `ease-glide`, not level. In the rail, each destination is an icon with its name in a tooltip to the right.
10
+ - **The top bar** rises one level while content passes under it, as `ScrollArea`'s header does.
11
+ - **On narrow screens** (below `mobileBreakpoint`, default 640px, the compact breakpoint) the sidebar moves into a `Sheet` drawer from the left (level 4), titled "Navigation". Choosing a destination closes it.
12
+
13
+ **Parts** (shadcn/ui's names):
14
+ - `AppShell`: `sidebar`, `topBar` and the page. It includes a `SidebarProvider`, a skip link, and a `SidebarTrigger` at the start of the top bar.
15
+ - `SidebarProvider`: `collapsed`, `defaultCollapsed`, `onCollapsedChange`, `mobileBreakpoint`. ⌘B or Ctrl+B toggles. `useSidebar()` reads and changes the state.
16
+ - `Sidebar`: a `nav` (`aria-label`, default "Main") with `collapsible` (`'icon' | 'none'`). Inside it go `SidebarHeader`, `SidebarContent` (scrolls, and holds the pill), `SidebarFooter`.
17
+ - `SidebarGroup`: `label`; `collapsible` makes the label a disclosure button. Also `SidebarGroupLabel`.
18
+ - `SidebarMenu` › `SidebarMenuItem` › `SidebarMenuButton` (`href`, `current`, `icon`, `asChild` for a router's link) and `SidebarMenuBadge` (`label`, such as "4 unread").
19
+ - `SidebarTrigger`, and `SidebarInset` (the `main` landmark; it sets `--alt-page-pad` for `PageHeader`).
20
+
21
+ **Open questions, answered.** Persisting the collapsed state is up to your app (use `collapsed` and `onCollapsedChange` with a cookie or `localStorage`). The workspace switcher is a recipe, a `DropdownMenu` whose trigger is an `Avatar` and a name, not a part (see the preview).
22
+
23
+ **Accessibility.** [Landmarks](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/): `nav` with a label, `header`, `main`. A skip link ("Skip to content") comes first. The current destination has `aria-current="page"`. **A count is part of the destination's name** ("Inbox, 4 unread"). In the rail, each destination keeps its name as `aria-label` and in a tooltip. Collapsible groups use the disclosure pattern. `SidebarTrigger` says what it will do ("Collapse sidebar", "Open navigation") and carries `aria-keyshortcuts`. Under forced colours the pill gets a 1px border. Under `contrast="more"` it gets a 2px slab and the dividers are `line-control`. Under reduced motion the pill appears in place and the rail changes width at once.
24
+
25
+ ```tsx
26
+ <AppShell
27
+ sidebar={
28
+ <Sidebar>
29
+ <SidebarHeader><WorkspaceMenu /></SidebarHeader>
30
+ <SidebarContent>
31
+ <SidebarGroup label="Workspace">
32
+ <SidebarMenu>
33
+ <SidebarMenuItem>
34
+ <SidebarMenuButton href="/inbox" current icon={<Inbox />}>Inbox</SidebarMenuButton>
35
+ <SidebarMenuBadge label="4 unread">4</SidebarMenuBadge>
36
+ </SidebarMenuItem>
37
+ </SidebarMenu>
38
+ </SidebarGroup>
39
+ </SidebarContent>
40
+ <SidebarFooter><UserMenu /></SidebarFooter>
41
+ </Sidebar>
42
+ }
43
+ topBar={<SearchEntry />}>
44
+ {page}
45
+ </AppShell>
46
+ ```
47
+
48
+ - Do give every destination an icon if the sidebar can collapse to a rail.
49
+ - Do say what a count counts (`label="4 unread"`).
50
+ - Don't raise the sidebar into a card or give it a shadow.
51
+ - Don't put actions in the sidebar's destinations; they navigate.
52
+
53
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
54
+ ## Example
55
+
56
+ `AppShell` as first used in its preview:
57
+
58
+ ```tsx
59
+ import { AppShell, Avatar, Button, DropdownMenu, Kbd, PageHeader, PageHeaderDescription, PageHeaderTitle, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from 'altitude-design';
60
+
61
+ <AppShell
62
+ sidebar={
63
+ <Sidebar>
64
+ <SidebarHeader>
65
+ <DropdownMenu items={[{ label: 'Acme Inc.' }, { label: 'Northwind' }, { type: 'separator' }, { label: 'Create workspace' }]}
66
+ trigger={<button className="as-ws"><Avatar name="Acme Inc." size={24} /><span className="alt-sidebar-text">Acme Inc.</span></button>} />
67
+ </SidebarHeader>
68
+ <SidebarContent>
69
+ <SidebarGroup label="Workspace"><SidebarMenu>{item('inbox', 'Inbox', 4)}{item('projects', 'Projects')}{item('reports', 'Reports')}</SidebarMenu></SidebarGroup>
70
+ <SidebarGroup label="Admin" collapsible><SidebarMenu>{item('members', 'Members')}{item('billing', 'Billing')}{item('settings', 'Settings')}</SidebarMenu></SidebarGroup>
71
+ </SidebarContent>
72
+ <SidebarFooter><SidebarMenu><SidebarMenuItem><SidebarMenuButton href="#/me" icon={<Avatar name="Maya Chen" size={20} />}>Maya Chen</SidebarMenuButton></SidebarMenuItem></SidebarMenu></SidebarFooter>
73
+ </Sidebar>
74
+ }
75
+ topBar={<Button variant="secondary" size="sm" className="search">Search… <Kbd>⌘K</Kbd></Button>}>
76
+ <PageHeader primary={<Button variant="primary">New message</Button>}><PageHeaderTitle>{at[0].toUpperCase() + at.slice(1)}</PageHeaderTitle><PageHeaderDescription>The sidebar's current destination sits on a raised pill that glides as you move. ⌘B collapses it to a rail.</PageHeaderDescription></PageHeader>
77
+ <div className="rows">{Array.from({ length: 8 }, (_, i) => <div key={i} />)}</div>
78
+ </AppShell>
79
+ ```
80
+
81
+ The full preview, as rendered in Storybook and checked in CI (`as`, `as-ws`, `search`, `rows` are the preview page's own layout classes, not Altitude's):
82
+
83
+ ```tsx
84
+ import * as React from 'react';
85
+ import { AppShell, Avatar, Button, DropdownMenu, Kbd, PageHeader, PageHeaderDescription, PageHeaderTitle, Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem } from 'altitude-design';
86
+
87
+ const I = ({ d }) => <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d={d} /></svg>;
88
+ const icons = { inbox: 'M3 13h5l2 3h4l2-3h5M5 5h14l2 8v6H3v-6z', projects: 'M3 7h7l2 2h9v10H3z', reports: 'M5 20V10M12 20V4M19 20v-7', members: 'M16 19v-1a4 4 0 0 0-8 0v1M12 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6', billing: 'M3 7h18v10H3zM3 11h18', settings: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6M19 12l2-1-2-4-2 1-2-1-1-2h-4l-1 2-2 1-2-1-2 4 2 1v0l-2 1 2 4 2-1 2 1 1 2h4l1-2 2-1 2 1 2-4z' };
89
+ export default function App() {
90
+ const [at, setAt] = React.useState('inbox');
91
+ const item = (key, label, badge) => (
92
+ <SidebarMenuItem key={key}>
93
+ <SidebarMenuButton href={'#/' + key} current={at === key} icon={<I d={icons[key]} />} onClick={(e) => { e.preventDefault(); setAt(key); }}>{label}</SidebarMenuButton>
94
+ {badge && <SidebarMenuBadge label={`${badge} unread`}>{badge}</SidebarMenuBadge>}
95
+ </SidebarMenuItem>
96
+ );
97
+ return (
98
+ <div className="as">
99
+ <AppShell
100
+ sidebar={
101
+ <Sidebar>
102
+ <SidebarHeader>
103
+ <DropdownMenu items={[{ label: 'Acme Inc.' }, { label: 'Northwind' }, { type: 'separator' }, { label: 'Create workspace' }]}
104
+ trigger={<button className="as-ws"><Avatar name="Acme Inc." size={24} /><span className="alt-sidebar-text">Acme Inc.</span></button>} />
105
+ </SidebarHeader>
106
+ <SidebarContent>
107
+ <SidebarGroup label="Workspace"><SidebarMenu>{item('inbox', 'Inbox', 4)}{item('projects', 'Projects')}{item('reports', 'Reports')}</SidebarMenu></SidebarGroup>
108
+ <SidebarGroup label="Admin" collapsible><SidebarMenu>{item('members', 'Members')}{item('billing', 'Billing')}{item('settings', 'Settings')}</SidebarMenu></SidebarGroup>
109
+ </SidebarContent>
110
+ <SidebarFooter><SidebarMenu><SidebarMenuItem><SidebarMenuButton href="#/me" icon={<Avatar name="Maya Chen" size={20} />}>Maya Chen</SidebarMenuButton></SidebarMenuItem></SidebarMenu></SidebarFooter>
111
+ </Sidebar>
112
+ }
113
+ topBar={<Button variant="secondary" size="sm" className="search">Search… <Kbd>⌘K</Kbd></Button>}>
114
+ <PageHeader primary={<Button variant="primary">New message</Button>}><PageHeaderTitle>{at[0].toUpperCase() + at.slice(1)}</PageHeaderTitle><PageHeaderDescription>The sidebar's current destination sits on a raised pill that glides as you move. ⌘B collapses it to a rail.</PageHeaderDescription></PageHeader>
115
+ <div className="rows">{Array.from({ length: 8 }, (_, i) => <div key={i} />)}</div>
116
+ </AppShell>
117
+ </div>
118
+ );
119
+ }
120
+ ```
121
+ <!-- /example -->
@@ -0,0 +1,101 @@
1
+ # AreaChart
2
+
3
+ A total over time and the parts that make it up (requests by region, storage by type), or a single volume over time. Stacked areas answer "how big, and from where"; `stack="expand"` answers "what share". It renders inside a [`ChartContainer`](../ChartContainer/README.md).
4
+
5
+ **Why it's flat.** Bands are the record. Each is a flat `data-*` fill, stacked on the ground line in config order from the bottom, and parted from the band above by a 2px line in the plot's own colour, the same gap a stacked bar's segments have. The topmost band carries a 2px top line in its colour. A single area is a flat `data-1` fill at reduced opacity under a 2px `data-1` line.
6
+
7
+ **No gradient fill.** Most libraries fade an area to transparent by default. Altitude's only face gradient is `--alt-sheen`, and it means "press it" (E4), so an area never has one.
8
+
9
+ **Stack order never changes.** Bands stack in config order, so a band never jumps to a new position when values change, and its colour stays its own. In a stack a missing value adds nothing (the bands above rest on the one below); in a single area it's a gap, as in a line.
10
+
11
+ **Named in place.** Stacked bands are labelled inside themselves in `ink`, on a halo of the plot's colour, at their thickest point, where they're at least 20px tall; thin bands rely on the legend and the readout. `labels={false}` turns this off.
12
+
13
+ **Without colour.** Under forced colours and in print, and with `patterns` on the container, each band takes its slot's hatch (the first band is solid). With `contrast="more"` the separators are 3px `ink`.
14
+
15
+ **Motion.** As `LineChart`: when the x values are the same, bands glide to their new values on `ease-glide`, after any rescale; when they differ, the old chart and the new one cross-fade. Nothing grows from the ground on first render ([Motion](../../visual/Motion.md), "Charts"). While data loads, the imprint is an area pressed into the page.
16
+
17
+ **Accessibility.** The keyboard path is the container's (#63): ←/→ along x and, in a stack, ↑/↓ through the bands at that x, from the bottom up, so ↑ goes to the band above. Each point names its band's value and its share of the total: "Oct 1, 2026, EU, 1.1K. 34% of 3.2K. 1 of 14." The table view (`views`) has a column per band and a Total.
18
+
19
+ **Not yet the record.** `status` marks partial or projected records. From the last settled point on, every band is hatched in its colour on the plot's own, with no solid fill, and the top line is dashed. A 1px `line-control` rule named "Today" (`boundary`) marks where the record ends, and the readout, point names and table say "Projected" or "So far". While patterns are on, a band with its own hatch keeps it there, faded, so bands stay told apart.
20
+
21
+ **Selecting and drilling.** `selected` and `onSelect` make the marks a selection that cross-filters the rest of a dashboard, and `drill` opens a level down inside a `Drilldown`. Hover only promises what a click does. See [ChartContainer](../ChartContainer/README.md), "Marks you can select or drill into".
22
+
23
+ **Filtering in place.** `relevance` recedes the bands to 35% around each missing record, so the stack keeps its shape; `rescale` fits the axis to the matches. See [ChartContainer](../ChartContainer/README.md), "Filtering in place".
24
+
25
+ **A sliding window.** `window` works as `LineChart`'s: the latest stretch of x, gliding one step in a `live` container, or a chosen `{ start, end }`, which a `ChartBrush` sets (`onWindowChange`).
26
+
27
+ **Props:** `data` and `x`; `window` and `onWindowChange`; `stack` (`true` or `'expand'`); `curve` (`'linear'` or `'monotone'`); `y={{ format }}`; `labels`; `tooltip`; `status` and `boundary`; and a `ChartTooltip` or `ChartLegend` as children.
28
+
29
+ **Focus.** `focus={['team']}` keeps those bands' colours and draws the rest in `data-context`, their labels in `ink-muted`. The stack's order doesn't change. See [ChartContainer](../ChartContainer/README.md), "Focus".
30
+
31
+ **You provide:** a `ChartContainer` whose `config` names each band in stacking order (the base first), the `data` in x order, and usually a `summary`.
32
+
33
+ ```tsx
34
+ <ChartContainer config={{ us: { label: 'US' }, eu: { label: 'EU' }, apac: { label: 'APAC' } }}>
35
+ <AreaChart data={requests} x="day" stack /> {/* stack="expand" for 100% */}
36
+ </ChartContainer>
37
+ ```
38
+
39
+ - Do put the largest or steadiest part at the bottom, where it stands on the ground line and reads most accurately.
40
+ - Do use `stack="expand"` only when the share is the point; the totals disappear.
41
+ - Do keep to six bands or fewer; fold the rest into one "Other" series.
42
+ - Don't overlay several unstacked areas; compare trends with a `LineChart`.
43
+ - Don't add a gradient, a glow or a shadow to make it look richer.
44
+
45
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
46
+ ## Example
47
+
48
+ `AreaChart` as first used in its preview:
49
+
50
+ ```tsx
51
+ import { AreaChart, ChartLegend, ChartLegendContent } from 'altitude-design';
52
+
53
+ <AreaChart data={week ? reqB : reqA} x="day" stack curve="monotone">
54
+ <ChartLegend content={<ChartLegendContent toggle />} />
55
+ </AreaChart>
56
+ ```
57
+
58
+ The full preview, as rendered in Storybook and checked in CI (`ac`, `wide` are the preview page's own layout classes, not Altitude's):
59
+
60
+ ```tsx
61
+ import * as React from 'react';
62
+ import { AreaChart, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer, ChartLegend, ChartLegendContent } from 'altitude-design';
63
+
64
+ const acDays = Array.from({ length: 14 }, (_, i) => `2026-10-${String(i + 1).padStart(2, '0')}`);
65
+ const reqA = acDays.map((day, i) => ({ day, us: 1600 + i * 40 + (i % 3) * 90, eu: 1100 + Math.round(180 * Math.sin(i / 2)), apac: 520 + i * 35 }));
66
+ const reqB = acDays.map((day, i) => ({ day, us: 1500 + i * 55 + (i % 4) * 70, eu: 1250 + Math.round(140 * Math.cos(i / 2)), apac: 700 + i * 20 }));
67
+ const plans = ['2026-01-01', '2026-02-01', '2026-03-01', '2026-04-01', '2026-05-01', '2026-06-01', '2026-07-01', '2026-08-01']
68
+ .map((month, i) => ({ month, free: 620 - i * 30, team: 210 + i * 22, business: 90 + i * 15, enterprise: 20 + i * 6 }));
69
+ const storage = acDays.map((day, i) => ({ day, used: [41, 42, 42.5, 44, 45, 45.2, 47, 49, 50, 50.4, 53, 55, 56, 58][i] }));
70
+ const k = new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 });
71
+ function Chart({ id, title, sub, children, className, ...props }) {
72
+ return (
73
+ <Card className={className}>
74
+ <CardHeader><CardTitle id={id}>{title}</CardTitle><CardDescription>{sub}</CardDescription></CardHeader>
75
+ <CardContent><ChartContainer aria-labelledby={id} {...props}>{children}</ChartContainer></CardContent>
76
+ </Card>
77
+ );
78
+ }
79
+ export default function App() {
80
+ const [week, setWeek] = React.useState(0);
81
+ return (
82
+ <div className="ac">
83
+ <Chart className="wide" id="a1" title="Requests by region" sub={<>Stacked: the total and where it came from · <Button size="sm" variant="ghost" onClick={() => setWeek((w) => 1 - w)}>{week ? 'First week' : 'Second week'}</Button></>}
84
+ config={{ us: { label: 'US', format: k.format }, eu: { label: 'EU', format: k.format }, apac: { label: 'APAC', format: k.format } }} aspect={3.2} views>
85
+ <AreaChart data={week ? reqB : reqA} x="day" stack curve="monotone">
86
+ <ChartLegend content={<ChartLegendContent toggle />} />
87
+ </AreaChart>
88
+ </Chart>
89
+ <Chart id="a2" title="Accounts by plan" sub='stack="expand", focus on Team'
90
+ config={{ free: { label: 'Free' }, team: { label: 'Team' }, business: { label: 'Business' }, enterprise: { label: 'Enterprise' } }} aspect={1.7}>
91
+ <AreaChart data={plans} x="month" stack="expand" focus={['team']} />
92
+ </Chart>
93
+ <Chart id="a3" title="Storage used" sub="One area: a flat fill under its line" config={{ used: { label: 'Used', format: (v) => `${v} GB` } }} aspect={1.7}
94
+ summary="Storage use rose from 41 GB to 58 GB over two weeks, fastest in the second.">
95
+ <AreaChart data={storage} x="day" />
96
+ </Chart>
97
+ </div>
98
+ );
99
+ }
100
+ ```
101
+ <!-- /example -->
@@ -0,0 +1,99 @@
1
+ # Artifact
2
+
3
+ What an agent is making (a document, a component, a page, a chart, a config) in its own pane beside the conversation, with its versions. Claude's artifacts, ChatGPT's canvas and v0's preview work this way: the latest version as it's written, preview and source, earlier versions to go back to, what changed.
4
+
5
+ **Elevation: the current version is the record.**
6
+ - **The pane** lies on the ground beside the conversation (put it in a `ResizablePanel`), named by its title.
7
+ - **Being written** isn't the record yet: a dashed `line-control` rule at the pane's edge and "Writing… version 3" (`aria-busy`).
8
+ - **Complete**: the rule goes solid with one afterglow, and "Version 3" (announced once: "Pricing page, version 3, complete").
9
+ - **Failed**: "Couldn't finish version 3" with a critical mark and Retry; keep the last whole version on show. The edge stays solid and neutral: a coloured stripe is a tell of generated interfaces (Distinctiveness, CM3), and the words already say it.
10
+ - **Viewing an earlier version** is a mode, and a mode isn't a height: a flush strip across the top with a `line-control` border, "Viewing version 1 of 3", Return to latest and Restore this version.
11
+ - **Header tools** are ghost buttons named for the artifact. **Preview / Source / Changes** is segmented `Tabs`.
12
+ - **The reference** in a message is a plate that acts, lifting one level when pointed at.
13
+
14
+ **Accessibility.** A region named by its title. The reference is a button with `aria-controls` on the pane: it opens the pane and moves focus to the pane's heading, and Escape in the pane goes back to it. Give an iframe preview a `title`, and keep the source view as its alternative. Writing isn't announced again and again; completion is said once.
15
+
16
+ **Parts:** `Artifact` (`title`, `status`: `'writing' | 'complete' | 'failed'`, `version`, `onRetry`, `id`); `ArtifactHeader` (`onCopy`, `onDownload`, `onOpen`, `onClose`, extra tools as children); `ArtifactVersions` (`versions`, `current`, `onChange`, `onRestore`); `ArtifactContent` (`views`: `{ preview?, source?, changes? }`, `defaultView`); `ArtifactReference` (`title`, `version`, `status`, `controls`, `onOpen`).
17
+
18
+ **You provide:** the versions and their content (a sandboxed iframe for previews, `CodeBlock` for source, `DiffView` for changes), and where the pane sits. On a phone, a `ResizablePanelGroup` with `narrow="stack"` shows the conversation or the pane, one at a time; set `narrowPanel` to the pane when it opens.
19
+
20
+ ```tsx
21
+ <Artifact id="pricing" title="Pricing page" status={a.status} version={a.versions.length} onRetry={retry}>
22
+ <ArtifactHeader onCopy onDownload={download} onClose={close} />
23
+ <ArtifactVersions versions={a.versions.length} current={v} onChange={setV} onRestore={restore} />
24
+ <ArtifactContent views={{ preview: <iframe sandbox="allow-scripts" srcDoc={html} title="Pricing page preview" />, source: <CodeBlock language="tsx" code={code} /> }} />
25
+ </Artifact>
26
+ ```
27
+
28
+ - Do sandbox preview iframes; the frame's permissions are yours.
29
+ - Do keep the last whole version visible when a new one fails.
30
+ - Don't lift the pane or tint it for an earlier version; say it in the strip.
31
+
32
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
33
+ ## Example
34
+
35
+ `Artifact` as first used in its preview:
36
+
37
+ ```tsx
38
+ import { Artifact, ArtifactContent, ArtifactHeader, ArtifactVersions, CodeBlock, DiffView } from 'altitude-design';
39
+
40
+ <Artifact id="pricing-artifact" title="Pricing page" status={status} version={3}>
41
+ <ArtifactHeader onCopy={() => versions[v - 1].code} onDownload={() => {}} onClose={() => setOpen(false)} />
42
+ <ArtifactVersions versions={3} current={v} onChange={setV} onRestore={() => setV(3)} />
43
+ <ArtifactContent views={{
44
+ preview: <iframe sandbox="" srcDoc={html(v)} title={`Pricing page preview, version ${v}`} />,
45
+ source: <CodeBlock language="tsx" code={versions[v - 1].code} streaming={status === 'writing' && v === 3} />,
46
+ changes: v > 1 ? <DiffView caption={`Version ${v - 1} → ${v}`} before={versions[v - 2].code} after={versions[v - 1].code} /> : undefined,
47
+ }} />
48
+ </Artifact>
49
+ ```
50
+
51
+ The full preview, as rendered in Storybook and checked in CI (`af`, `chat` are the preview page's own layout classes, not Altitude's):
52
+
53
+ ```tsx
54
+ import * as React from 'react';
55
+ import { Artifact, ArtifactContent, ArtifactHeader, ArtifactReference, ArtifactVersions, CodeBlock, DiffView, Message, MessageContent, ResizableHandle, ResizablePanel, ResizablePanelGroup } from 'altitude-design';
56
+
57
+ const versions = [
58
+ { code: `export function Pricing() {\n return <h1>Pricing</h1>;\n}` },
59
+ { code: `export function Pricing() {\n return (\n <section>\n <h1>Pricing</h1>\n <p>Start free, upgrade when you grow.</p>\n </section>\n );\n}` },
60
+ { code: `export function Pricing() {\n return (\n <section>\n <h1>Pricing</h1>\n <p>Start free, upgrade when you grow.</p>\n <Plans tiers={['Starter', 'Pro', 'Business']} />\n </section>\n );\n}` },
61
+ ];
62
+ const html = (v) => `<body style="font:15px system-ui;margin:24px;color:#1b2230"><h1 style="font-size:22px">Pricing</h1>${v >= 2 ? '<p>Start free, upgrade when you grow.</p>' : ''}${v >= 3 ? '<div style="display:flex;gap:8px">' + ['Starter', 'Pro', 'Business'].map((t) => `<div style="flex:1;padding:12px;border:1px solid #d5dae3;border-radius:8px">${t}</div>`).join('') + '</div>' : ''}</body>`;
63
+ export default function App() {
64
+ const [open, setOpen] = React.useState(true);
65
+ const [status, setStatus] = React.useState('writing');
66
+ const [v, setV] = React.useState(3);
67
+ React.useEffect(() => { const t = setTimeout(() => setStatus('complete'), 2500); return () => clearTimeout(t); }, []);
68
+ return (
69
+ <div className="af">
70
+ <ResizablePanelGroup direction="horizontal" narrow="stack" narrowPanel={open ? 1 : 0}>
71
+ <ResizablePanel defaultSize={42} aria-label="Conversation">
72
+ <div className="chat">
73
+ <Message from="user"><MessageContent>Make a pricing page with three plans.</MessageContent></Message>
74
+ <Message from="assistant">
75
+ <MessageContent>Here's the page, with the three plans in a row.</MessageContent>
76
+ <ArtifactReference title="Pricing page" version={3} status={status} controls="pricing-artifact" onOpen={() => setOpen(true)} />
77
+ </Message>
78
+ </div>
79
+ </ResizablePanel>
80
+ <ResizableHandle />
81
+ <ResizablePanel defaultSize={58} aria-label="Pricing page">
82
+ {open && (
83
+ <Artifact id="pricing-artifact" title="Pricing page" status={status} version={3}>
84
+ <ArtifactHeader onCopy={() => versions[v - 1].code} onDownload={() => {}} onClose={() => setOpen(false)} />
85
+ <ArtifactVersions versions={3} current={v} onChange={setV} onRestore={() => setV(3)} />
86
+ <ArtifactContent views={{
87
+ preview: <iframe sandbox="" srcDoc={html(v)} title={`Pricing page preview, version ${v}`} />,
88
+ source: <CodeBlock language="tsx" code={versions[v - 1].code} streaming={status === 'writing' && v === 3} />,
89
+ changes: v > 1 ? <DiffView caption={`Version ${v - 1} → ${v}`} before={versions[v - 2].code} after={versions[v - 1].code} /> : undefined,
90
+ }} />
91
+ </Artifact>
92
+ )}
93
+ </ResizablePanel>
94
+ </ResizablePanelGroup>
95
+ </div>
96
+ );
97
+ }
98
+ ```
99
+ <!-- /example -->
@@ -0,0 +1,75 @@
1
+ # Avatar
2
+
3
+ Represents a person. A face is a disc on the page, built from the same pieces as everything around it: while the person is here it rests on the page like a card, and while they're not it's part of the record, a plate set flush into the page. Only someone live leaves the ground.
4
+
5
+ | State | Height | Face | Lamp |
6
+ |---|---|---|---|
7
+ | `active` | resting: level 1, a contact shadow | `surface-raised` | filled `positive` |
8
+ | `idle` | resting: level 1, a contact shadow | `surface-raised` | an empty ring in `caution` |
9
+ | `offline` | flush in the page (ground), photo greyscale | a badge plate, `ink-muted` initials | none |
10
+ | `live` | lifted off the page, drifting over `dur-hover`; its shadow appears on the page beneath it | `surface-raised` at level 2 | as its status |
11
+
12
+ Faces are neutral. Status colours never stand for a person, so an initial on a pink disc can't read as an error; colour lives in the lamp, as it does on a `Badge`. Active and idle differ in the lamp's shape (filled or an empty ring), not only its colour, and not in height: they're one tier, because both people are here.
13
+
14
+ `live` (speaking, typing, presenting right now) lifts the face: the shadow beneath it appears on the page and gets smaller and fainter as the face rises, the cue people read as an object floating rather than resting. Under reduced motion it holds a little above the page instead of drifting. Height is animated through a registered `--hr` property, so face and shadow move together on `ease-settle`. `presence={1|2|3}` rings the face in that person's presence colour, a 2px ring drawn tight around it.
15
+
16
+ The lamp is cut out of the face by a 2px ring of ground. It shows at 24px and larger (`lamp={false}` hides it) and is never shown inside a group. With `onClick` the avatar becomes a button: hovering or focusing lifts it off the page, and pressing returns it to the ground, the same promise as everything else that lifts. Avatars without `onClick` never lift on hover.
17
+
18
+ **Teams, organisations, bots and agents** are `shape="square"`: a tile whose corners grow with its size (`radius-xs` up to 24px, `radius-sm` to 36, `radius-md` to 48, `radius-lg` above), with the same states, lamp and shadow as a person. Keep circles for people, so a glance tells you whether a person did something.
19
+
20
+ **Faces.** With `src` the photo fills the face; if it fails to load, the initials show instead. Initials are the first letter of the first and last words, skipping punctuation and emoji ("Mara de Quist" is MQ, "Jean-Luc Picard" JP, "李小龙" 李); below 28px, one letter, drawn larger. Names that no rule abbreviates well are better given a photo.
21
+
22
+ **Sizes.** 24px in dense rows and tables, 28–32px in lists and groups, 36px (the default) beside a name, 44px on cards, 64px in a profile header.
23
+
24
+ In an `AvatarGroup`, every face stands on one ground line, each cut out of the one before it by a ring of ground, overlapping by 12% so the initials beneath stay whole. Offline faces are flush plates in the row; a live face lifts in front of its neighbours. `max` caps the faces shown and the rest become a flush "+N" plate whose label names them ("2 more: Eli Ward, Kai Ito"); `size` sizes every face that doesn't set its own. The group is a `role="group"` labelled with how many people it holds ("7 people"); pass your own `aria-label` for something more specific ("Editing now").
25
+
26
+ **Loading.** While `name` is undefined (`user?.name` while your data loads), or a promise, the avatar is an imprint of its `size` and `shape`; it rises out of it when the name arrives (see `Await`).
27
+
28
+ **Forced colours and high contrast.** Faces get a `CanvasText` rim (`GrayText` when offline, 2px when live), and the active lamp is filled while the idle one stays a ring. Under `contrast="more"` a resting face sits on the same ink slab as a card.
29
+
30
+ **Right to left.** The lamp sits at the inline end and a group overlaps from the inline start, so both mirror with `dir="rtl"`.
31
+
32
+ **You provide:** `name` (always: it's the accessible label, with the status appended), optional `src`, `size` in pixels (default 36), `shape`, `status`, `live`, `presence`, `lamp`, `onClick`. On `AvatarGroup`: `max`, `size`.
33
+
34
+ - Leave about a fifth of the avatar's size free above a live or pressable avatar for it to rise into, and a quarter above a group.
35
+
36
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
37
+ ## Example
38
+
39
+ `Avatar` as first used in its preview:
40
+
41
+ ```tsx
42
+ import { Avatar } from 'altitude-design';
43
+
44
+ <Avatar name="Mara Quist" size={44} status="active" live presence={1} />
45
+ ```
46
+
47
+ The full preview, as rendered in Storybook and checked in CI (`col`, `people`, `who`, `cap` are the preview page's own layout classes, not Altitude's):
48
+
49
+ ```tsx
50
+ import { Avatar, AvatarGroup } from 'altitude-design';
51
+
52
+ export default function App() {
53
+ return (
54
+ <div className="col" style={{ gap: 22 }}>
55
+ <div className="people">
56
+ <div className="who"><Avatar name="Mara Quist" size={44} status="active" live presence={1} /><span className="cap">live</span></div>
57
+ <div className="who"><Avatar name="Jon Reyes" size={44} status="active" /><span className="cap">active</span></div>
58
+ <div className="who"><Avatar name="Lena Park" size={44} status="idle" /><span className="cap">idle</span></div>
59
+ <div className="who"><Avatar name="Sam Okafor" size={44} status="offline" /><span className="cap">offline</span></div>
60
+ <div className="who"><Avatar name="Noa Levi" size={44} status="active" onClick={() => {}} /><span className="cap">pressable</span></div>
61
+ <div className="who"><Avatar name="Northwind Studio" size={44} shape="square" /><span className="cap">team</span></div>
62
+ <div className="who"><Avatar name="Deploy bot" size={44} shape="square" status="active" /><span className="cap">bot</span></div>
63
+ </div>
64
+ <div className="people" style={{ alignItems: 'center' }}>
65
+ <AvatarGroup max={5} size={32}>
66
+ {[['Ada Obi', 'active', true], ['Ivy Chen', 'idle'], ['Theo Marsh', 'active'], ['Rui Costa', 'idle'], ['Noa Levi', 'offline'], ['Eli Ward', 'offline'], ['Kai Ito', 'offline']].map(([n, st, live]) => <Avatar key={n} name={n} status={st} live={live} />)}
67
+ </AvatarGroup>
68
+ {[24, 36, 64].map((s) => <Avatar key={s} name="Ines Park" size={s} status="active" />)}
69
+ </div>
70
+ <p className="cap">People who are here rest on the page: a filled lamp when they're active, an empty one when they're away. Offline people lie flush and grey. Mara is speaking, so she lifts off her shadow, ringed in her presence colour. Teams and bots are square. Hover Noa: an avatar you can press rises like everything else that acts.</p>
71
+ </div>
72
+ );
73
+ }
74
+ ```
75
+ <!-- /example -->