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,97 @@
1
+ # Select
2
+
3
+ Chooses one value from a list: an opening you fill by choosing, with the list rising from its edge at level 3.
4
+
5
+ **Why an opening.** In Altitude, things you press are raised and things you fill are sunk. Choosing a value fills a field, so a Select is built like `Input`, not like a button. While nothing is chosen it's a well with its back wall showing (`--alt-wall-in`) and its placeholder sitting on the floor; as the pointer approaches, the wall deepens. Once a value is chosen the floor rises flush (`surface-raised`, no wall), so a form's unanswered selects read as holes at a glance, exactly like its empty inputs. When it's invalid it drops to trench depth on `critical-soft`. A chevron says it opens.
6
+
7
+ **The list.** It rises from the field's edge to level 3 like Popover and DropdownMenu (6px from 96% scale, its shadow growing to `elev-3`), opening upward instead when there's more room above. One `accent-soft` highlight is carried between options on `ease-settle`. The chosen option wears the Checkbox family's tick (the same path and stroke, in `accent-ink`), drawn in as the list opens. Choosing sinks the list back, raises the floor flush and lets the new value settle in with a brief `accent-soft` afterglow (a fifth of `dur-settle`).
8
+
9
+ | State | Looks like |
10
+ |---|---|
11
+ | Empty | a well with its back wall showing; the placeholder in `ink-muted` |
12
+ | Chosen | flush with the page; the value in `ink` |
13
+ | Open or focused | the lit floor of a focused Input; reached by keyboard, also its 2px neutral rim (kept while the list has focus); an empty field holds its approached depth; the list at level 3 |
14
+ | Invalid | trench depth on `critical-soft` with a `critical` rim |
15
+ | Disabled | flat on the ground, `ink-faint` |
16
+
17
+ **Keyboard and screen readers.** The WAI-ARIA listbox pattern with a button trigger. The trigger is a `button` with `aria-haspopup="listbox"` and `aria-expanded`; ↓, ↑, Enter or Space open the list and focus moves into it, where `aria-activedescendant` names the highlighted option and `aria-selected` the chosen one. ↑ and ↓ move, Home and End jump to the ends, typing letters jumps to the next option that starts with them (also on the closed trigger, which opens at the match), Enter or Space chooses, and Escape closes without changing the value. Disabled options are skipped. The list is named by the field's label. In forced colours the highlighted option has a Highlight rim, empty fields a dashed rim and chosen ones a solid rim.
18
+
19
+ **As a sheet.** `presentation="auto"` (the default) opens the list as a sheet at the bottom edge on a narrow touch screen, titled by the field's label, with Close and 44px rows; a tap outside closes it too. `"anchored"` or `"sheet"` forces one.
20
+
21
+ **You provide:** options as `items` (`{ value, label, description?, icon?, disabled? }`) or `SelectItem` children; `value`/`onValueChange` or `defaultValue`; a `placeholder` that says what to choose ("Choose a region"); `name` to submit it with a form (a hidden native select carries the value and participates in constraint validation). Wrap it in `Field` for a label, hint and error: Field passes the id, the description and `invalid` through.
22
+
23
+ `required` rejects an empty value through native form validation, including without a `name`; invalid submission focuses the visible trigger, marks it invalid and associates a visible “Choose an option.” alert. Set `requiredMessage` to localize or clarify that instruction. A supplied `Field` error takes precedence so the same issue is not announced twice. Valid selection and form reset clear this native-validation feedback. Disabled selects are excluded from validation and form data. Form reset restores `defaultValue` for uncontrolled selects and calls `onValueChange` with that reset value. Controlled selects retain their application-owned value; reset that state in the form’s reset handler if desired. A cancelled form reset leaves the selection untouched.
24
+
25
+ - Do use a RadioGroup instead when there are five options or fewer: people can see and compare them without opening anything.
26
+ - Do give a non-string `label` a `textValue`, so typeahead can find it.
27
+ - Don't use it for actions; that's a DropdownMenu.
28
+
29
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
30
+ ## Example
31
+
32
+ `Select` as first used in its preview:
33
+
34
+ ```tsx
35
+ import { Select } from 'altitude-design';
36
+
37
+ <Select items={regions} placeholder="Choose a region" name="region" />
38
+ ```
39
+
40
+ The full preview, as rendered in Storybook and checked in CI (`col`, `grid`, `cap` are the preview page's own layout classes, not Altitude's):
41
+
42
+ ```tsx
43
+ import * as React from 'react';
44
+ import { Field, Select, SelectItem } from 'altitude-design';
45
+
46
+ const regions = [
47
+ { value: "iad", label: "Virginia", description: "us-east-1" },
48
+ { value: "fra", label: "Frankfurt", description: "eu-central-2" },
49
+ { value: "sin", label: "Singapore", description: "ap-southeast-1" },
50
+ { value: "gru", label: "S\xE3o Paulo", description: "sa-east-1" },
51
+ { value: "syd", label: "Sydney", description: "Opens in March", disabled: true }
52
+ ];
53
+ export default function App() {
54
+ const [plan, setPlan] = React.useState("team");
55
+ const [tz, setTz] = React.useState("");
56
+ return <div className="col">
57
+ <div className="grid">
58
+ <Field label="Region" hint="Where your deployments run.">
59
+ <Select items={regions} placeholder="Choose a region" name="region" />
60
+ </Field>
61
+ <Field label="Plan">
62
+ <Select value={plan} onValueChange={setPlan}>
63
+ <SelectItem value="solo">Solo</SelectItem>
64
+ <SelectItem value="team">Team</SelectItem>
65
+ <SelectItem value="org">Organization</SelectItem>
66
+ </Select>
67
+ </Field>
68
+ <Field label="Timezone" required={true} error={tz ? null : "Choose a timezone."}>
69
+ <Select
70
+ value={tz}
71
+ onValueChange={setTz}
72
+ placeholder="Choose a timezone"
73
+ items={[{ value: "utc", label: "UTC" }, { value: "lis", label: "Europe/Lisbon" }, { value: "nyc", label: "America/New_York" }]}
74
+ />
75
+ </Field>
76
+ <Field label="Runtime">
77
+ <Select
78
+ defaultValue="node"
79
+ defaultOpen={true}
80
+ items={[{ value: "node", label: "Node.js 22" }, { value: "bun", label: "Bun 1.2" }, { value: "deno", label: "Deno 2" }, { value: "edge", label: "Edge (V8 isolates)" }]}
81
+ />
82
+ </Field>
83
+ <Field label="Owner">
84
+ <Select
85
+ disabled={true}
86
+ defaultValue="mara"
87
+ items={[{ value: "mara", label: "Mara Quist" }]}
88
+ />
89
+ </Field>
90
+ </div>
91
+ <p className="cap" style={{ marginTop: 150 }}>
92
+ Empty fields are holes with their back wall showing; choosing raises the floor flush. Type a letter on a focused field to jump to it.
93
+ </p>
94
+ </div>;
95
+ }
96
+ ```
97
+ <!-- /example -->
@@ -0,0 +1,70 @@
1
+ # Separator
2
+
3
+ A divider: between sections in a card, groups in a toolbar, items in a sidebar, or "or" between two ways to sign in.
4
+
5
+ **Elevation.** A divider is part of the page, so it lies on the ground: 1px of `line`, with no shadow and no wall. `line` is for decorative dividers. `line-control` is for the edges of controls, so a separator never uses it except under `contrast="more"`. Don't draw an `<hr>` with your own border colour: "never invent an in-between gray".
6
+
7
+ **Semantics.** By default it's `role="separator"` with `aria-orientation`, so a screen reader knows the content is parted. Set `decorative` when the layout already says it (a line between groups of toolbar buttons): it becomes `role="none"` and is skipped. A labelled separator (`label="or"`) is the word in `ink-muted` between two drawn lines, and it's read as plain text. `role="separator"` would make its children presentational and lose the word.
8
+
9
+ **Orientation.** `horizontal` fills its container's width. `vertical` stretches to the height of the row it's in (at least 1em), so put it in a flex row.
10
+
11
+ **Accessibility.** Under forced colours it's 1px of `CanvasText`. Under `contrast="more"` the lines are `line-control`.
12
+
13
+ **Props:** `orientation` (`'horizontal' | 'vertical'`, default horizontal); `decorative`; `label`.
14
+
15
+ **You provide:** where it goes, and whether it says something (leave `decorative` off) or only decorates.
16
+
17
+ ```tsx
18
+ <Separator />
19
+ <Separator orientation="vertical" decorative />
20
+ <Separator label="or" />
21
+ ```
22
+
23
+ - Do use it instead of a border on one side of a section.
24
+ - Do make a separator between groups of toolbar buttons `decorative`.
25
+ - Don't use it to fake a table's row lines; `Table` and `ItemGroup` draw their own (`ItemSeparator`).
26
+ - Don't give it a colour, a thickness or a shadow.
27
+
28
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
29
+ ## Example
30
+
31
+ `Separator` as first used in its preview:
32
+
33
+ ```tsx
34
+ import { Separator } from 'altitude-design';
35
+
36
+ <Separator />
37
+ ```
38
+
39
+ The full preview, as rendered in Storybook and checked in CI (`sp`, `cap`, `col`, `bar`, `signin` are the preview page's own layout classes, not Altitude's):
40
+
41
+ ```tsx
42
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, Separator } from 'altitude-design';
43
+
44
+ export default function App() {
45
+ return (
46
+ <div className="sp">
47
+ <Card>
48
+ <CardHeader><CardTitle>Workspace</CardTitle><CardDescription>Name, URL and region</CardDescription></CardHeader>
49
+ <Separator />
50
+ <CardContent><p className="cap">Sections in a card are parted by a horizontal separator: role="separator", 1px of line.</p></CardContent>
51
+ </Card>
52
+ <div className="col">
53
+ <div className="bar">
54
+ <Button size="sm" variant="ghost">Bold</Button>
55
+ <Button size="sm" variant="ghost">Italic</Button>
56
+ <Separator orientation="vertical" decorative />
57
+ <Button size="sm" variant="ghost">Link</Button>
58
+ </div>
59
+ <div className="signin">
60
+ <Button variant="secondary">Continue with SSO</Button>
61
+ <Separator label="or" />
62
+ <Input placeholder="Email address" aria-label="Email address" />
63
+ </div>
64
+ <p className="cap">A vertical one between groups of buttons is decorative. "or" is read as a word; its lines are drawn.</p>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
69
+ ```
70
+ <!-- /example -->
@@ -0,0 +1,126 @@
1
+ # Sheet
2
+
3
+ A large panel laid over the page at level 4, anchored to an edge, for a task that needs more room than a popover and less commitment than a page.
4
+
5
+ **Why level 4.** A sheet is a whole working surface above the page: higher than menus and popovers (level 3), because it holds a task rather than one choice, and below the summit (level 5), because it doesn't demand an answer. It's drawn with `surface-overlay` and `elev-4`, the toasts' tier.
6
+
7
+ **Risen at its edge, not slid in.** Nothing in Altitude slides in from the side. A sheet appears where it's anchored: it travels 16px out of its edge as it fades in, while its shadow grows from `elev-1` to `elev-4` (`dur-travel-4`, `ease-rise`). It stands 8px clear of the edge with `radius-xl` corners, so its own edges, and the shadow it casts below them, read as height. A modal sheet recedes the page behind it, as a dialog does. Closing lifts it off: it rises 8px and grows 1% as its shadow spreads and it fades, in 70% of the time it took to arrive.
8
+
9
+ **Inside.** The sheet scrolls as one column. `SheetHeader` stays at the top and rises to `elev-2` once content passes under it; `SheetFooter` stays at the bottom, and while there's more below, its top edge curves away (`lip-bottom`), as in ScrollArea.
10
+
11
+ **Bottom sheets** have a finger pull at the top: a small recess, because everything you put something into is an opening in Altitude, and this is where your finger goes. It deepens as the pointer approaches. Drag it and the sheet follows; past a third of its height, or with a flick, it goes; otherwise it springs back. The pull is a convenience: Escape, the close button and the scrim still close it.
12
+
13
+ **Modal or not.** By default a sheet is modal (the WAI-ARIA dialog pattern): the scrim blocks the page, Tab is trapped, Escape closes it and focus returns to what opened it. `modal={false}` makes a side panel that leaves the page usable: no scrim, no receding page, no trap; focus still moves into it, and Escape closes it from inside.
14
+
15
+ | Prop | Default | |
16
+ |---|---|---|
17
+ | `side` | `right` | `right`, `left` or `bottom` |
18
+ | `size` | `md` | width: `sm` 360, `md` 440, `lg` 560px; a bottom sheet 480, 720, 960px |
19
+ | `modal` | `true` | `false`: a non-blocking panel |
20
+ | `dismissible` | `true` | `false`: only your own buttons close it |
21
+
22
+ **On a phone.** It sizes to the visible viewport, not the largest one: with the on-screen keyboard open it shrinks to the part you can see, so its footer and the focused field stay in reach.
23
+
24
+ **You provide:** `open`/`onOpenChange` or a `trigger`, and the content: `SheetHeader` with `SheetTitle` (it names the sheet) and `SheetDescription`, the body, and `SheetFooter` with the actions (cancel as ghost first, the action last).
25
+
26
+ - Do use a sheet to edit an item without leaving the list it's in, and for filters, details and settings.
27
+ - Don't open a sheet from a Dialog or the CommandPalette. A level-4 surface can't sit on the summit: if it's opened under one, it paints beneath it and leaves focus where it is. A Dialog may open from a sheet, to confirm something.
28
+ - Don't stack a sheet on a sheet; replace its content instead.
29
+
30
+ A modal Sheet shares document-scoped isolation, scroll ownership and eligible-focus containment with Dialog. Its locks remain through exit. Opening a Dialog from a Sheet suspends the Sheet, then resumes its focus and existing background policy after the Dialog closes. A Sheet requested beneath a summit cannot take its focus or `aria-modal` ownership. A stationary Dialog also removes the lower Sheet's scrim blur until it closes.
31
+
32
+ `modal={false}` keeps the page interactive and scrollable: no scrim, recession or focus trap. Closing that Sheet does not restore focus over a newer modal.
33
+
34
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
35
+ ## Example
36
+
37
+ `Sheet` as first used in its preview:
38
+
39
+ ```tsx
40
+ import { Button, Input, Sheet, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from 'altitude-design';
41
+
42
+ <Sheet side="bottom" size="sm" trigger={<Button>Share</Button>}>
43
+ <SheetHeader>
44
+ <SheetTitle>Share Northwind</SheetTitle>
45
+ <SheetDescription>
46
+ Anyone with the link can view the latest deployment.
47
+ </SheetDescription>
48
+ </SheetHeader>
49
+ <Input
50
+ defaultValue="https://acme.dev/n/8dc01b2"
51
+ readOnly={true}
52
+ aria-label="Share link"
53
+ />
54
+ <SheetFooter><Button variant="primary">Copy link</Button></SheetFooter>
55
+ </Sheet>
56
+ ```
57
+
58
+ The full preview, as rendered in Storybook and checked in CI (`page`, `row`, `grid`, `form` are the preview page's own layout classes, not Altitude's):
59
+
60
+ ```tsx
61
+ import * as React from 'react';
62
+ import { Button, Card, Checkbox, Field, Input, Sheet, SheetDescription, SheetFooter, SheetHeader, SheetTitle, Switch, Textarea } from 'altitude-design';
63
+
64
+ export default function App() {
65
+ const [edit, setEdit] = React.useState(true);
66
+ return <div className="page">
67
+ <div className="row">
68
+ <Button variant="primary" onClick={() => setEdit(true)}>Edit project</Button>
69
+ <Sheet side="bottom" size="sm" trigger={<Button>Share</Button>}>
70
+ <SheetHeader>
71
+ <SheetTitle>Share Northwind</SheetTitle>
72
+ <SheetDescription>
73
+ Anyone with the link can view the latest deployment.
74
+ </SheetDescription>
75
+ </SheetHeader>
76
+ <Input
77
+ defaultValue="https://acme.dev/n/8dc01b2"
78
+ readOnly={true}
79
+ aria-label="Share link"
80
+ />
81
+ <SheetFooter><Button variant="primary">Copy link</Button></SheetFooter>
82
+ </Sheet>
83
+ <Sheet
84
+ side="left"
85
+ size="sm"
86
+ modal={false}
87
+ trigger={<Button variant="ghost">Filters</Button>}
88
+ >
89
+ <SheetHeader>
90
+ <SheetTitle>Filters</SheetTitle>
91
+ <SheetDescription>The page stays usable while this is open.</SheetDescription>
92
+ </SheetHeader>
93
+ <Checkbox label="Production" defaultChecked={true} />
94
+ <Checkbox label="Preview" />
95
+ <Checkbox label="Failed builds only" />
96
+ </Sheet>
97
+ </div>
98
+ <div className="grid" data-tells-ignore="LA4 placeholder page behind the overlay">
99
+ <Card />
100
+ <Card />
101
+ <Card />
102
+ <Card />
103
+ <Card />
104
+ <Card />
105
+ </div>
106
+ <Sheet open={edit} onOpenChange={setEdit}>
107
+ <SheetHeader>
108
+ <SheetTitle>Edit project</SheetTitle>
109
+ <SheetDescription>Changes apply to every environment.</SheetDescription>
110
+ </SheetHeader>
111
+ <div className="form">
112
+ <Field label="Project name"><Input defaultValue="Northwind" /></Field>
113
+ <Field label="Description" hint="Shown on the project's card.">
114
+ <Textarea placeholder="What is this project for?" />
115
+ </Field>
116
+ <Switch label="Deploy previews for every branch" defaultChecked={true} />
117
+ </div>
118
+ <SheetFooter>
119
+ <Button variant="ghost" onClick={() => setEdit(false)}>Cancel</Button>
120
+ <Button variant="primary" onClick={() => setEdit(false)}>Save changes</Button>
121
+ </SheetFooter>
122
+ </Sheet>
123
+ </div>;
124
+ }
125
+ ```
126
+ <!-- /example -->
@@ -0,0 +1,50 @@
1
+ # Skeleton
2
+
3
+ Stands in for loading content as an **imprint**: the shape of what's coming, pressed into the page. When the content arrives it rises out of its imprint and the imprint fills in behind it.
4
+
5
+ Imprints are wells (`surface-well`, inset shadow) because they're openings waiting to receive something. While waiting, a soft band of light passes over them every `dur-sweep`, staggered line by line; it stops under reduced motion. When `loaded` turns true, the children rise from the bottom of the imprint over `dur-travel-3` while the well fades out. Reveals share the attention budget, so if many land at once, no more than four begin together and the rest follow in order.
6
+
7
+ **Or don't.** `Avatar`, `Badge`, `CardTitle`, `CardDescription`, `CardContent` and `DataTable` draw their own imprint while your data is still undefined; see `Await`. Use Skeleton for layouts of your own.
8
+
9
+ **You provide:** the real content as children, `loaded`, and a shape that matches it: `shape="circle"` with a `width`, `shape="text"` with `lines`, or a block with `width` and `height`.
10
+
11
+ - Match the real layout so nothing jumps when content arrives.
12
+ - Don't show imprints for loads under about 300ms; show nothing.
13
+
14
+ Under `AltitudeProvider loading="breath"` (experimental) a Skeleton breathes in depth instead of being swept by light, on the same page clock as every other imprint, and its content fills past the ground when `loaded` turns true. See `Await`.
15
+
16
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
17
+ ## Example
18
+
19
+ `Skeleton` as first used in its preview:
20
+
21
+ ```tsx
22
+ import { Avatar, Skeleton } from 'altitude-design';
23
+
24
+ <Skeleton loaded={loaded} shape="circle" width={44}><Avatar name="Mara Quist" size={44} status="active" /></Skeleton>
25
+ ```
26
+
27
+ The full preview, as rendered in Storybook and checked in CI (`col`, `card`, `lines`, `txt`, `cap` are the preview page's own layout classes, not Altitude's):
28
+
29
+ ```tsx
30
+ import * as React from 'react';
31
+ import { Avatar, Skeleton, Surface } from 'altitude-design';
32
+
33
+ export default function App() {
34
+ const [loaded, setLoaded] = React.useState(false);
35
+ React.useEffect(() => { const t = setInterval(() => setLoaded((l) => !l), 2600); return () => clearInterval(t); }, []);
36
+ return (
37
+ <div className="col">
38
+ <Surface level={1} className="card">
39
+ <Skeleton loaded={loaded} shape="circle" width={44}><Avatar name="Mara Quist" size={44} status="active" /></Skeleton>
40
+ <div className="lines">
41
+ <Skeleton loaded={loaded} width="45%" height={14}><b style={{ font: '600 14px/20px var(--font-sans)' }}>Mara Quist</b></Skeleton>
42
+ <Skeleton loaded={loaded} shape="text" lines={2}><p className="txt">Merged “Faster cold starts” into main. Cold starts are down 38% across all regions.</p></Skeleton>
43
+ </div>
44
+ </Surface>
45
+ <p className="cap">While it loads, the layout is pressed into the page; when content lands it rises out of its imprint, four at a time at most.</p>
46
+ </div>
47
+ );
48
+ }
49
+ ```
50
+ <!-- /example -->
@@ -0,0 +1,60 @@
1
+ # Slider
2
+
3
+ Picks a number from a range by dragging a raised thumb along a trench.
4
+
5
+ The track is level −2 and the filled part is a raised `accent` bar inside it. The thumb is level 1 with an `accent` rim, level 2 on hover, and floats to level 3 (scaled 12%) while held, with its value floating above it in a bubble at the same height as a tooltip. Keyboard: arrows step, Page Up/Down jump a tenth, Home/End go to the ends.
6
+
7
+ **You provide:** `value`/`onValueChange` or `defaultValue`, `min`, `max`, `step`, an `aria-label`, and `format` for the bubble (`n => n + '%'`).
8
+
9
+ ## A range
10
+
11
+ Pass `value` (or `defaultValue`) as `[low, high]` and the Slider has two thumbs, with the raised `accent` fill running between them in the same trench: a price filter, a latency window.
12
+
13
+ ```tsx
14
+ <Slider value={[lo, hi]} onValueChange={([a, b]) => setRange([a, b])} min={0} max={500} step={5}
15
+ minStepsBetween={2} aria-label={['Minimum price', 'Maximum price']} format={(n) => `$${n}`} />
16
+ ```
17
+
18
+ - **Thumbs can't cross.** A thumb pushed against the other stops there, with no overshoot; `minStepsBetween` keeps them that many steps apart. Two thumbs that meet (with no gap) part in the direction of the first drag.
19
+ - **Drag the fill** to move both thumbs together; the window keeps its width and stops at the ends. The fill lifts to level 2 on hover and shows a grab cursor, because dragging it does something.
20
+ - **Press the track** outside the thumbs and the nearer one moves there and takes focus. Pressing the fill moves neither.
21
+ - **Only the thumb in hand floats** to level 3 with its bubble; the other stays at rest. Panning holds both.
22
+ - **Keyboard**, after the [APG multi-thumb slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/): each thumb is its own Tab stop and `role="slider"`, with `aria-valuemin` and `aria-valuemax` bounded by the other thumb (less the gap). Arrows step, Page Up/Down jump a tenth, Home/End go to that thumb's own bounds. When `format` returns text, it's each thumb's `aria-valuetext` too.
23
+ - **Names.** Give each thumb its own name (`['Minimum price', 'Maximum price']`), or one name for the pair: the thumbs are then "Price, minimum" and "Price, maximum", in a `role="group"` named "Price".
24
+
25
+ - Do show the current value in text near the slider when it matters precisely; the bubble only shows while dragging or focused.
26
+
27
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
28
+ ## Example
29
+
30
+ `Slider` as first used in its preview:
31
+
32
+ ```tsx
33
+ import { Slider } from 'altitude-design';
34
+
35
+ <Slider value={v} onValueChange={setV} aria-label="Volume" format={(n) => n + '%'} />
36
+ ```
37
+
38
+ The full preview, as rendered in Storybook and checked in CI (`col`, `wrap`, `cap`, `gap` are the preview page's own layout classes, not Altitude's):
39
+
40
+ ```tsx
41
+ import * as React from 'react';
42
+ import { Slider } from 'altitude-design';
43
+
44
+ export default function App() {
45
+ const [v, setV] = React.useState(64);
46
+ const [price, setPrice] = React.useState([20, 80]);
47
+ return (
48
+ <div className="col wrap">
49
+ <Slider value={v} onValueChange={setV} aria-label="Volume" format={(n) => n + '%'} />
50
+ <Slider defaultValue={30} disabled aria-label="Disabled" />
51
+ <p className="cap">Volume {v}% — drag, or focus and use the arrow keys.</p>
52
+ <div className="gap" />
53
+ <Slider value={price} onValueChange={setPrice} min={0} max={200} step={5} minStepsBetween={2}
54
+ aria-label={['Minimum price', 'Maximum price']} format={(n) => '$' + n} />
55
+ <p className="cap">Price ${price[0]}–${price[1]} — drag either thumb, or the fill to move both.</p>
56
+ </div>
57
+ );
58
+ }
59
+ ```
60
+ <!-- /example -->
@@ -0,0 +1,45 @@
1
+ # SortableList
2
+
3
+ Reorders a list by picking items up: the item you hold floats in your hand, the slot it will land in opens, and it lands.
4
+
5
+ Pressing and dragging an item lifts it to level 3 (floating surface, `elev-3`), scales it 1.5% and tilts it half a degree; the others glide aside on `ease-settle`, and a well opens where it will land. Releasing animates it into place (FLIP) while its shadow falls back to `elev-1`. Keyboard: focus an item, press Space to pick it up, arrow keys to move it, Space to drop, Escape to cancel; every move is announced.
6
+
7
+ **On touch.** A swipe on a row scrolls; a press-and-hold picks it up, and the grip picks it up at once. A mouse drag needs no hold.
8
+
9
+ **You provide:** `items`, `getKey`, `onReorder` (receives the new array), `renderItem` (the row content; the row itself and its grip are drawn for you), `getLabel` for announcements, `aria-label`.
10
+
11
+ - Do keep rows short and uniform; the slot is sized to the item being moved.
12
+
13
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
14
+ ## Example
15
+
16
+ `SortableList` as first used in its preview:
17
+
18
+ ```tsx
19
+ import { Badge, SortableList } from 'altitude-design';
20
+
21
+ <SortableList aria-label="Priorities" items={items} getKey={(i) => i.id} getLabel={(i) => i.t} onReorder={setItems}
22
+ renderItem={(i) => <div className="it">{i.t}<Badge tone={i.p === 'High' ? 'critical' : i.p === 'Med' ? 'caution' : 'neutral'}>{i.p}</Badge></div>} />
23
+ ```
24
+
25
+ The full preview, as rendered in Storybook and checked in CI (`col`, `wrap`, `it`, `cap` are the preview page's own layout classes, not Altitude's):
26
+
27
+ ```tsx
28
+ import * as React from 'react';
29
+ import { Badge, SortableList } from 'altitude-design';
30
+
31
+ export default function App() {
32
+ const [items, setItems] = React.useState([
33
+ { id: 'a', t: 'Write release notes', p: 'High' }, { id: 'b', t: 'Review pricing copy', p: 'Med' },
34
+ { id: 'c', t: 'Fix cold-start regression', p: 'High' }, { id: 'd', t: 'Update onboarding emails', p: 'Low' }, { id: 'e', t: 'Plan Q1 roadmap', p: 'Med' },
35
+ ]);
36
+ return (
37
+ <div className="col wrap">
38
+ <SortableList aria-label="Priorities" items={items} getKey={(i) => i.id} getLabel={(i) => i.t} onReorder={setItems}
39
+ renderItem={(i) => <div className="it">{i.t}<Badge tone={i.p === 'High' ? 'critical' : i.p === 'Med' ? 'caution' : 'neutral'}>{i.p}</Badge></div>} />
40
+ <p className="cap">Drag, or focus a row and press Space, then the arrow keys.</p>
41
+ </div>
42
+ );
43
+ }
44
+ ```
45
+ <!-- /example -->
@@ -0,0 +1,87 @@
1
+ # Sparkline
2
+
3
+ A trend the size of a word: the last 30 days of a metric in a `DataTable` cell, a `PropertyList` value, a `Stat`, or a sentence. It says rising, falling, spiky or flat at a glance, beside the number that gives the detail (after Tufte's sparklines).
4
+
5
+ **Why it's flat.** A sparkline is part of the record in the row it sits in, so it lies on the ground like every mark a chart draws: a 1.5px stroke with round joins, no fill under it, no shadow, no glow. There are no axes, gridlines or readout; the text beside it carries the numbers. The last point wears a 4px marker, so you can see where "now" is. `kind="bar"` draws flat bars 1px apart, standing on zero, for counts. A missing value (`null`) is a gap, never bridged. A normal range (`band`) is a flat band of `line` behind the marks.
6
+
7
+ **Its size.** Its height is the line height of the text it sits beside (`1lh`), and it sits in that line. Its width is `5em` unless you give one.
8
+
9
+ **Colour does one job.** By default it's `ink-muted`: context for the number beside it. `tone="data"` makes it `data-1`, when the sparkline *is* the series. `positive`, `caution` or `critical` only when the series means good or bad, and then the text beside it says so too ("Rising"), because colour never travels alone.
10
+
11
+ **Accessibility.** It's `role="img"` with a generated label that says the trend in words: "Up 17% over 30 days, from 1.2K to 1.4K. Low 1.2K, high 1.5K." With `x` naming daily dates it says "days"; otherwise "values". With a `band` it counts the values outside it. An `aria-label` you pass goes first ("Signups, last 30 days. Up 17%…"); `summary` replaces the generated text. When the row already says it, pass `decorative` and it's hidden from assistive technology. It's never focusable. Under forced colours it's drawn in `CanvasText` and the band is a dashed outline; under `contrast="more"` the line is 2px.
12
+
13
+ **Loading.** Pass `data` as it is: `undefined` while it loads, or a promise, and it's an imprint one line high and its own width (`Await`), which lands within the attention budget. A value that changes in a live table gets the row's afterglow, not a sparkline animation.
14
+
15
+ **Props:** `data` (numbers, or records with `y`); `y`; `x` (a date key, for the label); `kind` (`'line'` or `'bar'`); `tone` (`'neutral'`, `'data'`, `'positive'`, `'caution'`, `'critical'`); `band` (`[low, high]`); `width`; `format` (for the label; default compact); `summary`; `decorative`; `locale`.
16
+
17
+ **You provide:** the values, oldest first; the number they explain, beside them; and either an `aria-label` naming the measure or `decorative` when the row already says it all.
18
+
19
+ ```tsx
20
+ <Sparkline data={daily} x="day" y="signups" aria-label="Signups, last 30 days" />
21
+ <DataTable columns={[…, { key: 'trend', header: 'Last 30 days', cell: (r) => <Sparkline data={r.daily} decorative /> }]} rows={teams} />
22
+ ```
23
+
24
+ - Do put the number it explains right beside it; a sparkline alone has no scale.
25
+ - Do use `kind="bar"` for discrete counts (deploys per day), and the line for measures.
26
+ - Do leave missing values as `null`.
27
+ - Don't give it axes, a tooltip or a legend; when someone needs to read values, that's a `LineChart`.
28
+ - Don't colour it with a status hue unless the series means good or bad, and say so in words beside it.
29
+ - Don't compare sparklines in different rows as if they shared a scale: each is scaled to its own values.
30
+
31
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
32
+ ## Example
33
+
34
+ `Sparkline` as first used in its preview:
35
+
36
+ ```tsx
37
+ import { Sparkline } from 'altitude-design';
38
+
39
+ <Sparkline data={signups} x="day" y="signups" tone="data" width={120} aria-label="Signups, last 30 days" />
40
+ ```
41
+
42
+ The full preview, as rendered in Storybook and checked in CI (`sp`, `row`, `big`, `note` are the preview page's own layout classes, not Altitude's):
43
+
44
+ ```tsx
45
+ import * as React from 'react';
46
+ import { Badge, DataTable, Sparkline } from 'altitude-design';
47
+
48
+ const spDays = (vals) => vals.map((v, i) => ({ day: `2026-10-${String(i + 1).padStart(2, '0')}`, signups: v }));
49
+ const signups = spDays([1200, 1180, 1230, 1260, 1210, 1290, 1310, 1280, 1300, 1350, 1330, 1290, 1340, 1370, 1360, 1320, 1380, 1400, 1390, 1360, 1410, 1430, 1390, 1420, 1450, 1410, 1380, 1420, 1440, 1400]);
50
+ const teams = [
51
+ { id: 'core', name: 'Core', builds: [12, 14, 13, 15, 16, 18, 17, 19, 21, 20, 22, 24, 23, 25], errors: [0.4, 0.5, 0.4, 0.6, 0.5, 0.7, 0.9, 1.1, 1.0, 1.3, 1.5, 1.4, 1.8, 2.1] },
52
+ { id: 'web', name: 'Web', builds: [30, 28, 31, 27, 29, 26, 25, 27, 24, 23, 25, 22, 21, 20], errors: [1.2, 1.1, 1.3, 1.2, 1.1, 1.2, 1.0, 1.1, 1.2, 1.1, 1.0, 1.1, 1.2, 1.1] },
53
+ { id: 'data', name: 'Data', builds: [8, 9, null, null, 11, 10, 12, 14, 13, 15, 14, 16, 18, 17], errors: [2.0, 1.8, null, null, 1.6, 1.5, 1.6, 1.4, 1.3, 1.2, 1.3, 1.1, 1.0, 0.9] },
54
+ ];
55
+ const latency = [182, 176, 190, 240, 178, 184, 171, 169, 175, 260, 181, 177, 174, 172, 179, 185, 176, 173];
56
+ export default function App() {
57
+ const [slow, setSlow] = React.useState(undefined);
58
+ React.useEffect(() => { const t = setTimeout(() => setSlow([4, 6, 5, 8, 7, 9, 12, 11, 13, 12]), 2600); return () => clearTimeout(t); }, []);
59
+ return (
60
+ <div className="sp">
61
+ <section>
62
+ <h3>Beside a number</h3>
63
+ <div className="row"><span className="big">1,400</span><Sparkline data={signups} x="day" y="signups" tone="data" width={120} aria-label="Signups, last 30 days" /></div>
64
+ <p className="note">Signups, last 30 days. Up 17% from 1,200.</p>
65
+ </section>
66
+ <section>
67
+ <h3>In running text</h3>
68
+ <p>p95 latency <Sparkline data={latency} band={[160, 200]} format={(ms) => `${ms} ms`} aria-label="p95 latency, last 18 hours" /> spiked twice outside its normal range.</p>
69
+ <p>Deploys this week <Sparkline data={[3, 5, 2, 6, 4, 0, 1]} kind="bar" aria-label="Deploys per day" /> peaked on Thursday.</p>
70
+ <p className="note">Loading: <Sparkline data={slow} aria-label="Queue depth" /></p>
71
+ </section>
72
+ <section style={{ gridColumn: '1 / -1' }}>
73
+ <h3 id="sp-teams">In a table</h3>
74
+ <DataTable aria-labelledby="sp-teams" getRowId={(r) => r.id} rows={teams} columns={[
75
+ { key: 'name', header: 'Team' },
76
+ { key: 'builds', header: 'Builds per day', cell: (r) => <span className="row">{r.builds[r.builds.length - 1]}<Sparkline data={r.builds} decorative /></span> },
77
+ { key: 'errors', header: 'Error rate, 14 days', cell: (r) => {
78
+ const rising = r.errors[r.errors.length - 1] > r.errors[0] * 1.5;
79
+ return <span className="row">{r.errors[r.errors.length - 1]}%<Sparkline data={r.errors} tone={rising ? 'critical' : 'neutral'} format={(v) => `${v}%`} aria-label={`${r.name} error rate`} />{rising && <Badge tone="critical">Rising</Badge>}</span>;
80
+ } },
81
+ ]} />
82
+ </section>
83
+ </div>
84
+ );
85
+ }
86
+ ```
87
+ <!-- /example -->
@@ -0,0 +1,65 @@
1
+ # SplitButton
2
+
3
+ One action with its alternatives: "Save changes ▾" (Save as draft, Save and close), "Merge ▾" (squash, rebase), "Deploy ▾" (to staging, to production).
4
+
5
+ **Elevation.** It's one pressable action, so it's one slab: **two faces sharing one wall**, parted by a 1px notch (`line-control`, or a mix of `on-accent` into the accent on a primary slab). Being one slab, it moves as one: pointing at either face lifts the whole slab, and pressing either presses it all, so the faces never stand at two heights with a step between them. Which face you're on, and so what a click does, is a tint on that face alone. Each face has its own focus ring around its face and wall. The menu floats at level 3 (`DropdownMenu`). Placing a `Button` and a `DropdownMenu` side by side reads as two slabs of different importance. A SplitButton reads as one action with options.
6
+
7
+ **Loading.** `loading` holds the action face pressed with its label kept, as a `Button` does. The chevron stays usable.
8
+
9
+ **Accessibility.** A [button](https://www.w3.org/WAI/ARIA/apg/patterns/button/) and a [menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) in a `role="group"` named by the action ("Save changes"). There are two tab stops. Enter or Space on the chevron, or Alt+Down on the action, opens the menu. Name the chevron for what it offers (`menuLabel="More save options"`). Under forced colours each face has a 1px border, and the notch is `CanvasText`.
10
+
11
+ **Props:** `label`; `variant` (`'primary' | 'secondary'`); `items` (`DropdownMenu` entries); `menuLabel` (default "More options"); `groupLabel` (when `label` isn't plain text); `onClick`, `loading`, `disabled`, `size` and the other `Button` props, which go to the action face.
12
+
13
+ **You provide:** the default action and its alternatives. The default is what most people want most of the time.
14
+
15
+ ```tsx
16
+ <SplitButton variant="primary" label="Save changes" onClick={save} menuLabel="More save options"
17
+ items={[
18
+ { label: 'Save as draft', onSelect: saveDraft },
19
+ { label: 'Save and close', onSelect: saveClose },
20
+ ]} />
21
+ ```
22
+
23
+ - Do put the common case on the face and the rest in the menu.
24
+ - Do mark a destructive alternative with `tone: 'critical'`.
25
+ - Don't hide the default inside the menu, or offer only one alternative (use two buttons).
26
+ - Don't nest a SplitButton in a `Toolbar`; its tools are ghosts.
27
+
28
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
29
+ ## Example
30
+
31
+ `SplitButton` as first used in its preview:
32
+
33
+ ```tsx
34
+ import { SplitButton } from 'altitude-design';
35
+
36
+ <SplitButton label="Save changes" menuLabel="More save options" onClick={() => setSaid('Saved.')}
37
+ items={[{ label: 'Save as draft', onSelect: () => setSaid('Saved as a draft.') }, { label: 'Save and close', onSelect: () => setSaid('Saved and closed.') }]} />
38
+ ```
39
+
40
+ 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):
41
+
42
+ ```tsx
43
+ import * as React from 'react';
44
+ import { SplitButton } from 'altitude-design';
45
+
46
+ export default function App() {
47
+ const [said, setSaid] = React.useState('');
48
+ const [deploying, setDeploying] = React.useState(false);
49
+ const deploy = (where) => { setSaid(`Deploying to ${where}…`); setDeploying(true); setTimeout(() => { setDeploying(false); setSaid(`Deployed to ${where}.`); }, 1600); };
50
+ return (
51
+ <div className="col">
52
+ <div className="row">
53
+ <SplitButton label="Save changes" menuLabel="More save options" onClick={() => setSaid('Saved.')}
54
+ items={[{ label: 'Save as draft', onSelect: () => setSaid('Saved as a draft.') }, { label: 'Save and close', onSelect: () => setSaid('Saved and closed.') }]} />
55
+ <SplitButton variant="secondary" label="Merge" menuLabel="More merge options" onClick={() => setSaid('Merged.')}
56
+ items={[{ label: 'Squash and merge', onSelect: () => setSaid('Squashed and merged.') }, { label: 'Rebase and merge', onSelect: () => setSaid('Rebased and merged.') }]} />
57
+ <SplitButton variant="secondary" label="Deploy" menuLabel="More deploy options" loading={deploying} onClick={() => deploy('staging')}
58
+ items={[{ label: 'Deploy to staging', onSelect: () => deploy('staging') }, { label: 'Deploy to production', onSelect: () => deploy('production'), tone: 'critical' }]} />
59
+ </div>
60
+ <p className="cap" role="status">{said || 'Each face lifts on its own. Alt+Down on an action opens its menu. While Deploy is loading, its chevron still works.'}</p>
61
+ </div>
62
+ );
63
+ }
64
+ ```
65
+ <!-- /example -->