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,73 @@
1
+ # Tabs
2
+
3
+ Switches between views of the same content.
4
+
5
+ **`variant="folder"` (default): the sheet on top is the one you're reading.** Every tab is the top edge of its own sheet. The chosen tab and its panel are one raised piece (`surface-raised`, `elev-2`) with concave fillets where they meet; the other tabs lie flush on the ground behind it. Choosing a tab brings its sheet up from behind: its tab rises 10px into place on `ease-settle` while the old tab drops back, and the panel's shadow swells from `elev-1` as the new sheet surfaces, its content emerging 3px below. Hovering an unchosen tab peeks its sheet up a little: a ghost of the tab, before you commit.
6
+
7
+ **`variant="segmented"`:** a compact control for ranges and modes. The options sit in a well and one raised pill glides between them on `ease-settle`; hovering an option shows a faint ghost of the pill.
8
+
9
+ Arrow keys, Home and End move between tabs in both.
10
+
11
+ **Narrow.** A tab list that doesn't fit scrolls within itself, so the page never scrolls sideways: the chosen tab stays in view, and an edge with more tabs past it fades out.
12
+
13
+ **You provide:** `Tabs` with `value`/`onValueChange` or `defaultValue` and optionally `variant`; a `TabsList` (with an `aria-label`) of `TabsTrigger value="…"`; one `TabsContent value="…"` per trigger. In the folder variant put the `TabsContent`s directly after the `TabsList`: together they're one folder.
14
+
15
+ - Keep labels to one or two words.
16
+ - Don't use tabs for sequential steps; they imply the views are peers.
17
+
18
+ `TabsContent` takes `forceMount` to keep an unchosen panel mounted (inert, hidden from assistive technology, `data-state="inactive"`), for views that hold state or cross-fade, as a chart's Chart | Table views do.
19
+
20
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
21
+ ## Example
22
+
23
+ `Tabs` as first used in its preview:
24
+
25
+ ```tsx
26
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from 'altitude-design';
27
+
28
+ <Tabs defaultValue="overview">
29
+ <TabsList aria-label="Project">
30
+ <TabsTrigger value="overview">Overview</TabsTrigger>
31
+ <TabsTrigger value="activity">Activity</TabsTrigger>
32
+ <TabsTrigger value="settings">Settings</TabsTrigger>
33
+ <TabsTrigger value="members">Members</TabsTrigger>
34
+ </TabsList>
35
+ <TabsContent value="overview"><p className="body">Three deployments this week. Everything is healthy.</p></TabsContent>
36
+ <TabsContent value="activity"><p className="body">Mara merged “Faster cold starts” 2 hours ago.</p></TabsContent>
37
+ <TabsContent value="settings"><p className="body">Region, domains and environment variables.</p></TabsContent>
38
+ <TabsContent value="members"><p className="body">Six people have access to this project.</p></TabsContent>
39
+ </Tabs>
40
+ ```
41
+
42
+ The full preview, as rendered in Storybook and checked in CI (`stack`, `body` are the preview page's own layout classes, not Altitude's):
43
+
44
+ ```tsx
45
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from 'altitude-design';
46
+
47
+ export default function App() {
48
+ return (
49
+ <div className="stack">
50
+ <Tabs defaultValue="overview">
51
+ <TabsList aria-label="Project">
52
+ <TabsTrigger value="overview">Overview</TabsTrigger>
53
+ <TabsTrigger value="activity">Activity</TabsTrigger>
54
+ <TabsTrigger value="settings">Settings</TabsTrigger>
55
+ <TabsTrigger value="members">Members</TabsTrigger>
56
+ </TabsList>
57
+ <TabsContent value="overview"><p className="body">Three deployments this week. Everything is healthy.</p></TabsContent>
58
+ <TabsContent value="activity"><p className="body">Mara merged “Faster cold starts” 2 hours ago.</p></TabsContent>
59
+ <TabsContent value="settings"><p className="body">Region, domains and environment variables.</p></TabsContent>
60
+ <TabsContent value="members"><p className="body">Six people have access to this project.</p></TabsContent>
61
+ </Tabs>
62
+ <Tabs defaultValue="week" variant="segmented">
63
+ <TabsList aria-label="Range">
64
+ <TabsTrigger value="day">Day</TabsTrigger>
65
+ <TabsTrigger value="week">Week</TabsTrigger>
66
+ <TabsTrigger value="month">Month</TabsTrigger>
67
+ </TabsList>
68
+ </Tabs>
69
+ </div>
70
+ );
71
+ }
72
+ ```
73
+ <!-- /example -->
@@ -0,0 +1,116 @@
1
+ # TagInput
2
+
3
+ Edit a set of short values in one field: labels on an issue, recipients, skills, keywords, allowed domains. Type and press Enter or comma to add; Backspace twice takes the last one back; each tag has a remove button; Enter, F2 or a double-click edits a tag in place. `Tag` is the same plate on its own, for showing a set outside a field.
4
+
5
+ ```tsx
6
+ <Field label="Labels">
7
+ <TagInput
8
+ value={labels}
9
+ onValueChange={setLabels}
10
+ suggestions={allLabels} // optional; free entry is allowed unless `restrict`
11
+ max={10}
12
+ normalize={(tag) => tag.trim().toLowerCase()} // duplicates are compared after this
13
+ validate={(tag) => tag.length <= 24 ? undefined : 'Use 24 characters or fewer.'}
14
+ noun={['label', 'labels']}
15
+ />
16
+ </Field>
17
+ ```
18
+
19
+ Without `onAdd` and `onRemove` it's a controlled form value. With them, each tag is persisted on its own: `onAdd(tag)` returns `{ ok: true, value }` with the canonical text or `{ ok: false, error }`, the result contract `InlineEdit` uses. The tag waits, pending, until the result. A rejected tag's text comes back to the input with the error so it can be corrected.
20
+
21
+ ## Elevation
22
+
23
+ - **The field** is an opening, built like `Input`: an empty field shows its back wall (the well). Once it holds a tag its floor rises flush, so the field reads as filled. Invalid fields drop to trench depth on `critical-soft`.
24
+ - **A tag** is a label and part of the record, so it's a Badge plate (ground, level 0) stamped flush into the floor. A new tag is stamped in (down 3px, settling), as a Badge is when its label changes.
25
+ - **Armed** (the first Backspace on an empty input): the last tag lifts one level (`elev-1`) on a `critical-soft` tint, and the status says “Press Backspace again to remove <tag>”. The second Backspace lifts it off (up 8px, fading), and the gap closes. Typing anything disarms it.
26
+ - **Pending** (waiting on `onAdd` or `onRemove`): after `dur-grace` the tag hovers at level 2 (`surface-lifted`, `elev-2`), with `aria-busy`. **Rejected**: the tag drops to a trench with a critical rim; the message is shown and announced.
27
+ - **Being edited**: the plate becomes a small well with an input labelled “Edit <tag>”.
28
+ - **Suggestions** are the `Combobox` list at level 3, with its ranking and gliding highlight.
29
+
30
+ Without height: tags are text in a list named by their count; the armed tag is announced; pending tags are `aria-busy`; errors are text wired to the input's `aria-describedby`. Under forced colours tags are 1px outlined, the armed tag is outlined in Highlight, pending dotted, failed thick dashed, and an empty field dashed. Under `contrast="more"` the armed tag gets a 2px ink slab.
31
+
32
+ ## Keyboard
33
+
34
+ - **In the input** (a `combobox`): Enter or a separator adds what's typed (Enter on an active suggestion adds it); ↓/↑ move through suggestions; Backspace on an empty input arms the last tag, and a second removes it; ← from the start moves to the last tag; Escape closes the list or disarms. Nothing acts during IME composition, and a separator typed as text (a soft keyboard's comma) adds too. A paste containing separators or newlines adds one tag per entry, up to `max`; what doesn't fit stays in the input.
35
+ - **On a tag**: the tags are one Tab stop (roving `tabindex`); ←/→ move between them and → from the last returns to the input; Home/End go to the ends. Delete or Backspace removes the tag and focuses the next tag, else the previous, else the input. Enter or F2 edits it in place (Enter saves, Escape cancels); editing is off when `onAdd` or `onRemove` persist tags, because a rename would be two writes.
36
+ - **At `max`** the input stays rendered, focused and read-only, and the description says “Limit reached (10).” Focus is never lost.
37
+
38
+ The remove buttons are for pointers (`tabIndex={-1}`, named “Remove <tag>”); the keyboard path is Delete on the focused tag. One polite status announces “Added design”, “Removed design”, “design is already added.” and errors. The input is described by the count and the separators (“3 labels. Press comma or Enter to add.”).
39
+
40
+ ## Props
41
+
42
+ | Prop | |
43
+ |---|---|
44
+ | `value`, `defaultValue`, `onValueChange` | the tags, a `string[]` |
45
+ | `suggestions`, `restrict` | strings or `SelectOption`s offered as you type; with `restrict` only they can be added |
46
+ | `separators` | default `[',', 'Enter']`; single characters also split a paste |
47
+ | `max`, `validate`, `normalize` | a limit; a per-tag error; applied before validating and comparing (default trim) |
48
+ | `onAdd`, `onRemove` | persist one tag at a time (see above) |
49
+ | `renderTag`, `noun` | draw a tag's contents; words for the count (default `['tag', 'tags']`) |
50
+ | `placeholder`, `id`, `name`, `disabled`, `required`, `invalid`, `size` | as `Combobox`; `name` submits each tag through a hidden input |
51
+
52
+ `Tag`: `children`, optional `onRemove` (adds a remove button) and `label` (names it when the children aren't text).
53
+
54
+ ## You provide
55
+
56
+ The set, its policy (what's allowed, the limit, normalizing), and any persistence. TagInput doesn't fetch suggestions; pass the current list.
57
+
58
+ ## Do and don't
59
+
60
+ - **Do** use it for sets of short, free values. For one value from a list, use `Combobox`; for several from a short fixed list, use `Checkbox`es.
61
+ - **Do** normalize (case, spaces) so duplicates are caught.
62
+ - **Don't** put long text in tags; a tag is cut at 24 characters with an ellipsis.
63
+
64
+ ## Demo and verification
65
+
66
+ The `TagInput` section of `build/previews.txt` shows labels (suggestions, a limit of 6, coloured dots), recipients persisted one at a time (a fictional invite service rejects other domains), a disabled set and standalone `Tag`s. `node checks/tag-input.mjs` (from `build/`; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers adding, suggestions, duplicates, double Backspace, focus after removal, editing, paste splitting, the limit, IME, pending and rejected adds, removal, and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
67
+
68
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
69
+ ## Example
70
+
71
+ `TagInput` as first used in its preview:
72
+
73
+ ```tsx
74
+ import { TagInput } from 'altitude-design';
75
+
76
+ <TagInput value={labels} onValueChange={setLabels} suggestions={TAG_LABELS} max={6} noun={['label', 'labels']}
77
+ placeholder="Add a label" normalize={tag => tag.trim().toLowerCase()} validate={tag => tag.length <= 24 ? undefined : 'Use 24 characters or fewer.'}
78
+ renderTag={tag => <>{TAG_DOTS[tag] && <span className="tags-dot" style={{ '--dot': TAG_DOTS[tag] }} aria-hidden="true" />}{tag}</>} />
79
+ ```
80
+
81
+ The full preview, as rendered in Storybook and checked in CI (`tags-demo`, `tags-dot`, `tags-caption`, `tags-row` are the preview page's own layout classes, not Altitude's):
82
+
83
+ ```tsx
84
+ import * as React from 'react';
85
+ import { Field, Tag, TagInput } from 'altitude-design';
86
+
87
+ const TAG_LABELS = ['bug', 'design', 'docs', 'performance', 'accessibility', 'onboarding', 'billing', 'mobile'];
88
+ const TAG_DOTS = { bug: 'var(--critical)', design: 'var(--accent)', accessibility: 'var(--positive)', performance: 'var(--caution)' };
89
+ const tagWait = ms => new Promise(resolve => setTimeout(resolve, ms));
90
+ export default function App() {
91
+ const [labels, setLabels] = React.useState(['design', 'accessibility']);
92
+ const [recipients, setRecipients] = React.useState(['mara@northwind.example']);
93
+ const [skills, setSkills] = React.useState(['Figma', 'Research', 'Writing']);
94
+ return <div className="tags-demo">
95
+ <Field label="Labels">
96
+ <TagInput value={labels} onValueChange={setLabels} suggestions={TAG_LABELS} max={6} noun={['label', 'labels']}
97
+ placeholder="Add a label" normalize={tag => tag.trim().toLowerCase()} validate={tag => tag.length <= 24 ? undefined : 'Use 24 characters or fewer.'}
98
+ renderTag={tag => <>{TAG_DOTS[tag] && <span className="tags-dot" style={{ '--dot': TAG_DOTS[tag] }} aria-hidden="true" />}{tag}</>} />
99
+ </Field>
100
+ <Field label="Share with">
101
+ <TagInput value={recipients} onValueChange={setRecipients} noun={['person', 'people']} placeholder="name@northwind.example"
102
+ validate={tag => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(tag) ? undefined : `${tag} isn’t an email address.`}
103
+ onAdd={async tag => { await tagWait(500); return tag.endsWith('@northwind.example') ? { ok: true, value: tag.toLowerCase() } : { ok: false, error: `Only people at northwind.example can be invited. ${tag} wasn’t added.` }; }}
104
+ onRemove={async () => { await tagWait(300); return { ok: true }; }} />
105
+ </Field>
106
+ <Field label="Skills">
107
+ <TagInput value={skills} onValueChange={setSkills} noun={['skill', 'skills']} disabled />
108
+ </Field>
109
+ <div>
110
+ <p className="tags-caption">Standalone tags</p>
111
+ <div className="tags-row"><Tag>Figma</Tag><Tag>Research</Tag><Tag onRemove={() => {}}>Writing</Tag></div>
112
+ </div>
113
+ </div>;
114
+ }
115
+ ```
116
+ <!-- /example -->
@@ -0,0 +1,69 @@
1
+ # Terminal
2
+
3
+ A command's output as it runs, with how it ended. Coding agents run `npm test`, `git status` or a migration; the person watches the output arrive, then reads the exit. `CodeBlock streaming` is for code being written; a process also has stderr, an exit status, a duration and Stop.
4
+
5
+ **Elevation: CodeBlock's frame, with a process's states.**
6
+ - **The frame** is flush on the ground with a 1px `line` rim, mono, no shadow, named by its command (`$ npm test`).
7
+ - **Running** isn't the record yet: a dashed `line-control` rule down the left, "Running", and Stop (secondary, level 1).
8
+ - **Finished**, the rule goes solid with one afterglow and the exit is said in words: "Exited with 0 · 4.2s" with a check, "Exited with 1 · 4.2s" with a critical mark (the edge stays neutral; Distinctiveness, CM3), or "Stopped by you".
9
+ - **stderr lines** carry a critical mark in the gutter and say "stderr" to screen readers.
10
+ - **"Jump to latest output"** floats at level 3 when you scroll up while it runs, as in `Conversation`.
11
+
12
+ **Accessibility.** A `figure` with a focusable `log` region that isn't live, so output isn't read line by line; completion is announced once ("npm test exited with 1 after 4.2 seconds."). Tab reaches the output, Stop and Copy; arrows scroll; Escape in the output stops a running command when `stopOnEscape` is set.
13
+
14
+ **ANSI.** With `ansi` (default), red, yellow and green map to `critical`, `caution` and `positive`, blue and cyan to `accent-ink`, dim to `ink-muted`, bold to 600; everything else is stripped. The status inks clear 4.5:1 as text on `surface-ground` in both schemes (lowest: `positive` in daylight, 5.31). A carriage return overwrites its line, so progress bars settle on their last state.
15
+
16
+ **Props:** `command`, `output` (a string, or `{ text, stream?: 'stdout' | 'stderr' }[]`), `status` (`'running' | 'exited' | 'stopped'`), `exitCode`, `duration`, `onStop`, `stopOnEscape`, `maxLines` (default 400; earlier lines behind "Show N earlier lines"), `ansi`, `copyable`.
17
+
18
+ **You provide:** the chunks as they arrive, the status, and the exit code and duration when it ends.
19
+
20
+ ```tsx
21
+ <Terminal command="npm test" output={chunks} status={run.status} exitCode={run.code} duration={run.ms}
22
+ onStop={run.status === 'running' ? stop : undefined} />
23
+ ```
24
+
25
+ - Do keep stderr marked as stderr; it's how a failing line is found.
26
+ - Do say the exit, even when it's 0.
27
+ - Don't use it for code the agent is writing (that's `CodeBlock streaming`).
28
+
29
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
30
+ ## Example
31
+
32
+ `Terminal` as first used in its preview:
33
+
34
+ ```tsx
35
+ import { Terminal } from 'altitude-design';
36
+
37
+ <Terminal command="npm test" output={testRun} status="exited" exitCode={1} duration={4200} />
38
+ ```
39
+
40
+ The full preview, as rendered in Storybook and checked in CI (`tm` is the preview page's own layout class, not Altitude's):
41
+
42
+ ```tsx
43
+ import * as React from 'react';
44
+ import { Terminal } from 'altitude-design';
45
+
46
+ const E = String.fromCharCode(27);
47
+ const testRun = [
48
+ { text: '> altitude@0.2.1 test\n> vitest run\n\n' },
49
+ { text: E + '[32m ✓' + E + '[0m src/format.test.ts ' + E + '[2m(12 tests)' + E + '[0m\n' },
50
+ { text: E + '[32m ✓' + E + '[0m src/tokens.test.ts ' + E + '[2m(31 tests)' + E + '[0m\n' },
51
+ { text: E + '[31m ✗' + E + '[0m src/billing.test.ts > refunds an annual plan\n' },
52
+ { text: ' AssertionError: expected 240 to equal 200\n', stream: 'stderr' },
53
+ { text: '\n' + E + '[1m Tests' + E + '[0m ' + E + '[31m1 failed' + E + '[0m | ' + E + '[32m43 passed' + E + '[0m (44)\n' },
54
+ ];
55
+ export default function App() {
56
+ const [n, setN] = React.useState(0);
57
+ const [stopped, setStopped] = React.useState(false);
58
+ React.useEffect(() => { if (stopped || n >= 30) return; const t = setTimeout(() => setN(n + 1), 140); return () => clearTimeout(t); }, [n, stopped]);
59
+ const lines = Array.from({ length: n }, (_, i) => `[migrate] applying 2026_10_${String(i + 1).padStart(2, '0')}_add_index.sql … ok\n`).join('');
60
+ return (
61
+ <div className="tm">
62
+ <Terminal command="npm test" output={testRun} status="exited" exitCode={1} duration={4200} />
63
+ <Terminal command="npm run migrate" output={lines} status={stopped ? 'stopped' : n >= 30 ? 'exited' : 'running'} exitCode={0} duration={n * 140}
64
+ onStop={() => setStopped(true)} stopOnEscape />
65
+ </div>
66
+ );
67
+ }
68
+ ```
69
+ <!-- /example -->
@@ -0,0 +1,110 @@
1
+ # ThreadList
2
+
3
+ Conversations with an assistant, beside the thread: new conversation, grouped by date, search, rename, delete, and for agents that run in the background, which ones are still working, which finished while you were away and which are waiting for you. It's built from `Sidebar`'s parts.
4
+
5
+ **Elevation: a record of conversations, with live rows.**
6
+ - **Rows** are destinations: flush at rest, lifting one level when pointed at; the current thread sits on the Sidebar's raised pill (`aria-current="page"`).
7
+ - **Working** isn't pending (nothing you sent is in flight), so it never hovers: a plate with a steady accent lamp says "Working". No spinner.
8
+ - **Needs you** ("Needs approval", "Has a question") is a caution plate with a lamp. A row can't lift into a card inside a nav without breaking the list, so the word carries it, and it's announced once.
9
+ - **Finished, unread** is a dot and the title in 600. **Failed** and **stopped** are plates in words.
10
+ - **Renaming** opens a field (a well) in the row. **Deleting** leaves a `RemovalReceipt` in the row's slot with Undo.
11
+ - **New conversation** is secondary: the composer's Send keeps the view's one primary.
12
+
13
+ **Accessibility.** A landmark (the Sidebar's `aria-label`, "Conversations") holding lists of links. Each row's name includes its state: "Q3 pricing comparison, Needs approval, 2 minutes ago". F2 renames the focused row (Enter saves, Escape cancels, focus returns to it); Delete deletes it; the row menu is named "More actions for Q3 pricing comparison". ⌘K / Ctrl+K focuses search when `searchable`. A background thread that starts needing you is announced politely once; finishing isn't announced.
14
+
15
+ **Parts:** `ThreadList` (`searchable`, `onSearch`, `searchLabel`); `ThreadListGroup` (`label`); `ThreadListItem` (`href`, `title`, `updatedAt`, `status`: `'idle' | 'working' | 'needs-input' | 'failed' | 'stopped'`, `needs`: `'approval' | 'question'`, `unread`, `current`, `onSelect`, `onRename`, `onDelete`, `onUndoDelete`, `onDeleted`, `onArchive`); `ThreadListNew`. Helper: `groupByDate(threads, getDate, now?, locale?)` → Today, Yesterday, Previous 7 days, Previous 30 days, then months.
16
+
17
+ **You provide:** the threads and their states, what selecting, renaming and deleting do. Keep a deleted thread until `onDeleted` (when its receipt closes), so Undo can bring it back.
18
+
19
+ ```tsx
20
+ <Sidebar aria-label="Conversations">
21
+ <SidebarHeader><ThreadListNew onClick={newThread} /></SidebarHeader>
22
+ <SidebarContent>
23
+ <ThreadList searchable onSearch={setQuery}>
24
+ {groupByDate(threads, (t) => t.updatedAt).map((g) => (
25
+ <ThreadListGroup key={g.label} label={g.label}>
26
+ {g.threads.map((t) => <ThreadListItem key={t.id} href={`/c/${t.id}`} current={t.id === id} title={t.title} updatedAt={t.updatedAt}
27
+ status={t.status} unread={t.unread} onRename={(name) => rename(t.id, name)} onDelete={() => remove(t.id)} onUndoDelete={() => restore(t.id)} onDeleted={() => drop(t.id)} />)}
28
+ </ThreadListGroup>
29
+ ))}
30
+ </ThreadList>
31
+ </SidebarContent>
32
+ </Sidebar>
33
+ ```
34
+
35
+ - Do say every state in words; the lamp only repeats it.
36
+ - Do keep deleted threads until the receipt closes.
37
+ - Don't show a spinner or make a working row hover.
38
+
39
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
40
+ ## Example
41
+
42
+ `ThreadList` as first used in its preview:
43
+
44
+ ```tsx
45
+ import { ThreadList, ThreadListGroup, ThreadListItem, groupByDate } from 'altitude-design';
46
+
47
+ <ThreadList searchable onSearch={setQ}>
48
+ {groupByDate(shown, (t) => t.at, now).map((g) => (
49
+ <ThreadListGroup key={g.label} label={g.label}>
50
+ {g.threads.map((t) => (
51
+ <ThreadListItem key={t.id} href={'#/c/' + t.id} title={t.title} updatedAt={t.at} status={t.status} needs={t.needs} unread={t.unread} current={t.id === at}
52
+ onSelect={(e) => { e.preventDefault(); setAt(t.id); set(t.id, { unread: false }); }}
53
+ onRename={(title) => set(t.id, { title })} onDelete={() => {}} onUndoDelete={() => {}} onDeleted={() => setThreads((ts) => ts.filter((x) => x.id !== t.id))} />
54
+ ))}
55
+ </ThreadListGroup>
56
+ ))}
57
+ </ThreadList>
58
+ ```
59
+
60
+ The full preview, as rendered in Storybook and checked in CI (`tl`, `page` are the preview page's own layout classes, not Altitude's):
61
+
62
+ ```tsx
63
+ import * as React from 'react';
64
+ import { AppShell, Sidebar, SidebarContent, SidebarHeader, ThreadList, ThreadListGroup, ThreadListItem, ThreadListNew, groupByDate } from 'altitude-design';
65
+
66
+ const now = Date.now(), min = 60000, hour = 3600000, day = 86400000;
67
+ // Earlier threads hang off the start of today, not off now, so each stays in its group just after midnight.
68
+ const today = new Date(now).setHours(0, 0, 0, 0);
69
+ const seed = [
70
+ { id: 'a', title: 'Q3 pricing comparison', at: now - 2 * min, status: 'needs-input', needs: 'approval' },
71
+ { id: 'b', title: 'Refactor the billing module', at: Math.max(now - 9 * min, today), status: 'working' },
72
+ { id: 'c', title: 'Summarise incident #4821', at: Math.max(now - 40 * min, today), unread: true },
73
+ { id: 'd', title: 'Draft the release note', at: today - 9 * hour },
74
+ { id: 'e', title: 'Migrate the users table', at: today - 11 * hour, status: 'failed' },
75
+ { id: 'f', title: 'Onboarding email ideas', at: today - 3 * day - 12 * hour },
76
+ { id: 'g', title: 'Competitor research', at: today - 11 * day - 12 * hour, status: 'stopped' },
77
+ ];
78
+ export default function App() {
79
+ const [threads, setThreads] = React.useState(seed);
80
+ const [at, setAt] = React.useState('b');
81
+ const [q, setQ] = React.useState('');
82
+ const shown = threads.filter((t) => t.title.toLowerCase().includes(q.toLowerCase()));
83
+ const set = (id, patch) => setThreads((ts) => ts.map((t) => (t.id === id ? { ...t, ...patch } : t)));
84
+ return (
85
+ <div className="tl">
86
+ <AppShell sidebar={
87
+ <Sidebar aria-label="Conversations">
88
+ <SidebarHeader><ThreadListNew onClick={() => { const id = String(Date.now()); setThreads((ts) => [{ id, title: 'New conversation', at: Date.now() }, ...ts]); setAt(id); }} /></SidebarHeader>
89
+ <SidebarContent>
90
+ <ThreadList searchable onSearch={setQ}>
91
+ {groupByDate(shown, (t) => t.at, now).map((g) => (
92
+ <ThreadListGroup key={g.label} label={g.label}>
93
+ {g.threads.map((t) => (
94
+ <ThreadListItem key={t.id} href={'#/c/' + t.id} title={t.title} updatedAt={t.at} status={t.status} needs={t.needs} unread={t.unread} current={t.id === at}
95
+ onSelect={(e) => { e.preventDefault(); setAt(t.id); set(t.id, { unread: false }); }}
96
+ onRename={(title) => set(t.id, { title })} onDelete={() => {}} onUndoDelete={() => {}} onDeleted={() => setThreads((ts) => ts.filter((x) => x.id !== t.id))} />
97
+ ))}
98
+ </ThreadListGroup>
99
+ ))}
100
+ </ThreadList>
101
+ </SidebarContent>
102
+ </Sidebar>
103
+ }>
104
+ <div className="page">The conversation “{threads.find((t) => t.id === at)?.title}” is open here.</div>
105
+ </AppShell>
106
+ </div>
107
+ );
108
+ }
109
+ ```
110
+ <!-- /example -->
@@ -0,0 +1,63 @@
1
+ # TimeInput
2
+
3
+ A time of day: when a scheduled report is sent, quiet hours, business hours, a maintenance window. It pairs with `DatePicker` when you need both.
4
+
5
+ **Elevation.** It's an opening, built like `DatePicker`'s typed field: the same segments in the same well. It follows the DatePicker rule for completeness: **it fills only when every segment holds a value**. A partial time ("09:––") is still a hole with its back wall showing, and the floor rises flush once the whole time is there. A time outside `min` and `max` (or `invalid`) drops it to a trench on `critical-soft`. A time zone's short name ("GMT+1") sits after the time in `ink-muted`.
6
+
7
+ **Typing.** Type digits and they move on by themselves once no further digit could fit ("9" in the hour moves to the minutes, while "1" waits for a second digit). Up and Down change a segment (minutes by `step`), Left and Right move between segments, Backspace clears, and ":" moves on. On a 12-hour clock, "a" or "p" sets AM or PM, and Up or Down flips it. The clock follows the locale unless `hourCycle` says otherwise.
8
+
9
+ **Value.** Always "HH:mm" on a 24-hour clock ("15:45"), whatever is shown, or null until the time is complete. `timeZone` only labels the time; it doesn't convert it.
10
+
11
+ **Accessibility.** Segmented [spinbuttons](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) ("Hour", "Minute", "AM/PM", each with its value or "Empty") in a `role="group"`, as in `DatePicker`. The time zone describes each segment. Under forced colours it's dashed while empty or partial and solid when complete. Under `contrast="more"` it gets the well's hard inner edge.
12
+
13
+ **Props:** `value`, `defaultValue`, `onValueChange` (`"HH:mm" | null`); `step` (minutes, default 1); `hourCycle` (`'auto' | 'h12' | 'h23'`); `min`, `max` ("HH:mm"); `timeZone`; `locale`; `invalid`; `disabled`; `required`; `size`; `name` (submits the value with a form).
14
+
15
+ **You provide:** a label (with `Field`), the allowed range, and the time zone when people in several zones see it.
16
+
17
+ ```tsx
18
+ <Field label="Send the report at">
19
+ <TimeInput value="09:30" onValueChange={setTime} step={15} hourCycle="auto" timeZone="Europe/Berlin" />
20
+ </Field>
21
+ ```
22
+
23
+ - Do show the time zone whenever the time isn't in the viewer's own.
24
+ - Do set `step` to the granularity you actually schedule at (15 minutes, not 1).
25
+ - Don't use `<input type="time">`; it looks different in every browser and ignores the scheme.
26
+ - Don't offer a long Select of times when people know the time they want.
27
+
28
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
29
+ ## Example
30
+
31
+ `TimeInput` as first used in its preview:
32
+
33
+ ```tsx
34
+ import { TimeInput } from 'altitude-design';
35
+
36
+ <TimeInput value={report} onValueChange={setReport} step={15} hourCycle="h12" locale="en-US" timeZone="Europe/Berlin" />
37
+ ```
38
+
39
+ The full preview, as rendered in Storybook and checked in CI (`col`, `ti`, `cap` are the preview page's own layout classes, not Altitude's):
40
+
41
+ ```tsx
42
+ import * as React from 'react';
43
+ import { Field, TimeInput } from 'altitude-design';
44
+
45
+ export default function App() {
46
+ const [report, setReport] = React.useState('09:30');
47
+ const [quiet, setQuiet] = React.useState(null);
48
+ const [open, setOpen] = React.useState('08:00');
49
+ return (
50
+ <div className="col">
51
+ <div className="ti">
52
+ <Field label="Send the report at" hint={report ? `Stored as ${report}` : 'Not set'}><TimeInput value={report} onValueChange={setReport} step={15} hourCycle="h12" locale="en-US" timeZone="Europe/Berlin" /></Field>
53
+ <Field label="Quiet hours start" hint="24-hour clock"><TimeInput value={quiet} onValueChange={setQuiet} hourCycle="h23" /></Field>
54
+ <Field label="Opens at" hint="Between 06:00 and 12:00" error={open && (open < '06:00' || open > '12:00') ? 'Choose a time between 06:00 and 12:00.' : undefined}>
55
+ <TimeInput value={open} onValueChange={setOpen} min="06:00" max="12:00" hourCycle="h23" />
56
+ </Field>
57
+ </div>
58
+ <p className="cap">Type digits (they move on by themselves), or use Up and Down; minutes step by 15 in the first field. "a" or "p" sets AM or PM. The field fills only once the whole time is there.</p>
59
+ </div>
60
+ );
61
+ }
62
+ ```
63
+ <!-- /example -->
@@ -0,0 +1,87 @@
1
+ # Timeline
2
+
3
+ Events in order: activity on a record ("Maya changed status to Done"), workspace activity, version history, webhook delivery attempts, an order's history, background agent runs.
4
+
5
+ **Elevation: the ground is the record, literally.** The events stand on a ground line turned upright, the same 1px `ink-muted` rule that `Stepper`, the `Drilldown` staircase and a chart's baseline stand on. Each event is a flush entry stamped onto it, with its icon on a flush plate on the line. Past events are never raised cards: a raised card claims to be live.
6
+ - **New events** arrive lifted and settle with the afterglow, at most four starting at once. "2 new events" is announced.
7
+ - **An event you caused** (`status="pending"`) hovers ("Sending…") until it lands and joins the record. A failed one drops to a trench with "Couldn't send" and Retry.
8
+ - **An event that links** (`href`) lifts when pointed at.
9
+ - **Detail** (a `DiffView`, a payload) opens behind a Details disclosure as a raised card, as an `Accordion` lid does.
10
+
11
+ With `groupBy="day"` (the default), each day starts with a heading ("Today", "Yesterday", "Monday", "March 4").
12
+
13
+ **Accessibility.** A `section` named by `aria-label`, holding an ordered list. Each event is one sentence with its `<time>` (a `RelativeTime` with its exact time in a tooltip). The line and markers are hidden from assistive technology, and day headings are real headings (`headingLevel`, default 3). Details is a disclosure button. Under forced colours the line is `CanvasText`, a pending event is dotted, and a failed one has a thick dash. Under `contrast="more"` the line is 2px.
14
+
15
+ **Props.** `Timeline`: `groupBy` (`'day' | 'none'`), `headingLevel`, `locale`, `aria-label`, and `TimelineItem` children (give each a key). `TimelineItem`: `time`, `icon`, `status` (`'pending' | 'committed' | 'failed'`), `onRetry`, `detail`, `href`, and the sentence as children.
16
+
17
+ **You provide:** the events, each as a sentence (who did what to what), with its time.
18
+
19
+ ```tsx
20
+ <Timeline aria-label="Activity">
21
+ {events.map((e) => (
22
+ <TimelineItem key={e.id} time={e.at} icon={iconFor(e)} status={e.status} detail={e.diff && <DiffView … />}>
23
+ <strong>{e.actor}</strong> changed status to <Badge>Done</Badge>
24
+ </TimelineItem>
25
+ ))}
26
+ </Timeline>
27
+ ```
28
+
29
+ - Do write each event as a sentence a screen reader can read on its own.
30
+ - Do keep keys stable, so only new events glow.
31
+ - Don't raise past events into cards.
32
+ - Don't use it for steps still to come; that's a `Stepper`.
33
+
34
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
35
+ ## Example
36
+
37
+ `Timeline` as first used in its preview:
38
+
39
+ ```tsx
40
+ import { Timeline, TimelineItem } from 'altitude-design';
41
+
42
+ <Timeline aria-label="Activity">
43
+ {events.map((e) => <TimelineItem key={e.id} time={e.at} icon={<I d={icons[e.icon]} />} status={e.status} href={e.href} detail={e.detail}
44
+ onRetry={() => setEvents((xs) => xs.map((x) => (x.id === e.id ? { ...x, status: 'committed' } : x)))}>{e.text}</TimelineItem>)}
45
+ </Timeline>
46
+ ```
47
+
48
+ The full preview, as rendered in Storybook and checked in CI (`tl`, `col`, `row`, `cap` are the preview page's own layout classes, not Altitude's):
49
+
50
+ ```tsx
51
+ import * as React from 'react';
52
+ import { Badge, Button, Input, Timeline, TimelineItem } from 'altitude-design';
53
+
54
+ // Anchored to the calendar day, so "Today" and "Yesterday" hold at any hour (just after midnight too).
55
+ const ago = (m) => { const start = new Date(); start.setHours(0, 0, 0, 0); return new Date(Math.max(Date.now() - m * 60000, start.getTime() + 1000)); };
56
+ const yesterdayAt = (h) => { const d = new Date(); d.setDate(d.getDate() - 1); d.setHours(h, 0, 0, 0); return d; };
57
+ const I = ({ d }) => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d={d} /></svg>;
58
+ const icons = { status: 'M5 12.5l4.5 4.5L19 7.5', comment: 'M4 5h16v11H8l-4 4z', assign: 'M16 19v-1a4 4 0 0 0-8 0v1M12 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6' };
59
+ export default function App() {
60
+ const [events, setEvents] = React.useState([
61
+ { id: 'e3', at: ago(14), icon: 'status', text: <><strong>Maya</strong> changed status to <Badge tone="positive">Done</Badge></> },
62
+ { id: 'e2', at: ago(95), icon: 'assign', text: <><strong>Theo</strong> assigned this to <strong>Maya</strong></>, href: '#/people/maya' },
63
+ { id: 'e1', at: yesterdayAt(12), icon: 'comment', text: <><strong>Ada</strong> commented</>, detail: <p style={{ margin: 0 }}>“Can we ship this before the release freeze on Friday?”</p> },
64
+ ]);
65
+ const [text, setText] = React.useState('');
66
+ const send = (fail) => {
67
+ const id = 'n' + Date.now();
68
+ setEvents((xs) => [{ id, at: new Date(), icon: 'comment', status: 'pending', text: <><strong>You</strong> commented “{text || 'Looks good'}”</> }, ...xs]);
69
+ setText('');
70
+ setTimeout(() => setEvents((xs) => xs.map((x) => (x.id === id ? { ...x, status: fail ? 'failed' : 'committed' } : x))), 1200);
71
+ };
72
+ return (
73
+ <div className="tl">
74
+ <Timeline aria-label="Activity">
75
+ {events.map((e) => <TimelineItem key={e.id} time={e.at} icon={<I d={icons[e.icon]} />} status={e.status} href={e.href} detail={e.detail}
76
+ onRetry={() => setEvents((xs) => xs.map((x) => (x.id === e.id ? { ...x, status: 'committed' } : x)))}>{e.text}</TimelineItem>)}
77
+ </Timeline>
78
+ <div className="col">
79
+ <Input value={text} onChange={(ev) => setText(ev.target.value)} placeholder="Add a comment" aria-label="Comment" />
80
+ <div className="row"><Button variant="primary" onClick={() => send(false)}>Comment</Button><Button onClick={() => send(true)}>Comment (fails)</Button></div>
81
+ <p className="cap">Your comment hovers until it lands, then settles into the record; one that fails drops to a trench with Retry.</p>
82
+ </div>
83
+ </div>
84
+ );
85
+ }
86
+ ```
87
+ <!-- /example -->
@@ -0,0 +1,54 @@
1
+ # Toaster
2
+
3
+ Confirms what just happened, in a depth stack: the newest toast floats in front at level 4 and older ones sink behind it.
4
+
5
+ Call `toast()` from anywhere; render `Toaster` once inside `AltitudeProvider`. A new toast rises 24px into the front while the others each step back: 10px up, 5% smaller, more transparent and one shadow level lower, with their text hidden. Hovering or focusing the stack fans it out to full height and pauses every timer. Dismissed toasts sink and fade. `critical` toasts use `role="alert"`.
6
+
7
+ **On touch.** A tap on the stack fans it out and pauses the timers until a tap outside folds it, as hover does for a mouse. A horizontal swipe dismisses a toast.
8
+
9
+ **You provide:** `toast({ title, description?, tone?, duration?, action?, announce? })` or just `toast('Changes saved')`. `duration` defaults to 5000ms; 0 keeps it until dismissed. Returns an id for `toast.dismiss(id)`. `announce: false` shows a toast without announcing it, for words a screen reader has already heard where they happened: `ItemGroup`'s Undo toast repeats what the receipt in the list already announced.
10
+
11
+ - Do write the title as the outcome in past tense: "Domain verified".
12
+ - Don't use toasts for errors the person must fix in place; show those in the form with Field.
13
+
14
+ While a modal owns the document, toasts retain their visual position above the scrim, but their controls are inert and expiry timers pause with their remaining lifetime intact. Notification text is routed to a noninteractive live region inside the active modal, and the external live region is suppressed to avoid duplicate announcements. Timers and controls resume after final release. Essential modal errors belong inside that modal.
15
+
16
+ When an item leaves a list, its result is shown first in the item's own slot ([RemovalReceipt](../RemovalReceipt/README.md)); once that slot has closed, a toast carries Undo, as in the [Actions in place recipe](../../visual/ActionsInPlace.md). A timed toast may supplement recovery, but must never be its only route: keep a durable Archived/Restore path after a brief Undo opportunity expires. Withdraw a toast's Undo (`toast.dismiss(id)`) as soon as a newer revision makes it invalid.
17
+
18
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
19
+ ## Example
20
+
21
+ `Toaster` as first used in its preview:
22
+
23
+ ```tsx
24
+ import { Toaster } from 'altitude-design';
25
+
26
+ <Toaster />
27
+ ```
28
+
29
+ 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):
30
+
31
+ ```tsx
32
+ import * as React from 'react';
33
+ import { Button, Toaster, toast } from 'altitude-design';
34
+
35
+ const msgs = [
36
+ { title: 'Deployment queued', description: 'northwind-web · main@8dc01b2' },
37
+ { title: 'Domain verified', description: 'acme.dev now points here.', tone: 'positive' },
38
+ { title: 'Build failed', description: 'Missing DATABASE_URL.', tone: 'critical', action: { label: 'Retry', onClick: () => {} } },
39
+ ];
40
+ export default function App() {
41
+ React.useEffect(() => { msgs.forEach((m, i) => setTimeout(() => toast({ ...m, duration: 60000 }), 300 + i * 450)); }, []);
42
+ return (
43
+ <div className="col">
44
+ <div className="row">
45
+ <Button onClick={() => toast({ title: 'Changes saved', tone: 'positive' })}>Toast</Button>
46
+ <Button variant="ghost" onClick={() => toast({ title: 'Running low on storage', description: '92% used', tone: 'caution' })}>Warning</Button>
47
+ </div>
48
+ <p className="cap" style={{ maxWidth: 300 }}>Hover or tap the stack to fan it out; timers pause while you read; swipe a toast away.</p>
49
+ <Toaster />
50
+ </div>
51
+ );
52
+ }
53
+ ```
54
+ <!-- /example -->