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,85 @@
1
+ # Toggle and ToggleGroup
2
+
3
+ Buttons that stay on: bold and italic in a formatting bar, a list/board/calendar view switch, "Show archived", thumbs up or down on an AI answer.
4
+
5
+ **Elevation: on is a choice, not a held press.** Pressing a button returns it to the ground, and loading holds it there, so an "on" button drawn at ground level would look like it's loading. A toggle that's on is a choice, so it's built like the Switch: its face is cut into a shallow well (`well`) with `accent-soft` poured in and its label in `accent-ink`. Off is the slab it started as: a ghost (ground) or `secondary` (level 1). Turning on sinks on `ease-sink`, and turning off rises on `ease-rise`. Pointing at an on toggle previews off (feedforward): the well lightens toward the raised face it would become. This is the one exception to "never style a button as sunk" (see [Elevation](../../visual/Elevation.md), "Rules").
6
+
7
+ **ToggleGroup.**
8
+ - `type="single"` is one choice: segmented `Tabs` without panels, built with the same classes. The options sit in a well and one raised pill glides between them. It's a radio group, so arrow keys move and choose.
9
+ - `type="multiple"` is a row of Toggles, each with `aria-pressed`. Arrow keys move and Space toggles.
10
+
11
+ Either way the group is **one tab stop**, and returning to it lands on the item you left. Inside a `Toolbar`, a ToggleGroup leaves the arrow keys to the toolbar, so the whole bar is one tab stop.
12
+
13
+ **Accessibility.** A Toggle is a [toggle button](https://www.w3.org/WAI/ARIA/apg/patterns/button/): its state is `aria-pressed`, and **its name never changes with state** (no "Bold" → "Unbold"). Icon-only toggles need an `aria-label`. A single-choice group is a [radio group](https://www.w3.org/WAI/ARIA/apg/patterns/radio/) with `aria-checked`. Under forced colours an on toggle is a dashed border with a `Highlight` fill, and an off `secondary` toggle has a 1px border. Under `contrast="more"` the on well gets the well's hard inner edge. Under reduced motion nothing travels: the pill appears in place and the fill still changes.
14
+
15
+ **Props.** `Toggle`: `pressed`, `defaultPressed`, `onPressedChange`, `variant` (`'ghost' | 'secondary'`), and the `Button` props (`size`, `square`, `loading`, …). `ToggleGroup`: `type` (`'single' | 'multiple'`), `value`, `defaultValue`, `onValueChange`, `variant` and `size` (for `multiple`), `orientation`, `disabled`, `aria-label`. `ToggleGroupItem`: `value`.
16
+
17
+ **You provide:** a label that names the thing being toggled ("Bold", "Show archived"), not the action, and an `aria-label` on each group.
18
+
19
+ ```tsx
20
+ <Toggle aria-label="Bold" pressed={bold} onPressedChange={setBold} square><BoldIcon /></Toggle>
21
+
22
+ <ToggleGroup type="single" value={view} onValueChange={setView} aria-label="View">
23
+ <ToggleGroupItem value="list">List</ToggleGroupItem>
24
+ <ToggleGroupItem value="board">Board</ToggleGroupItem>
25
+ </ToggleGroup>
26
+
27
+ <ToggleGroup type="multiple" value={marks} onValueChange={setMarks} aria-label="Text style" />
28
+ ```
29
+
30
+ - Do use a Toggle for a tool or a view that stays on; use a `Switch` for a setting that's saved.
31
+ - Do use `type="single"` for mutually exclusive views; use `Tabs` when each option has its own panel.
32
+ - Don't change a toggle's label when it turns on.
33
+ - Don't use a Toggle for an action that happens once; that's a `Button`.
34
+
35
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
36
+ ## Example
37
+
38
+ `Toggle` as first used in its preview:
39
+
40
+ ```tsx
41
+ import { Toggle } from 'altitude-design';
42
+
43
+ <Toggle variant="secondary" pressed={archived} onPressedChange={setArchived}>Show archived</Toggle>
44
+ ```
45
+
46
+ The full preview, as rendered in Storybook and checked in CI (`tg`, `bar`, `cap` are the preview page's own layout classes, not Altitude's):
47
+
48
+ ```tsx
49
+ import * as React from 'react';
50
+ import { Toggle, ToggleGroup, ToggleGroupItem } from 'altitude-design';
51
+
52
+ const Thumb = ({ down }) => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={down ? { transform: 'scaleY(-1)' } : undefined}><path d="M7 10v11H4V10zM7 10l4-7a2 2 0 0 1 3 2l-1 5h6a2 2 0 0 1 2 2.3l-1.5 7A2 2 0 0 1 17.5 21H7" /></svg>;
53
+ export default function App() {
54
+ const [view, setView] = React.useState('board');
55
+ const [marks, setMarks] = React.useState(['bold']);
56
+ const [archived, setArchived] = React.useState(false);
57
+ const [vote, setVote] = React.useState('');
58
+ return (
59
+ <div className="tg">
60
+ <div className="bar">
61
+ <ToggleGroup type="multiple" value={marks} onValueChange={setMarks} aria-label="Text style">
62
+ <ToggleGroupItem value="bold" aria-label="Bold" square><b>B</b></ToggleGroupItem>
63
+ <ToggleGroupItem value="italic" aria-label="Italic" square><i>I</i></ToggleGroupItem>
64
+ <ToggleGroupItem value="underline" aria-label="Underline" square><u>U</u></ToggleGroupItem>
65
+ </ToggleGroup>
66
+ <ToggleGroup type="single" value={view} onValueChange={setView} aria-label="View">
67
+ <ToggleGroupItem value="list">List</ToggleGroupItem>
68
+ <ToggleGroupItem value="board">Board</ToggleGroupItem>
69
+ <ToggleGroupItem value="calendar">Calendar</ToggleGroupItem>
70
+ </ToggleGroup>
71
+ <Toggle variant="secondary" pressed={archived} onPressedChange={setArchived}>Show archived</Toggle>
72
+ </div>
73
+ <div className="bar">
74
+ <span className="cap">Was this answer helpful?</span>
75
+ <ToggleGroup type="single" value={vote} onValueChange={setVote} aria-label="Rate this answer">
76
+ <ToggleGroupItem value="up" aria-label="Helpful"><Thumb /></ToggleGroupItem>
77
+ <ToggleGroupItem value="down" aria-label="Not helpful"><Thumb down /></ToggleGroupItem>
78
+ </ToggleGroup>
79
+ </div>
80
+ <p className="cap">On is a choice, built like the Switch: the face sinks into a well with the accent poured in. Pointing at an on toggle previews off. One choice (View) is segmented Tabs without panels. Each group is one tab stop; arrows move inside it.</p>
81
+ </div>
82
+ );
83
+ }
84
+ ```
85
+ <!-- /example -->
@@ -0,0 +1,106 @@
1
+ # Toolbar
2
+
3
+ A row of small actions with one tab stop: a formatting bar, a table's tools, a comment composer, a canvas's tools.
4
+
5
+ **Elevation.** The bar is settled: it lies on the ground, with a `line` rim when `bordered`. Its tools are live: ghost buttons that lift one level when pointed at and press to the ground. Latched tools (bold, a quote block) are `Toggle`s, so on is a well with the accent poured in. Groups are parted by decorative `Separator`s.
6
+
7
+ **One tab stop.** Tab reaches the bar once, Left and Right (Up and Down when vertical) move between tools, Home and End jump, and returning to the bar lands on the tool you left. A `ToggleGroup` inside a Toolbar leaves the arrow keys to the bar, so a 12-tool bar costs one Tab, not twelve.
8
+
9
+ **Overflow.** `overflow="wrap"` (default) wraps tools onto another row. `overflow="menu"` hides the tools that don't fit, from the end, and offers them in a "More tools" menu (`DropdownMenu`, level 3). There, toggles keep their state as checkable items (`menuitemcheckbox`). A separator never ends the visible row. Tools moving into the menu don't animate.
10
+
11
+ **Accessibility.** The [toolbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/): `role="toolbar"` with an `aria-label` (required) and `aria-orientation`. Icon-only tools need an `aria-label`: a development warning names any tool without one. Under forced colours a hovered or focused tool gets a 1px border and a latched one is dashed (the Toggle's fallback).
12
+
13
+ **Parts.**
14
+ - `Toolbar`: `aria-label`, `orientation` (`'horizontal' | 'vertical'`), `overflow` (`'wrap' | 'menu'`), `bordered`.
15
+ - `ToolbarGroup`: tools that belong together (`role="group"`; give it an `aria-label` if the group has a name).
16
+ - `ToolbarSeparator`: a decorative line between groups.
17
+ - `ToolbarButton`: a ghost `Button`.
18
+ - `ToolbarToggle`: a ghost `Toggle`.
19
+ - A `ToggleGroup` works inside it as it is.
20
+
21
+ **You provide:** a label for the bar and for every icon-only tool, and the actions.
22
+
23
+ ```tsx
24
+ <Toolbar aria-label="Formatting" overflow="menu">
25
+ <ToolbarGroup>
26
+ <ToolbarToggle aria-label="Bold" pressed={b} onPressedChange={setB} square><Bold /></ToolbarToggle>
27
+ <ToolbarToggle aria-label="Italic" pressed={i} onPressedChange={setI} square><Italic /></ToolbarToggle>
28
+ </ToolbarGroup>
29
+ <ToolbarSeparator />
30
+ <ToolbarButton aria-label="Insert link" onClick={link} square><Link2 /></ToolbarButton>
31
+ </Toolbar>
32
+ ```
33
+
34
+ - Do put the tools used most first: overflow takes them from the end.
35
+ - Do use a Toolbar for a set of small, related tools; a page's main actions are separate `Button`s.
36
+ - Don't put text fields inside a Toolbar: arrow keys would stop moving the caret.
37
+ - Don't give tools a raised (`secondary` or `primary`) variant; in a toolbar they're ghosts.
38
+
39
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
40
+ ## Example
41
+
42
+ `Toolbar` as first used in its preview:
43
+
44
+ ```tsx
45
+ import { ToggleGroup, ToggleGroupItem, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator, ToolbarToggle } from 'altitude-design';
46
+
47
+ <Toolbar aria-label="Formatting" overflow="menu" bordered className={className}>
48
+ <ToggleGroup type="multiple" value={marks} onValueChange={setMarks} aria-label="Text style">
49
+ <ToggleGroupItem value="bold" aria-label="Bold" square><b>B</b></ToggleGroupItem>
50
+ <ToggleGroupItem value="italic" aria-label="Italic" square><i>I</i></ToggleGroupItem>
51
+ <ToggleGroupItem value="underline" aria-label="Underline" square><u>U</u></ToggleGroupItem>
52
+ </ToggleGroup>
53
+ <ToolbarSeparator />
54
+ <ToolbarGroup aria-label="Blocks">
55
+ <ToolbarButton aria-label="Bulleted list" square><I d={list} /></ToolbarButton>
56
+ <ToolbarToggle aria-label="Quote" square pressed={quoted} onPressedChange={setQuoted}><I d={quote} /></ToolbarToggle>
57
+ <ToolbarButton aria-label="Code" square><I d={code} /></ToolbarButton>
58
+ </ToolbarGroup>
59
+ <ToolbarSeparator />
60
+ <ToolbarButton aria-label="Insert link" square><I d={link} /></ToolbarButton>
61
+ </Toolbar>
62
+ ```
63
+
64
+ The full preview, as rendered in Storybook and checked in CI (`tb`, `narrow`, `cap` are the preview page's own layout classes, not Altitude's):
65
+
66
+ ```tsx
67
+ import * as React from 'react';
68
+ import { ToggleGroup, ToggleGroupItem, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator, ToolbarToggle } from 'altitude-design';
69
+
70
+ const I = ({ d }) => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={d} /></svg>;
71
+ const link = 'M10 14a4 4 0 0 0 6 0l3-3a4 4 0 0 0-6-6l-1 1M14 10a4 4 0 0 0-6 0l-3 3a4 4 0 0 0 6 6l1-1';
72
+ const list = 'M9 6h11M9 12h11M9 18h11M4 6h.01M4 12h.01M4 18h.01';
73
+ const quote = 'M7 7h4v6H5V9zM15 7h4v6h-6V9zM9 13l-2 4M17 13l-2 4';
74
+ const code = 'M8 8l-4 4 4 4M16 8l4 4-4 4';
75
+ function Bar({ className }) {
76
+ const [marks, setMarks] = React.useState(['bold']);
77
+ const [quoted, setQuoted] = React.useState(false);
78
+ return (
79
+ <Toolbar aria-label="Formatting" overflow="menu" bordered className={className}>
80
+ <ToggleGroup type="multiple" value={marks} onValueChange={setMarks} aria-label="Text style">
81
+ <ToggleGroupItem value="bold" aria-label="Bold" square><b>B</b></ToggleGroupItem>
82
+ <ToggleGroupItem value="italic" aria-label="Italic" square><i>I</i></ToggleGroupItem>
83
+ <ToggleGroupItem value="underline" aria-label="Underline" square><u>U</u></ToggleGroupItem>
84
+ </ToggleGroup>
85
+ <ToolbarSeparator />
86
+ <ToolbarGroup aria-label="Blocks">
87
+ <ToolbarButton aria-label="Bulleted list" square><I d={list} /></ToolbarButton>
88
+ <ToolbarToggle aria-label="Quote" square pressed={quoted} onPressedChange={setQuoted}><I d={quote} /></ToolbarToggle>
89
+ <ToolbarButton aria-label="Code" square><I d={code} /></ToolbarButton>
90
+ </ToolbarGroup>
91
+ <ToolbarSeparator />
92
+ <ToolbarButton aria-label="Insert link" square><I d={link} /></ToolbarButton>
93
+ </Toolbar>
94
+ );
95
+ }
96
+ export default function App() {
97
+ return (
98
+ <div className="tb">
99
+ <Bar />
100
+ <Bar className="narrow" />
101
+ <p className="cap">One tab stop for the whole bar; arrows move between tools. The narrow bar moves what doesn't fit into "More tools", where Quote stays a checkable item.</p>
102
+ </div>
103
+ );
104
+ }
105
+ ```
106
+ <!-- /example -->
@@ -0,0 +1,41 @@
1
+ # Tooltip
2
+
3
+ Names an icon-only control or adds a short hint; it floats at level 3 above its anchor.
4
+
5
+ It opens after `delay` (450ms) on hover or immediately on keyboard focus, rising 4px from the anchor while its shadow grows from `elev-1` to `elev-3` (`dur-travel-3`). After one tooltip closes, neighbours open instantly for 400ms, so moving along a toolbar doesn't make you wait twice. Escape closes it.
6
+
7
+ **On a narrow screen.** It opens where its `side` and `align` put it, then shifts sideways to stay 8px inside the screen, and it's never wider than the screen less 16px.
8
+
9
+ **You provide:** `content` (a few words, optionally a shortcut), one focusable child, optionally `side="bottom"`.
10
+
11
+ - Do use `side="right"` for a rail of icons (a collapsed `Sidebar`), where a tooltip above would cover the next icon.
12
+ - Don't put anything interactive in a tooltip, and don't hide essential information in one.
13
+
14
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
15
+ ## Example
16
+
17
+ `Tooltip` as first used in its preview:
18
+
19
+ ```tsx
20
+ import { Button, Tooltip } from 'altitude-design';
21
+
22
+ <Tooltip content="Bold ⌘B" defaultOpen><Button square variant="ghost" aria-label="Bold"><b>B</b></Button></Tooltip>
23
+ ```
24
+
25
+ The full preview, as rendered in Storybook and checked in CI (`row`, `cap` are the preview page's own layout classes, not Altitude's):
26
+
27
+ ```tsx
28
+ import { Button, Tooltip } from 'altitude-design';
29
+
30
+ export default function App() {
31
+ return (
32
+ <div className="row">
33
+ <Tooltip content="Bold ⌘B" defaultOpen><Button square variant="ghost" aria-label="Bold"><b>B</b></Button></Tooltip>
34
+ <Tooltip content="Italic ⌘I"><Button square variant="ghost" aria-label="Italic"><i style={{ fontFamily: 'serif', fontSize: 17 }}>I</i></Button></Tooltip>
35
+ <Tooltip content="Underline ⌘U"><Button square variant="ghost" aria-label="Underline"><u>U</u></Button></Tooltip>
36
+ <span className="cap" style={{ marginLeft: 12 }}>Hover one, then slide to the next: no second delay.</span>
37
+ </div>
38
+ );
39
+ }
40
+ ```
41
+ <!-- /example -->
@@ -0,0 +1,73 @@
1
+ # Tour
2
+
3
+ A few steps pointing at parts of the interface, for onboarding or a feature launch: "Projects moved here", "New: filter by owner". Keep it short and skippable.
4
+
5
+ **Elevation: a note in the air, not a summit.**
6
+ - **One step at a time floats at level 3**, a `Popover`-built card beside its target, with "Step 2 of 4", a heading, the body, progress dots, Skip tour, Back and Next (Done on the last step).
7
+ - **The target takes a 2px `accent-ink` ring**, as `DataGrid`'s active cell does. It's a ring, not a lift, because the target may not be something you press.
8
+ - **It never dims the page.** Dimming means the page is receding, which is the summit's privilege (level 5, and only one thing sits there). Tours that dim with a cut-out spotlight block the very thing they point at.
9
+ - **It isn't modal**: the target stays usable.
10
+
11
+ **Motion.** Each step rises at its target on `ease-rise`. Moving on lifts the step off and the next one rises at its own target, so nothing flies across the screen. Under reduced motion the steps cross-fade.
12
+
13
+ **Accessibility.** A non-modal [dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) (`role="dialog"`, `aria-modal="false"`) named by the step's heading. The target is `aria-describedby` the step's body while it shows. Focus moves to the step when it opens. Tab reaches the target and back. Escape ends the tour. When the tour ends, focus returns to where it was. A target that's off screen is scrolled into view. Under forced colours the card has a 2px border and the ring is `Highlight`. Under `contrast="more"` the card gets a hard slab.
14
+
15
+ **Props:** `steps` (`{ target, title, body? }[]`, where `target` is a selector or an element); `open`, `defaultOpen`, `onOpenChange`; `onFinish(how)` (`'done'` or `'skipped'`); `onStepChange`.
16
+
17
+ **You provide:** the steps, when to start it, and remembering it's been seen (in `onFinish`).
18
+
19
+ ```tsx
20
+ <Tour open={!seen} onOpenChange={…} onFinish={markSeen} steps={[
21
+ { target: '#nav-projects', title: 'Projects moved here', body: 'All your projects are now in the sidebar.' },
22
+ { target: '[data-tour=filter-owner]', title: 'Filter by owner', body: 'Type a name to see only their projects.' },
23
+ ]} />
24
+ ```
25
+
26
+ - Do keep a tour to a few steps, and always let people skip it.
27
+ - Do remember when it's been finished or skipped.
28
+ - Don't dim the page or block the target.
29
+ - Don't use a tour to explain an interface that should explain itself.
30
+
31
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
32
+ ## Example
33
+
34
+ `Tour` as first used in its preview:
35
+
36
+ ```tsx
37
+ import { Tour } from 'altitude-design';
38
+
39
+ <Tour open={open} onOpenChange={setOpen} onFinish={(how) => setSaid(how === 'done' ? 'Tour finished.' : 'Tour skipped.')} steps={[
40
+ { target: '#tour-projects', title: 'Projects moved here', body: 'All your projects now live in one place.' },
41
+ { target: '[data-tour="owner"]', title: 'Filter by owner', body: 'Type a name to see only their projects.' },
42
+ { target: '[data-tour="new"]', title: 'Start something new', body: 'Projects can start from a template.' },
43
+ ]} />
44
+ ```
45
+
46
+ The full preview, as rendered in Storybook and checked in CI (`tr`, `bar`, `row`, `cap` are the preview page's own layout classes, not Altitude's):
47
+
48
+ ```tsx
49
+ import * as React from 'react';
50
+ import { Button, Input, Tour } from 'altitude-design';
51
+
52
+ export default function App() {
53
+ const [open, setOpen] = React.useState(false);
54
+ const [said, setSaid] = React.useState('');
55
+ return (
56
+ <div className="tr">
57
+ <div className="bar">
58
+ <Button variant="ghost" id="tour-projects">Projects</Button>
59
+ <Input size="sm" placeholder="Filter by owner" aria-label="Filter by owner" data-tour="owner" style={{ maxWidth: 220 }} />
60
+ <span style={{ marginLeft: 'auto' }}><Button variant="primary" data-tour="new">New project</Button></span>
61
+ </div>
62
+ <div className="row"><Button onClick={() => setOpen(true)}>Take the tour</Button></div>
63
+ <p className="cap">{said || 'The tour points at one thing at a time with a ring. The page isn\'t dimmed and stays usable: try typing in the filter while it\'s on that step.'}</p>
64
+ <Tour open={open} onOpenChange={setOpen} onFinish={(how) => setSaid(how === 'done' ? 'Tour finished.' : 'Tour skipped.')} steps={[
65
+ { target: '#tour-projects', title: 'Projects moved here', body: 'All your projects now live in one place.' },
66
+ { target: '[data-tour="owner"]', title: 'Filter by owner', body: 'Type a name to see only their projects.' },
67
+ { target: '[data-tour="new"]', title: 'Start something new', body: 'Projects can start from a template.' },
68
+ ]} />
69
+ </div>
70
+ );
71
+ }
72
+ ```
73
+ <!-- /example -->
@@ -0,0 +1,129 @@
1
+ # Trace
2
+
3
+ The steps an agent takes (searching, calling tools, reasoning, waiting for approval), so people can see what it did, what it's doing, what it plans, and where it failed.
4
+
5
+ **Elevation: a process, drawn as a vertical `Stepper`.** Stepper's rule, verbatim: "done steps are flush in the record with a stamped check, the current one stands raised on its wall, steps to come are imprints."
6
+ - **Done:** flush, stamped check, "Done · 820ms".
7
+ - **Running:** raised on its wall, "Running", `aria-current="step"`. **No spinner:** loading in Altitude is a held press or an imprint, never a spinner. The mark holds a small steady lamp.
8
+ - **Planned:** an imprint, "Planned". If the agent can't know its plan, leave planned steps out: steps then appear as they start, with the afterglow (at most four at once).
9
+ - **Failed:** drops to a trench with a `critical` rim, the reason, and Retry.
10
+ - **Waiting for approval:** raised like a running step, holding a `Confirmation`, the "Proposed" reading.
11
+ - **Detail** sits under its step behind `Accordion` lids: `TraceToolCall` (the tool's name; input and output in `CodeBlock`s) and `TraceReasoning` ("Thought for 2.1s", closed by default).
12
+
13
+ **Accessibility.** An ordered list (as `Stepper`), each step's state in words. Step changes are announced politely and throttled ("Done: Read 3 articles"), because an agent can finish several steps a second. Failures and approval requests are announced at once. Under forced colours a failed step is dashed thick.
14
+
15
+ **Parts:** `Trace` (`aria-label`); `TraceStep` (`title`, `status` (`'planned' | 'running' | 'done' | 'failed' | 'awaiting-approval'`), `duration` (ms), `error`, `onRetry`); `TraceToolCall` (`name`, `input`, `output`, `defaultOpen`); `TraceReasoning` (`duration`).
16
+
17
+ **Subagents.** A `Trace` inside a `TraceStep` is a nested run: a nested list behind a lid that says how it's going ("6 steps · 1 running"), open while something in it is running, waiting or failed, closed when it's all done. `TraceParallel label="Subagents"` groups steps that run at once, with a bracket and "3 in parallel · 2 of 3 done · 1 failed"; several can be raised at once, each because it's in play. `status="auto"` derives a parent from its children: running while any run, failed once the rest stop, done only when all are. Nested steps announce through the outer Trace with their run's name.
18
+
19
+ **Reasoning as it streams.** `TraceReasoning status="streaming"` says "Thinking…" on its lid; opened, the text follows its end as it arrives, behind a dashed rule (not the record yet), until the person scrolls up. Complete, it's "Thought for 12s"; `status="redacted"` says "Reasoning hidden by the provider" with nothing to open. No shimmer, pulsing dots or ticking counter.
20
+
21
+ **You provide:** the steps and their status from your agent's events.
22
+
23
+ ```tsx
24
+ <Trace aria-label="Agent run">
25
+ {run.steps.map((s) => (
26
+ <TraceStep key={s.id} title={s.title} status={s.status} duration={s.ms}
27
+ onRetry={s.status === 'failed' ? () => retry(s.id) : undefined}>
28
+ {s.tool && <TraceToolCall name={s.tool.name} input={s.tool.input} output={s.tool.output} />}
29
+ </TraceStep>
30
+ ))}
31
+ </Trace>
32
+ ```
33
+
34
+ - Do say why a step failed.
35
+ - Do put an approval request in the step that's waiting.
36
+ - Don't draw spinners or nested cards.
37
+ - Don't invent planned steps the agent hasn't planned.
38
+
39
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
40
+ ## Example
41
+
42
+ `Trace` as first used in its preview:
43
+
44
+ ```tsx
45
+ import { Confirmation, Trace, TraceReasoning, TraceStep, TraceToolCall } from 'altitude-design';
46
+
47
+ <Trace aria-label="Agent run: answer refund question">
48
+ {plan.map((s, i) => (
49
+ <TraceStep key={s.id} title={s.title} status={status(i)} duration={i < at ? s.ms : undefined}
50
+ error={failed && i === at ? 'The billing service timed out.' : undefined} onRetry={failed && i === at ? () => { setRetried(true); setFailed(false); } : undefined}>
51
+ {s.tool && <TraceToolCall name={s.tool.name} input={s.tool.input} output={i < at ? s.tool.output ?? { plan: 'annual', startedDaysAgo: 20 } : undefined} />}
52
+ {s.reasoning && i < at && <TraceReasoning duration={s.ms}>{s.reasoning}</TraceReasoning>}
53
+ {s.approval && <Confirmation state={approval} title="Send reply to Jamie Lee" consequence="Jamie will get an email confirming a full refund of €240." approveLabel="Send reply" rejectLabel="Don't send" runningLabel="Sending…" doneLabel="Sent"
54
+ onApprove={() => { setApproval('running'); setTimeout(() => setApproval('done'), 1000); }} onReject={() => setApproval('rejected')} />}
55
+ </TraceStep>
56
+ ))}
57
+ </Trace>
58
+ ```
59
+
60
+ The full preview, as rendered in Storybook and checked in CI (`col`, `tc` are the preview page's own layout classes, not Altitude's):
61
+
62
+ ```tsx
63
+ import * as React from 'react';
64
+ import { Confirmation, Trace, TraceParallel, TraceReasoning, TraceStep, TraceToolCall } from 'altitude-design';
65
+
66
+ const plan = [
67
+ { id: 's1', title: 'Search the help centre', ms: 820, tool: { name: 'search_documents', input: { query: 'refund policy annual plans', limit: 5 }, output: { results: 3 } } },
68
+ { id: 's2', title: 'Read 3 articles', ms: 1400 },
69
+ { id: 's3', title: 'Check the customer’s plan', ms: 600, fails: true, tool: { name: 'get_subscription', input: { customer: 'cus_EXAMPLE42' } } },
70
+ { id: 's4', title: 'Draft a reply', ms: 2100, reasoning: 'The customer is 20 days into an annual plan, so the 30-day full refund applies. No proration needed.' },
71
+ { id: 's5', title: 'Send the reply', approval: true },
72
+ ];
73
+ export default function App() {
74
+ const [at, setAt] = React.useState(0);
75
+ const [failed, setFailed] = React.useState(false);
76
+ const [retried, setRetried] = React.useState(false);
77
+ const [approval, setApproval] = React.useState('requested');
78
+ React.useEffect(() => {
79
+ if (failed || at >= plan.length - 1) return;
80
+ const step = plan[at];
81
+ const t = setTimeout(() => { if (step.fails && !retried) setFailed(true); else setAt(at + 1); }, step.ms);
82
+ return () => clearTimeout(t);
83
+ }, [at, failed, retried]);
84
+ const status = (i) => (i < at ? 'done' : i > at ? 'planned' : failed ? 'failed' : plan[i].approval ? (approval === 'done' ? 'done' : 'awaiting-approval') : 'running');
85
+ return (
86
+ <div className="col tc">
87
+ <Trace aria-label="Agent run: answer refund question">
88
+ {plan.map((s, i) => (
89
+ <TraceStep key={s.id} title={s.title} status={status(i)} duration={i < at ? s.ms : undefined}
90
+ error={failed && i === at ? 'The billing service timed out.' : undefined} onRetry={failed && i === at ? () => { setRetried(true); setFailed(false); } : undefined}>
91
+ {s.tool && <TraceToolCall name={s.tool.name} input={s.tool.input} output={i < at ? s.tool.output ?? { plan: 'annual', startedDaysAgo: 20 } : undefined} />}
92
+ {s.reasoning && i < at && <TraceReasoning duration={s.ms}>{s.reasoning}</TraceReasoning>}
93
+ {s.approval && <Confirmation state={approval} title="Send reply to Jamie Lee" consequence="Jamie will get an email confirming a full refund of €240." approveLabel="Send reply" rejectLabel="Don't send" runningLabel="Sending…" doneLabel="Sent"
94
+ onApprove={() => { setApproval('running'); setTimeout(() => setApproval('done'), 1000); }} onReject={() => setApproval('rejected')} />}
95
+ </TraceStep>
96
+ ))}
97
+ </Trace>
98
+ <Trace aria-label="Agent run: compare billing providers">
99
+ <TraceStep title="Plan the comparison" status="done" duration={900} />
100
+ <TraceStep title="Research pricing pages" status="auto">
101
+ <TraceParallel label="Subagents">
102
+ <TraceStep title="Stripe" status="auto">
103
+ <Trace aria-label="Stripe subagent">
104
+ <TraceStep title="Open the pricing page" status="done" duration={1200} />
105
+ <TraceStep title="Extract the fees" status="done" duration={3400} />
106
+ </Trace>
107
+ </TraceStep>
108
+ <TraceStep title="Paddle" status="auto">
109
+ <Trace aria-label="Paddle subagent">
110
+ <TraceStep title="Open the pricing page" status="done" duration={1500} />
111
+ <TraceStep title="Extract the fees" status="running" />
112
+ </Trace>
113
+ </TraceStep>
114
+ <TraceStep title="Lemon Squeezy" status="failed" error="The pricing page returned 403." onRetry={() => {}} />
115
+ </TraceParallel>
116
+ </TraceStep>
117
+ <TraceStep title="Weigh the fees" status="running">
118
+ <TraceReasoning status="streaming">{'Stripe charges 2.9% + 30¢ per card payment. Paddle is a merchant of record, so its 5% + 50¢ includes sales tax handling, which'}</TraceReasoning>
119
+ </TraceStep>
120
+ <TraceStep title="Check the provider's notes" status="done" duration={2400}>
121
+ <TraceReasoning status="redacted" />
122
+ </TraceStep>
123
+ <TraceStep title="Write the comparison" status="planned" />
124
+ </Trace>
125
+ </div>
126
+ );
127
+ }
128
+ ```
129
+ <!-- /example -->
@@ -0,0 +1,171 @@
1
+ # Tree
2
+
3
+ A hierarchy you see all at once and change in place: rename a node (F2), add a sibling or a child, carry a node to a new place (Space, or drag), collapse and expand, remove. Page trees, file explorers, nested categories, outlines.
4
+
5
+ Use `Drilldown` instead when people work one level at a time and don't need the siblings of ancestors in view (settings, a mobile hierarchy); use `Accordion` for one level of disclosure.
6
+
7
+ ```tsx
8
+ const tree = React.useRef<TreeHandle>(null);
9
+
10
+ <Tree<Page>
11
+ ref={tree}
12
+ aria-label="Pages"
13
+ items={pages}
14
+ getKey={(p) => p.id}
15
+ getLabel={(p) => p.title}
16
+ getChildren={(p) => p.children}
17
+ hasChildren={(p) => p.kind === 'folder'}
18
+ expanded={expanded} onExpandedChange={setExpanded}
19
+ selected={selected} onSelectedChange={setSelected}
20
+ renderItem={(p) => <><PageIcon />{p.title}</>}
21
+ onRename={async (p, title) => { const r = await api.rename(p.id, title, p.revision); if (r.ok) setPages(r.pages); return r; }}
22
+ onMove={async ({ id, to }) => { const r = await api.move(id, to.parentId, to.index); if (r.ok) setPages(r.pages); return r; }}
23
+ canDrop={({ to }) => to.parentId === null || isFolder(to.parentId)}
24
+ onCreate={async ({ parentId, index, name }) => { const r = await api.create(parentId, index, name); if (r.ok) setPages(r.pages); return r.ok ? { ok: true, value: r.page } : r; }}
25
+ onRemove={(p) => archiveWithUndo(p)}
26
+ />
27
+ <Button onClick={() => tree.current?.startCreate(null)}>New page</Button>
28
+ ```
29
+
30
+ The tree is controlled by `items`. Each write follows the `InlineEdit` contract: return `{ ok: true }` (for a create, `{ ok: true, value }` with the new item) after `items` reflects the change, or `{ ok: false, error }` for a known failure. A throw or malformed result shows a generic retry message. One write runs at a time.
31
+
32
+ ## Elevation
33
+
34
+ - **Nodes** are the record: flush rows on the ground, indented `space-4` per level, like `Table` rows. A node lifts one level on hover; the **selected** node is tinted `accent-soft` and raised one level.
35
+ - **Expanding** is disclosure of existing records, so it's a chevron turn (`ease-settle`), not a rise. A node whose children are loading shows an imprint row (`loadChildren`).
36
+ - **Renaming** sinks the label into a well (InlineEdit's field); an invalid or rejected name drops to a trench on `critical-soft`. A **pending** write hovers at level 2 after `dur-grace`; a **failed** one drops to a trench with a critical rim.
37
+ - **Carried** (keyboard): the node floats at level 3, tilted, and is shown where it will land. **Carried** (pointer): a floating copy follows the pointer with the count of what it carries; the top or bottom quarter of a row shows a **slot**, a thin well at the target's depth; the middle of a row opens that node into a **trench with an accent rim** (a critical rim when `canDrop` refuses it). Hovering a closed node for 600ms opens it (spring-loaded).
38
+ - **A new node** is named in a well at its insertion point; once added it settles with an afterglow.
39
+
40
+ Without height: `aria-level`, `aria-setsize`, `aria-posinset`, `aria-expanded`, `aria-selected` and `aria-busy` on every node (a carried node is announced through the live region, since `aria-pressed` isn't allowed on a tree item); moves are announced with their depth (“Moved Logo into Brand, position 1 of 3”). Forced colours: selected 1px Highlight, carried 2px, slots dashed, drop-into 3px dashed, pending dotted, failed thick dashed. `contrast="more"`: the carried node gets a 3px slab.
41
+
42
+ ## Keyboard
43
+
44
+ The APG tree view, plus editing:
45
+
46
+ | Key | |
47
+ |---|---|
48
+ | ↑ / ↓ | previous / next visible node |
49
+ | → / ← | expand, or go to the first child / collapse, or go to the parent (mirrored in RTL) |
50
+ | Home / End | first / last visible node |
51
+ | letters | type-ahead by label |
52
+ | `*` | expand every sibling |
53
+ | Enter | `onAction`, or select |
54
+ | F2 | rename (Enter saves, Escape cancels; IME composition is left alone) |
55
+ | Ctrl/Cmd+Enter, Ctrl/Cmd+Shift+Enter | add a sibling after / a child inside |
56
+ | Space | pick up (announced): ↑/↓ move between slots, → into the node above, ← out of the parent, Space or Enter drop, Escape cancel. Without `onMove`, Space selects |
57
+ | Alt+↑ / Alt+↓ | move one step without picking up |
58
+ | Delete | `onRemove`; focus moves to the next node, else the previous |
59
+
60
+ Only one node is in the Tab sequence. `TreeHandle.startCreate(parentId, index?)` and `startRename(id)` give pointer users the same actions from a toolbar.
61
+
62
+ ## Props
63
+
64
+ | Prop | |
65
+ |---|---|
66
+ | `items`, `getKey`, `getLabel`, `getChildren`, `hasChildren`, `loadChildren` | the nested data; `hasChildren` shows a chevron before children are loaded; `loadChildren` is called on the first expand |
67
+ | `expanded`, `defaultExpanded`, `onExpandedChange` | open nodes, by key |
68
+ | `selectionMode`, `selected`, `defaultSelected`, `onSelectedChange` | `'single'` (default) or `'none'` |
69
+ | `onAction`, `renderItem` | Enter's action; draw a node's label |
70
+ | `onRename`, `onMove`, `canDrop`, `onCreate`, `onRemove` | the writes, as above. `TreeTarget.index` is the place among the children once the node has left its old one |
71
+
72
+ ## You provide
73
+
74
+ The data, the writes and their concurrency guards, and the removal policy (recovery or confirmation, as in Actions in place). Multi-node carrying isn't supported.
75
+
76
+ ## Do and don't
77
+
78
+ - **Do** give pointer users a visible “New” and “Rename” (through `TreeHandle`); the keyboard shortcuts aren't discoverable on their own.
79
+ - **Do** use `canDrop` to keep pages out of pages that can't hold them.
80
+ - **Don't** use a tree for a flat list, or for more than a few levels people need to see at once.
81
+
82
+ ## Demo and verification
83
+
84
+ The `Tree` section of `build/previews.txt` is a page tree with a toolbar; the demo service rejects names over 40 characters and moves out of Archive. `node checks/tree.mjs` (from `build/`; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers the APG keys and RTL, rename (cancel, rejection, save), create (sibling and child), keyboard carry (up, down, indent, outdent, cancel, drop, a rejected move, `canDrop`), Alt+↓, Delete with focus repair, pointer carry into a closed node (spring-loaded) and onto a slot, and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
85
+ **On touch.** With `onMove`, a swipe on a row scrolls; a press-and-hold picks it up, then a drag moves it.
86
+
87
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
88
+ ## Example
89
+
90
+ `Tree` as first used in its preview:
91
+
92
+ ```tsx
93
+ import { Tree } from 'altitude-design';
94
+
95
+ <Tree ref={tree} aria-label="Pages" items={pages} getKey={p => p.id} getLabel={p => p.title} getChildren={p => p.children} hasChildren={p => !!p.children}
96
+ expanded={expanded} onExpandedChange={setExpanded} selected={selected} onSelectedChange={setSelected}
97
+ renderItem={p => <><span className="tree-demo-icon" aria-hidden="true">{p.children ? <FolderIcon /> : <PageIcon />}</span>{p.title}</>}
98
+ onRename={async (page, title) => { await treeWait(250); if (title.length > 40) return { ok: false, error: 'Use 40 characters or fewer.' }; setPages(list => treeRename(list, page.id, title.trim())); return { ok: true }; }}
99
+ canDrop={({ to }) => to.parentId === null || !!treeFind(pages, to.parentId).page.children}
100
+ onMove={async ({ id, to }) => {
101
+ await treeWait(250);
102
+ // The demo service refuses to move anything out of Archive.
103
+ if (treeFind(pages, id).parent === 'archive' && to.parentId !== 'archive') return { ok: false, error: 'Archived pages can’t be moved out of Archive.' };
104
+ setPages(list => { const page = treeFind(list, id).page; return treeInsert(treeRemove(list, id), to.parentId, to.index, page); });
105
+ return { ok: true };
106
+ }}
107
+ onCreate={async ({ parentId, index, name }) => { await treeWait(250); const page = { id: 'new-' + ++seq.current, title: name }; setPages(list => treeInsert(list, parentId, index, page)); return { ok: true, value: page }; }}
108
+ onRemove={page => setPages(list => treeRemove(list, page.id))} />
109
+ ```
110
+
111
+ The full preview, as rendered in Storybook and checked in CI (`tree-demo`, `tree-demo-tools`, `tree-demo-icon`, `tree-demo-panel` are the preview page's own layout classes, not Altitude's):
112
+
113
+ ```tsx
114
+ import * as React from 'react';
115
+ import { Button, Tree } from 'altitude-design';
116
+
117
+ const TREE_SEED = [
118
+ { id: 'brand', title: 'Brand', children: [
119
+ { id: 'logo', title: 'Logo usage' }, { id: 'colour', title: 'Colour' }, { id: 'type', title: 'Typography' } ] },
120
+ { id: 'product', title: 'Product', children: [
121
+ { id: 'roadmap', title: 'Roadmap' },
122
+ { id: 'specs', title: 'Specs', children: [{ id: 'search', title: 'Search' }, { id: 'billing', title: 'Billing' }] } ] },
123
+ { id: 'archive', title: 'Archive', children: [] },
124
+ { id: 'handbook', title: 'Team handbook' },
125
+ ];
126
+ const treeWait = ms => new Promise(resolve => setTimeout(resolve, ms));
127
+ // Pure helpers over the nested pages: find, remove, insert.
128
+ const treeFind = (list, id, parent = null) => { for (const [i, p] of list.entries()) { if (p.id === id) return { page: p, parent, index: i }; const hit = p.children && treeFind(p.children, id, p.id); if (hit) return hit; } return null; };
129
+ const treeRemove = (list, id) => list.filter(p => p.id !== id).map(p => p.children ? { ...p, children: treeRemove(p.children, id) } : p);
130
+ const treeInsert = (list, parentId, index, page) => parentId === null ? [...list.slice(0, index), page, ...list.slice(index)]
131
+ : list.map(p => p.id === parentId ? { ...p, children: [...(p.children || []).slice(0, index), page, ...(p.children || []).slice(index)] } : p.children ? { ...p, children: treeInsert(p.children, parentId, index, page) } : p);
132
+ const treeRename = (list, id, title) => list.map(p => p.id === id ? { ...p, title } : p.children ? { ...p, children: treeRename(p.children, id, title) } : p);
133
+ const FolderIcon = () => <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M2 4.5h4l1.5 1.5H14v6.5H2z" /></svg>;
134
+ const PageIcon = () => <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4"><path d="M4 2h5l3 3v9H4z" /><path d="M9 2v3h3" /></svg>;
135
+
136
+ export default function App() {
137
+ const [pages, setPages] = React.useState(TREE_SEED);
138
+ const [expanded, setExpanded] = React.useState(['brand', 'product']);
139
+ const [selected, setSelected] = React.useState('roadmap');
140
+ const tree = React.useRef(null);
141
+ const seq = React.useRef(0);
142
+ const current = selected && treeFind(pages, selected);
143
+ return <div className="tree-demo">
144
+ <div>
145
+ <div className="tree-demo-tools">
146
+ <Button size="sm" variant="ghost" onClick={() => tree.current.startCreate(current && current.page.children ? current.page.id : current ? current.parent : null)}>New page</Button>
147
+ <Button size="sm" variant="ghost" disabled={!current} onClick={() => tree.current.startRename(selected)}>Rename</Button>
148
+ </div>
149
+ <Tree ref={tree} aria-label="Pages" items={pages} getKey={p => p.id} getLabel={p => p.title} getChildren={p => p.children} hasChildren={p => !!p.children}
150
+ expanded={expanded} onExpandedChange={setExpanded} selected={selected} onSelectedChange={setSelected}
151
+ renderItem={p => <><span className="tree-demo-icon" aria-hidden="true">{p.children ? <FolderIcon /> : <PageIcon />}</span>{p.title}</>}
152
+ onRename={async (page, title) => { await treeWait(250); if (title.length > 40) return { ok: false, error: 'Use 40 characters or fewer.' }; setPages(list => treeRename(list, page.id, title.trim())); return { ok: true }; }}
153
+ canDrop={({ to }) => to.parentId === null || !!treeFind(pages, to.parentId).page.children}
154
+ onMove={async ({ id, to }) => {
155
+ await treeWait(250);
156
+ // The demo service refuses to move anything out of Archive.
157
+ if (treeFind(pages, id).parent === 'archive' && to.parentId !== 'archive') return { ok: false, error: 'Archived pages can’t be moved out of Archive.' };
158
+ setPages(list => { const page = treeFind(list, id).page; return treeInsert(treeRemove(list, id), to.parentId, to.index, page); });
159
+ return { ok: true };
160
+ }}
161
+ onCreate={async ({ parentId, index, name }) => { await treeWait(250); const page = { id: 'new-' + ++seq.current, title: name }; setPages(list => treeInsert(list, parentId, index, page)); return { ok: true, value: page }; }}
162
+ onRemove={page => setPages(list => treeRemove(list, page.id))} />
163
+ </div>
164
+ <div className="tree-demo-panel" aria-live="polite">
165
+ <h3>{current ? current.page.title : 'No page selected'}</h3>
166
+ <p>F2 renames. Ctrl+Enter adds a page after, Ctrl+Shift+Enter one inside. Space picks a page up: arrows move it, → puts it inside the page above, Space drops. Or drag it onto a folder.</p>
167
+ </div>
168
+ </div>;
169
+ }
170
+ ```
171
+ <!-- /example -->