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,115 @@
1
+ # ChartReference and ChartAnnotation
2
+
3
+ Reading a chart against something: an SLO target, a budget, a normal range, the average, the day you shipped v2. Overlays like these help people read charts (Kong & Agrawala 2012), and most dashboards need at least a target line and an event marker. Put them inside any cartesian chart type, or inside the container:
4
+
5
+ ```tsx
6
+ <LineChart data={uptime} x="day">
7
+ <ChartReference y={[0.995, 1]} label="Normal range" />
8
+ <ChartReference y={0.999} label="Target 99.9%" />
9
+ <ChartReference y={0.99} label="Limit 99%" tone="critical" />
10
+ <ChartReference y="mean" label="Average" />
11
+ <ChartAnnotation x="2026-03-14" label="DNS outage" />
12
+ </LineChart>
13
+ ```
14
+
15
+ **Why they're flush.** References and annotations are part of the reading, not something to act on, so they lie on the ground with the marks:
16
+
17
+ | Part | Drawn as | Words |
18
+ |---|---|---|
19
+ | Reference line | 1px `ink-muted`, solid, over the marks. Dashes mean "projected", so a reference line never has them | its label at the right end, above the line, in caption text; below it, at the left end or further along when that spot would cover a mark |
20
+ | Status threshold (`tone`) | 1px `caution` or `critical` | the label in `ink`, with the status icon in its tone |
21
+ | Band (`y={[low, high]}`) | a flat `line` fill under the marks: a decorative token, so it isn't read as data or as an opening | its label inside the band, at whichever corner covers no mark, or beside it when the band is under 20px tall |
22
+ | Annotation | a `Badge` plate flush at the top of the plot, in a row of headroom above the data (a label is part of the record), with a 1px `line-control` leader down to its x | the plate's text |
23
+ | Annotation with `onSelect` | the plate is a button and lifts one level when pointed at | button semantics, named "v2 launch, Mar 4, 2026" |
24
+
25
+ **Labels stay clear, of each other and of the data.** The container draws every reference and annotation itself, so it can lay their words out together with the marks. Plates sit in rows 26px apart at the top of the plot, and the data starts below the last row, so a plate never covers a mark. A reference's label takes the first spot that covers no line, area or bar and no other word: the right end above its line, then below, then the left end, then along the line. On a plot too small for any of those, it takes the spot that covers least. Across a horizontal chart, references stand upright and their labels stack in a row above the bars. All labels are on a halo of the plot's colour.
26
+
27
+ **Values.** A reference's own values count toward the y axis, so a target above every value is still on the plot. `y="mean"` and `y="median"` are read from the settled records of `series` (default: the first shown), and the label adds the value at the data's own precision: "Average 162.5 ms".
28
+
29
+ **Motion.** References are drawn in the plot's coordinates, so they glide with a rescale and the marks ([Motion](../../visual/Motion.md), "Charts"), and a mean or median glides to its new value when the data changes. Under reduced motion they change in place.
30
+
31
+ **Without colour.** Under forced colours lines, labels, icons and leaders are `CanvasText`, bands are hatched in `GrayText` on `Canvas`, and plates get a 1px `CanvasText` border.
32
+
33
+ **Accessibility.** References and annotations are read after the chart's summary, written or generated, and on the keyboard path's summary node: "References: Normal range, 99.5% to 100%; Target 99.9% at 99.9%. 1 annotation: DNS outage, Mar 14, 2026." Plates that only label are hidden from assistive technology, since the summary lists them. Plates with `onSelect` are buttons in the tab order after the plot.
34
+
35
+ **Props.** `ChartReference`: `y` (a value, `[low, high]`, `'mean'` or `'median'`), `label`, `tone` (`'caution'` or `'critical'`), `series`. `ChartAnnotation`: `x` (as the data's x values are written), `label`, `onSelect`.
36
+
37
+ - Do say what a reference is in its label ("Target 99.9%"), not only its value.
38
+ - Do use `tone` only when crossing the line means good or bad.
39
+ - Do keep annotations to a few words; the plate is a label, not a note.
40
+ - Don't dash a reference line; a dash means "projected".
41
+ - Don't use a reference band for data. A range in the data is an uncertainty band (`LineChart`'s `band`).
42
+
43
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
44
+ ## Example
45
+
46
+ `ChartReference` as first used in its preview:
47
+
48
+ ```tsx
49
+ import { ChartReference } from 'altitude-design';
50
+
51
+ <ChartReference y={[0.995, 1]} label="Normal range" />
52
+ ```
53
+
54
+ The full preview, as rendered in Storybook and checked in CI (`cr`, `row` are the preview page's own layout classes, not Altitude's):
55
+
56
+ ```tsx
57
+ import * as React from 'react';
58
+ import { BarChart, Button, Card, CardContent, CardDescription, CardHeader, CardTitle, ChartAnnotation, ChartContainer, ChartReference, LineChart } from 'altitude-design';
59
+
60
+ const uptime = Array.from({ length: 30 }, (_, i) => ({ day: `2026-03-${String(i + 1).padStart(2, '0')}`, uptime: [0.9995, 0.9991, 0.9998, 0.9962, 0.9971, 0.9993, 0.9997, 0.9999, 0.9994, 0.9988, 0.9996, 0.9999, 0.9997, 0.9921, 0.9984, 0.9995, 0.9998, 0.9996, 0.9999, 0.9997, 0.9993, 0.9996, 0.9998, 0.9999, 0.9995, 0.9997, 0.9999, 0.9998, 0.9996, 0.9999][i] }));
61
+ const pct = new Intl.NumberFormat('en', { style: 'percent', maximumFractionDigits: 2 });
62
+ const usd = new Intl.NumberFormat('en', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
63
+ const weekA = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((day, i) => ({ day, cost: [412, 455, 498, 430, 610, 220, 190][i] }));
64
+ const weekB = weekA.map((d, i) => ({ ...d, cost: [520, 590, 640, 575, 780, 300, 260][i] }));
65
+ const teams = [['Search', 92], ['Checkout', 81], ['Accounts', 74], ['Messaging', 66], ['Billing', 58]].map(([team, score]) => ({ team, score }));
66
+ const deploys = Array.from({ length: 21 }, (_, i) => ({ day: `2026-04-${String(i + 1).padStart(2, '0')}`, latency: 180 + Math.round(30 * Math.sin(i / 3)) + (i > 9 ? -40 : 0) + (i === 14 ? 60 : 0) }));
67
+ function Chart({ id, title, sub, children, ...props }) {
68
+ return (
69
+ <Card>
70
+ <CardHeader><CardTitle id={id}>{title}</CardTitle><CardDescription>{sub}</CardDescription></CardHeader>
71
+ <CardContent><ChartContainer aria-labelledby={id} aspect={1.9} {...props}>{children}</ChartContainer></CardContent>
72
+ </Card>
73
+ );
74
+ }
75
+ export default function App() {
76
+ const [later, setLater] = React.useState(false);
77
+ const [picked, setPicked] = React.useState(null);
78
+ return (
79
+ <div className="cr" data-tells-ignore="LA4 a gallery of references in each chart, to compare">
80
+ <Chart id="r1" title="Uptime" sub="March, against the SLO" config={{ uptime: { label: 'Uptime', format: pct.format } }}>
81
+ <LineChart data={uptime} x="day">
82
+ <ChartReference y={[0.995, 1]} label="Normal range" />
83
+ <ChartReference y={0.999} label="Target 99.9%" />
84
+ <ChartReference y={0.99} label="Limit 99%" tone="critical" />
85
+ <ChartAnnotation x="2026-03-14" label="DNS outage" />
86
+ </LineChart>
87
+ </Chart>
88
+ <Card>
89
+ <CardHeader><CardTitle id="r2">Cloud spend</CardTitle><CardDescription>Against the daily budget; the median moves with the week</CardDescription></CardHeader>
90
+ <CardContent>
91
+ <ChartContainer aria-labelledby="r2" aspect={2.2} config={{ cost: { label: 'Cost', format: usd.format } }}>
92
+ <BarChart data={later ? weekB : weekA} x="day">
93
+ <ChartReference y={700} label="Budget $700" tone="caution" />
94
+ <ChartReference y="median" label="Median" />
95
+ </BarChart>
96
+ </ChartContainer>
97
+ <div className="row" style={{ marginTop: 12 }}><Button size="sm" onClick={() => setLater((v) => !v)}>{later ? 'Last week' : 'This week'}</Button></div>
98
+ </CardContent>
99
+ </Card>
100
+ <Chart id="r3" title="Accessibility score" sub="By team, against the bar of 75" config={{ score: { label: 'Score' } }}>
101
+ <BarChart data={teams} x="team" layout="horizontal"><ChartReference y={75} label="Bar 75" /></BarChart>
102
+ </Chart>
103
+ <Chart id="r4" title="p95 latency" sub={picked ? `Picked: ${picked}` : 'Events stack into rows where they would overlap'} config={{ latency: { label: 'p95', format: (v) => `${v} ms` } }}>
104
+ <LineChart data={deploys} x="day">
105
+ <ChartReference y="mean" label="Average" />
106
+ <ChartAnnotation x="2026-04-10" label="Cache rollout" onSelect={() => setPicked('Cache rollout')} />
107
+ <ChartAnnotation x="2026-04-11" label="v2 launch" onSelect={() => setPicked('v2 launch')} />
108
+ <ChartAnnotation x="2026-04-15" label="Incident 412" />
109
+ </LineChart>
110
+ </Chart>
111
+ </div>
112
+ );
113
+ }
114
+ ```
115
+ <!-- /example -->
@@ -0,0 +1,101 @@
1
+ # ChartTooltip
2
+
3
+ Reading exact values off a chart. Point at a date on a line chart, or at a bar, and a hairline marks that x while a readout lists every series' value there, so you never have to land on a 2px line to get a number. It's on for every chart; `ChartTooltip` only changes what it shows.
4
+
5
+ **Why it floats.** The readout is live: it exists while you're reading, so it's a level-3 floating surface like `Tooltip` (`surface-floating`, `elev-3`, `radius-lg`). The crosshair and the ringed points are marks on the plot, on the ground: a 1px `line-control` rule snapped to the nearest x, and each line's point there ringed in the plot's own surface colour (an outline, not height). Bars don't change at all, because hovering a static bar does nothing.
6
+
7
+ **How it moves.** It rises 4px the first time, as its shadow grows from `elev-1` to `elev-3` over `dur-travel-3`, then doesn't rise again while you move. It steps from one x to the next with no easing: it follows the data, not each pixel of the pointer, so it never reads as a control drifting (E10). It opens on whichever side of the point has room, so it never covers it, and leaves in 70% of its entry time. Coming back within 400ms shows it at once, warm, shared with `Tooltip`. On touch, press and hold shows it, dragging scrubs along x, and lifting your finger leaves it until you tap elsewhere. Under reduced motion it fades with no travel.
8
+
9
+ **Without height.** Under forced colours it has a 2px `CanvasText` border and its keys take each series' dash. With `contrast="more"` it stands on the hard ink slab every level-3 surface gets.
10
+
11
+ **Accessibility.** The readout is `aria-hidden` decoration over data that's reachable elsewhere: the chart's summary, the keyboard path through the data, which opens the same readout on the focused record (#63), and the table view (#64). Its labels come from your data, so they're inserted as text, never as HTML.
12
+
13
+ **Rows.** The heading is the x value (formatted by the container, or by `labelFormat`). Each row puts the value first (`ink`, weight 500, tabular figures) and the series name second (`ink-muted`), keyed by a short stroke of the series colour. Only series that are shown and have a value there are listed.
14
+
15
+ **Props.** `ChartTooltip` takes `content`: a `ChartTooltipContent` with your options. `ChartTooltipContent` takes:
16
+
17
+ - `indicator`: `'line'` (default), `'dot'` or `'dashed'`;
18
+ - `labelFormat`, for the heading;
19
+ - `hideLabel` and `hideIndicator`.
20
+
21
+ Turn the readout off with `tooltip={false}` on the chart type or the container. From your own marks or keyboard code, `useChart().active` is the record being read and `setActive(index)` opens the readout on one.
22
+
23
+ **Pinning a reading.** `pinnable` on a `LineChart`, `BarChart` or `AreaChart` lets people keep a number: with the readout open, a click on the plot (or P on the keyboard path, or a quick tap while the touch readout is open) sets the reading down. The live readout lands on the record as a `Badge` plate at the top of the plot, from level 3 to the ground over `dur-travel-3` on `ease-settle`, then takes the afterglow once. It has a pin icon, so it isn't read as a `ChartAnnotation`, a 1px `line-control` leader to its x, and a ghost remove button named "Remove pin, Mar 9, 2026". With two pins, a strip under the x axis says the difference ("+24% over 28 days", or per series), and pinning the second says it aloud ("Apr 6, 2026 is 52% higher than Mar 2, 2026."). `pinnable` allows two; a number allows that many, and a new pin replaces the oldest. Escape on the chart removes the last pin before it clears a selection. Where a click selects or drills, it keeps doing that and only P pins. `pins` and `onPinsChange` control them, to keep them in the URL. The readout's action line says how to pin ("Click to pin", "P to pin", "Tap to pin").
24
+
25
+ **You provide:** nothing, for the default. To change it, a `ChartTooltip` inside the chart type or the container.
26
+
27
+ ```tsx
28
+ <ChartContainer config={config}>
29
+ <LineChart data={months} x="month">
30
+ <ChartTooltip content={<ChartTooltipContent indicator="dot" labelFormat={(m) => monthLong(m)} />} />
31
+ </LineChart>
32
+ </ChartContainer>
33
+ ```
34
+
35
+ - Do keep it on: it's how people get exact numbers without hunting for a line.
36
+ - Do format the heading the way people say the x ("Week of Mon, April 6"), not as stored.
37
+ - Don't put anything in it that can't be reached another way: it's a convenience over the summary, the keyboard path and the table.
38
+ - Don't add buttons or links to it; it doesn't take the pointer, and it moves away as soon as the pointer does.
39
+
40
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
41
+ ## Example
42
+
43
+ `ChartTooltip` as first used in its preview:
44
+
45
+ ```tsx
46
+ import { ChartTooltip, ChartTooltipContent } from 'altitude-design';
47
+
48
+ <ChartTooltip content={<ChartTooltipContent indicator="dot" labelFormat={(w) => 'Week of ' + long.format(new Date(w + 'T00:00:00Z'))} />} />
49
+ ```
50
+
51
+ The full preview, as rendered in Storybook and checked in CI (`ct` is the preview page's own layout class, not Altitude's):
52
+
53
+ ```tsx
54
+ import * as React from 'react';
55
+ import { BarChart, Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer, ChartTooltip, ChartTooltipContent, LineChart, useChart } from 'altitude-design';
56
+
57
+ // The readout is on for every chart; point at one (or press and hold on touch). Here it's opened with setActive,
58
+ // the same call the keyboard path uses, so the preview shows it without a pointer.
59
+ const weeks = ['2026-03-02', '2026-03-09', '2026-03-16', '2026-03-23', '2026-03-30', '2026-04-06', '2026-04-13'].map((week, i) => ({ week, opened: [31, 38, 35, 44, 41, 47, 52][i], closed: [28, 30, 36, 39, 43, 45, 49][i] }));
60
+ const long = new Intl.DateTimeFormat('en', { weekday: 'short', day: 'numeric', month: 'long', timeZone: 'UTC' });
61
+ function Open({ at }) {
62
+ const { setActive } = useChart();
63
+ React.useEffect(() => { setActive(at); }, []);
64
+ return null;
65
+ }
66
+ export default function App() {
67
+ const [pins, setPins] = React.useState(['2026-03-09', '2026-04-06']);
68
+ return (
69
+ <div className="ct" data-tells-ignore="LA4 a gallery of the readout in each chart, to compare">
70
+ <Card>
71
+ <CardHeader><CardTitle id="t1">Issues opened and closed</CardTitle><CardDescription>The default readout, open on 23 Mar</CardDescription></CardHeader>
72
+ <CardContent>
73
+ <ChartContainer aria-labelledby="t1" aspect={1.8} config={{ opened: { label: 'Opened' }, closed: { label: 'Closed' } }}>
74
+ <LineChart data={weeks} x="week" /><Open at={3} />
75
+ </ChartContainer>
76
+ </CardContent>
77
+ </Card>
78
+ <Card>
79
+ <CardHeader><CardTitle id="t2">Same data, your own content</CardTitle><CardDescription>indicator="dot" and a long date</CardDescription></CardHeader>
80
+ <CardContent>
81
+ <ChartContainer aria-labelledby="t2" aspect={1.8} config={{ opened: { label: 'Opened' }, closed: { label: 'Closed' } }}>
82
+ <BarChart data={weeks} x="week">
83
+ <ChartTooltip content={<ChartTooltipContent indicator="dot" labelFormat={(w) => 'Week of ' + long.format(new Date(w + 'T00:00:00Z'))} />} />
84
+ </BarChart>
85
+ <Open at={5} />
86
+ </ChartContainer>
87
+ </CardContent>
88
+ </Card>
89
+ <Card>
90
+ <CardHeader><CardTitle id="t3">Pinned readings</CardTitle><CardDescription>pinnable: click (or P) sets a reading down; two compare</CardDescription></CardHeader>
91
+ <CardContent>
92
+ <ChartContainer aria-labelledby="t3" aspect={1.8} config={{ opened: { label: 'Opened' } }}>
93
+ <LineChart data={weeks} x="week" pinnable pins={pins} onPinsChange={setPins} />
94
+ </ChartContainer>
95
+ </CardContent>
96
+ </Card>
97
+ </div>
98
+ );
99
+ }
100
+ ```
101
+ <!-- /example -->
@@ -0,0 +1,46 @@
1
+ # Checkbox
2
+
3
+ Toggles one independent option. A checkbox is built like the Switch and the RadioGroup, so the three read as one family.
4
+
5
+ - **Off:** an empty 22px trench (`surface-trench`, `trench` shadow, `line-control` rim), like the uncovered part of a switch track or an unchosen radio.
6
+ - **On:** the accent pours in from the centre (on `ease-settle`, as the switch fill follows its thumb), and since the fill covers the rim too, fill and outline are one shape. A **raised white check** is set on it: `on-accent` with a small drop shadow, the checkbox's equivalent of the switch thumb. It draws once the fill has landed. `indeterminate` sets a dash instead.
7
+ - **Hover** is feedforward: an empty box darkens its rim and shows a faint ghost of the check it would get; a checked box lifts its mark half a pixel. **Pressing** deepens the trench and squeezes the mark. **Disabled** matches a disabled switch: a flat track and a grey mark.
8
+
9
+ **You provide:** `label`, optional `description`, and `checked`/`onCheckedChange` (or `defaultChecked`). It's a real `<input type="checkbox">`, so it works in forms with `name` and `value`.
10
+
11
+ - Use a Switch when the change takes effect immediately; a Checkbox is for choices submitted together.
12
+
13
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
14
+ ## Example
15
+
16
+ `Checkbox` as first used in its preview:
17
+
18
+ ```tsx
19
+ import { Checkbox } from 'altitude-design';
20
+
21
+ <Checkbox label="Select all" checked={a && b} indeterminate={a !== b} onCheckedChange={(c) => { setA(c); setB(c); }} />
22
+ ```
23
+
24
+ The full preview, as rendered in Storybook and checked in CI (`col`, `family`, `cap` are the preview page's own layout classes, not Altitude's):
25
+
26
+ ```tsx
27
+ import * as React from 'react';
28
+ import { Checkbox, Switch } from 'altitude-design';
29
+
30
+ export default function App() {
31
+ const [a, setA] = React.useState(true);
32
+ const [b, setB] = React.useState(false);
33
+ return (
34
+ <div className="col">
35
+ <Checkbox label="Select all" checked={a && b} indeterminate={a !== b} onCheckedChange={(c) => { setA(c); setB(c); }} />
36
+ <div className="col" style={{ paddingLeft: 34 }}>
37
+ <Checkbox label="Weekly digest" description="A summary every Monday morning." checked={a} onCheckedChange={setA} />
38
+ <Checkbox label="Product updates" checked={b} onCheckedChange={setB} />
39
+ </div>
40
+ <Checkbox label="Disabled" disabled defaultChecked />
41
+ <div className="family"><span className="cap">Same build as</span><Switch defaultChecked aria-label="Switch" /><Switch aria-label="Switch off" /></div>
42
+ </div>
43
+ );
44
+ }
45
+ ```
46
+ <!-- /example -->
@@ -0,0 +1,137 @@
1
+ # Checklist
2
+
3
+ A list you write into, one line per item: to-dos, acceptance criteria, a packing list, a runbook's steps. Enter makes the next item, Backspace on an empty item removes it, Tab nests it, and each item has a checkbox. It's the keyboard-first counterpart to a list of structured records: here an item is its text, and the list is edited the way people write, with the outliner conventions of Workflowy, Logseq, Things, Notion and GitHub task lists.
4
+
5
+ ```tsx
6
+ <Checklist<Task>
7
+ aria-label="Launch tasks"
8
+ items={tasks} // { id, text, done, children? }
9
+ getKey={(t) => t.id}
10
+ getText={(t) => t.text}
11
+ isDone={(t) => t.done}
12
+ getChildren={(t) => t.children}
13
+ nested
14
+ onChange={(op) => { setTasks((list) => apply(list, op)); return api.applyChecklistOp(op); }}
15
+ onUndo={(op) => setTasks((list) => revert(list, op))}
16
+ />
17
+ ```
18
+
19
+ Each action reaches `onChange` as one op (`add`, `edit`, `toggle`, `remove`, `move`, `indent`, `outdent`), so persistence can be granular and collaborators' edits merge per item. Apply the op to `items` synchronously (the line you're writing in must not wait on the network), then return `{ ok: true }`, nothing, or `{ ok: false, error }`, synchronously or as a promise. A failure marks the item and offers Retry on the line; the text stays. New items get ids from `createId` (default a random UUID), so you can insert them at once.
20
+
21
+ ## No Save button, on purpose
22
+
23
+ `InlineEdit` and `EditRegion` have an explicit Save, and blur does nothing. A checklist where every line needs Save isn't a checklist, so here **the line is the editor and it commits when you leave it**: Enter, ↑/↓, Tab, Escape, a click elsewhere. Scoped to this component, it's an explicit act on a single field, and a refused change keeps the text in place with Retry, so nothing is lost silently.
24
+
25
+ ## Elevation
26
+
27
+ - **Items** are the record, flush on the ground. The line is a plain text input with a focus ring, not a raised editor.
28
+ - **The last line** (“Add an item”) is an opening: a well while it's focused or holds text, dashed in forced colours.
29
+ - **The checkbox** is `Checkbox`: an empty trench, then the accent pours in with a raised mark.
30
+ - **Done** is pressed into the record: the text goes `ink-muted` with a strike drawn left to right, and the item stays in place (or, with `sortDone`, goes after the open ones).
31
+ - **A new item** opens as a well under the line (`ease-sink`). **Pending** writes hover at level 2 after `dur-grace`; a **failed** one drops to a trench with a critical rim and Retry.
32
+ - **Removed**: the line glides shut where it was (`dur-travel-2`, `ease-glide`) while the caret is already on the line above. There's no receipt and no hold: removal only happens from the keyboard, on an empty line or by merging a line's text into the one above, so nothing was lost. Undo is Ctrl+Z (⌘Z on Apple platforms) through `onUndo`, and the polite announcement says so: “Empty item removed. Undo with Ctrl+Z.” Nothing is added after the list. Under reduced motion the line closes at once.
33
+
34
+ Without height: nested `ul`s carry the structure; each checkbox is named by its item; done items say “done”; the list's description lists the keys; a polite status announces indents, moves, removals and errors. `contrast="more"`: the open well gets a solid inner top edge.
35
+
36
+ ## Keyboard (while writing a line)
37
+
38
+ | Key | |
39
+ |---|---|
40
+ | Enter | split at the caret: the text after it becomes the next item (at the end: a new empty item) |
41
+ | Backspace at the start | on an empty item: remove it and go to the end of the line above. Otherwise merge into the line above, caret at the join |
42
+ | Tab / Shift+Tab | indent under the item above / outdent (only with `nested`) |
43
+ | Escape | leave the line and focus the item; Tab then moves on, so the list never traps focus |
44
+ | ↑ / ↓ | the line above / below, keeping the caret's position |
45
+ | Alt+↑ / Alt+↓ | move the item among its siblings |
46
+ | Ctrl/Cmd+Enter | check or uncheck it |
47
+ | Ctrl/Cmd+Z | undo the last add, remove, move or indent, through `onUndo` |
48
+
49
+ Nothing acts during IME composition. Pasting several lines adds one item per line; GitHub task-list markers (`- [ ]`, `- [x]`) and, with `nested`, two-space indentation are read.
50
+
51
+ ## Props
52
+
53
+ | Prop | |
54
+ |---|---|
55
+ | `items`, `getKey`, `getText`, `isDone`, `getChildren` | the nested data |
56
+ | `nested`, `sortDone` | allow Tab nesting (default false); show done items last (default false) |
57
+ | `onChange`, `onUndo`, `createId` | the ops, their reversal, and ids for new items |
58
+ | `placeholder`, `disabled`, `aria-label`, `aria-labelledby` | |
59
+
60
+ ## You provide
61
+
62
+ The items, applying and persisting each op, and reversing ops for Undo.
63
+
64
+ ## Do and don't
65
+
66
+ - **Do** use it when items are just text. When each item is a record with several fields, use a list of records with its own editor.
67
+ - **Don't** use it for long-form text; a line is one line.
68
+
69
+ ## Demo and verification
70
+
71
+ The `Checklist` section of `build/previews.txt` shows nested launch tasks (a fictional service refuses “TBD”) and a flat packing list with `sortDone`. `node checks/checklist.mjs` (from `build/`; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers Enter, split and merge at the caret, Backspace on an empty line, Tab and Shift+Tab, Escape then Tab, ↑/↓, Alt+↑, Ctrl+Enter, IME Enter, a refused edit with Retry, remove and Ctrl+Z, the add line, task-list paste, `sortDone`, and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
72
+
73
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
74
+ ## Example
75
+
76
+ `Checklist` as first used in its preview:
77
+
78
+ ```tsx
79
+ import { Checklist } from 'altitude-design';
80
+
81
+ <Checklist aria-labelledby="cl-launch" nested {...props} {...launch} />
82
+ ```
83
+
84
+ The full preview, as rendered in Storybook and checked in CI (`cl-demo` is the preview page's own layout class, not Altitude's):
85
+
86
+ ```tsx
87
+ import * as React from 'react';
88
+ import { Checklist } from 'altitude-design';
89
+
90
+ // A tiny store that applies the Checklist's ops to nested items, and reverses them for Undo.
91
+ const clStrip = (list, id) => { let found = null; const out = list.filter(x => { if (x.id === id) { found = x; return false; } return true; }).map(x => { if (found || !x.children) return x; const [kids, hit] = clStrip(x.children, id); if (hit) found = hit; return hit ? { ...x, children: kids } : x; }); return [out, found]; };
92
+ const clInsert = (list, parentId, index, node) => parentId === null ? [...list.slice(0, index), node, ...list.slice(index)]
93
+ : list.map(x => x.id === parentId ? { ...x, children: [...(x.children || []).slice(0, index), node, ...(x.children || []).slice(index)] } : x.children ? { ...x, children: clInsert(x.children, parentId, index, node) } : x);
94
+ const clUpdate = (list, id, patch) => list.map(x => x.id === id ? { ...x, ...patch } : x.children ? { ...x, children: clUpdate(x.children, id, patch) } : x);
95
+ const clApply = (list, op) => {
96
+ if (op.type === 'add') return clInsert(list, op.parentId, op.index, { id: op.id, text: op.text, done: op.done });
97
+ if (op.type === 'edit') return clUpdate(list, op.id, { text: op.text });
98
+ if (op.type === 'toggle') return clUpdate(list, op.id, { done: op.done });
99
+ const [rest, node] = clStrip(list, op.id);
100
+ if (op.type === 'remove') return rest;
101
+ return clInsert(rest, op.parentId, op.index, node);
102
+ };
103
+ function useChecklist(seed, refuse) {
104
+ const [items, setItems] = React.useState(seed);
105
+ const removedNodes = React.useRef(new Map());
106
+ const onChange = op => {
107
+ if (refuse && refuse(op)) return { ok: false, error: refuse(op) };
108
+ if (op.type === 'remove') removedNodes.current.set(op.id, clStrip(items, op.id)[1]);
109
+ setItems(list => clApply(list, op));
110
+ };
111
+ const onUndo = op => setItems(list => {
112
+ if (op.type === 'add') return clStrip(list, op.id)[0];
113
+ if (op.type === 'remove') return clInsert(list, op.parentId, op.index, removedNodes.current.get(op.id));
114
+ const [rest, node] = clStrip(list, op.id); return clInsert(rest, op.from.parentId, op.from.index, node);
115
+ });
116
+ return { items, onChange, onUndo };
117
+ }
118
+ const CL_LAUNCH = [
119
+ { id: 'venue', text: 'Book the venue', done: true },
120
+ { id: 'invites', text: 'Send invitations', done: false, children: [
121
+ { id: 'list', text: 'Confirm the guest list', done: true }, { id: 'design', text: 'Design the card', done: false } ] },
122
+ { id: 'catering', text: 'Choose catering', done: false },
123
+ ];
124
+ const CL_PACK = [{ id: 'p1', text: 'Passport', done: false }, { id: 'p2', text: 'Charger', done: true }, { id: 'p3', text: 'Rain jacket', done: false }];
125
+ export default function App() {
126
+ const launch = useChecklist(CL_LAUNCH, op => op.type === 'edit' && /tbd/i.test(op.text) ? 'Write the task, not “TBD”.' : undefined);
127
+ const pack = useChecklist(CL_PACK);
128
+ const props = { getKey: t => t.id, getText: t => t.text, isDone: t => t.done, getChildren: t => t.children };
129
+ return <div className="cl-demo">
130
+ <section><h3 id="cl-launch">Launch tasks</h3><p>Nested: Tab and Shift+Tab indent and outdent. Backspace on an empty line removes it; Ctrl+Z (⌘Z) brings it back. Writing “TBD” is refused.</p>
131
+ <Checklist aria-labelledby="cl-launch" nested {...props} {...launch} /></section>
132
+ <section><h3 id="cl-pack">Packing</h3><p>Flat, done items last.</p>
133
+ <Checklist aria-labelledby="cl-pack" sortDone {...props} {...pack} placeholder="Add something to pack" /></section>
134
+ </div>;
135
+ }
136
+ ```
137
+ <!-- /example -->
@@ -0,0 +1,72 @@
1
+ # CodeBlock
2
+
3
+ Code to read and copy: install commands, API examples in several languages, webhook payloads, config, code an agent wrote.
4
+
5
+ **Elevation.** Code shown to read and copy is the record, so it lies flush on the ground: a 1px `line` rim, mono type and no shadow. It isn't a `Card`, because code isn't a live surface. The header carries its name, a segmented `Tabs` switch between forms of the same thing (npm, pnpm; curl, Node, Python), and a ghost `CopyButton` ("Copy code"). Highlighted lines are `accent-soft` bands with an `accent-ink` edge behind the text. Line numbers sit in an `ink-faint` gutter. **Code still arriving** (`streaming`, from an agent) isn't the record yet: it has a dashed `line-control` rule down its left, says "Writing…", is `aria-busy`, and can't be copied until it's done.
6
+
7
+ **Syntax colours are yours.** Altitude ships no highlighter. Pass `syntax={(code, language) => …}` (Shiki, Prism, your own) and it renders whatever you return. The default token classes (`tok-comment`, `tok-keyword`, `tok-string`, and Prism's and highlight.js's equivalents) use `ink-muted`, `accent-ink` and `ink`, never the `data-*` palette. Colours carry nothing on their own.
8
+
9
+ **Overflow.** Code taller than `maxHeight` (default 360) or wider than its box scrolls. Only then does the scroll area become a focusable, named region (`tabIndex=0`, `role="region"`), so keyboard users can scroll it with the arrows.
10
+
11
+ **Accessibility.** A `<figure>` named by its title, holding `<pre><code>`. Line numbers are `aria-hidden`. Highlighted lines are said in words before the code ("Lines 3 and 7 are highlighted."). The form switch is `Tabs`, with `aria-selected`. Under forced colours the rim is `CanvasText` and highlighted lines are `Mark`. Under `contrast="more"` the rim is `line-control`. Switching forms cross-fades the code over `dur-travel-1` and nothing travels.
12
+
13
+ **Props:** `code` and `language` for one block, or `tabs` (`{ label, code, language? }[]`); `title`; `highlight` (line numbers from 1); `syntax`; `lineNumbers`; `streaming`; `maxHeight`; `copyable` (default true).
14
+
15
+ **You provide:** the code, a title (a filename, or what it does), and a highlighter if you want colour.
16
+
17
+ ```tsx
18
+ <CodeBlock title="Install" tabs={[
19
+ { label: 'npm', language: 'sh', code: 'npm install altitude-design' },
20
+ { label: 'pnpm', language: 'sh', code: 'pnpm add altitude-design' },
21
+ ]} />
22
+ <CodeBlock title="invoice.paid" language="json" code={payload} highlight={[3]} lineNumbers />
23
+ ```
24
+
25
+ - Do give every block a title, so it's named and so people know what to paste where.
26
+ - Do use tabs for the same thing in several forms, not for different things.
27
+ - Don't raise a code block into a card or give it a shadow.
28
+ - Don't colour code with the chart palette.
29
+
30
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
31
+ ## Example
32
+
33
+ `CodeBlock` as first used in its preview:
34
+
35
+ ```tsx
36
+ import { CodeBlock } from 'altitude-design';
37
+
38
+ <CodeBlock title="Install" tabs={[{ label: 'npm', language: 'sh', code: 'npm install altitude-design' }, { label: 'pnpm', language: 'sh', code: 'pnpm add altitude-design' }, { label: 'yarn', language: 'sh', code: 'yarn add altitude-design' }]} />
39
+ ```
40
+
41
+ The full preview, as rendered in Storybook and checked in CI (`cbk` is the preview page's own layout class, not Altitude's):
42
+
43
+ ```tsx
44
+ import * as React from 'react';
45
+ import { CodeBlock } from 'altitude-design';
46
+
47
+ const payload = `{
48
+ "id": "evt_1Q8xZ2",
49
+ "type": "invoice.paid",
50
+ "created": 1793304000,
51
+ "data": {
52
+ "invoice": "INV-0042",
53
+ "amount": 4900,
54
+ "currency": "eur"
55
+ }
56
+ }`;
57
+ // A stand-in highlighter, to show the syntax prop: comments in ink-muted, keywords in accent-ink.
58
+ const tiny = (code) => code.split(/(\/\/.*$|\b(?:const|await|import|from)\b)/m).map((part, i) => part.startsWith('//') ? <span key={i} className="tok-comment">{part}</span> : /^(const|await|import|from)$/.test(part) ? <span key={i} className="tok-keyword">{part}</span> : part);
59
+ export default function App() {
60
+ const [streamed, setStreamed] = React.useState('');
61
+ const full = "import { Altitude } from 'altitude-design';\n// Verify the signature before trusting the event\nconst event = await verify(request, secret);";
62
+ React.useEffect(() => { let i = 0; const t = setInterval(() => { i = Math.min(full.length, i + 3); setStreamed(full.slice(0, i)); if (i >= full.length) clearInterval(t); }, 60); return () => clearInterval(t); }, []);
63
+ return (
64
+ <div className="cbk">
65
+ <CodeBlock title="Install" tabs={[{ label: 'npm', language: 'sh', code: 'npm install altitude-design' }, { label: 'pnpm', language: 'sh', code: 'pnpm add altitude-design' }, { label: 'yarn', language: 'sh', code: 'yarn add altitude-design' }]} />
66
+ <CodeBlock title="invoice.paid" language="json" code={payload} highlight={[3, 7]} lineNumbers maxHeight={220} />
67
+ <CodeBlock title="webhook.ts" language="ts" code={streamed} syntax={tiny} streaming={streamed.length < full.length} />
68
+ </div>
69
+ );
70
+ }
71
+ ```
72
+ <!-- /example -->
@@ -0,0 +1,69 @@
1
+ # ColorPicker and ColorSwatchPicker
2
+
3
+ A brand colour for a white-label portal, a label or tag colour, a calendar colour. `AltitudeProvider accent` already takes any colour and derives a scheme from it. This is the settings control that chooses one.
4
+
5
+ **Elevation: choices are switches.** Swatches are built like `RadioGroup`. Each swatch is a trench (with a `line-control` rim) that its colour pours into. **The chosen one carries the raised white mark**, one mark that travels between swatches on `ease-glide`, as the radio thumb does. Pointing at another swatch shows a ghost of the mark where it would land (feedforward).
6
+
7
+ **Any colour.** With `allowCustom`:
8
+ - **The hex field** is an `Input` with a chip of the colour. It fills only when it holds a whole colour; a partial one ("0f8") is still an opening when you leave it.
9
+ - **"Custom…"** opens a `Popover` (level 3) with a saturation/brightness area and a hue strip. Both are wells, and their handles are raised.
10
+
11
+ **Contrast.** `accent` is the product's one hue, and text sits on it, so `checkContrast` says in words, for white text and for dark text, whether each reaches 4.5:1 on the colour ("White text fails: 4.3:1", "Dark text passes: 4.8:1"), as `Badge` plates. Some text nearly always passes, so it says which.
12
+
13
+ **Accessibility.** Swatches are a [radio group](https://www.w3.org/WAI/ARIA/apg/patterns/radio/): one tab stop, and arrows move and choose. **Every swatch has a name** ("Teal"), so colour is never the only way to tell them apart, and the chosen colour's name and hex are announced. The area is a [slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) for saturation and brightness: Left and Right change saturation, Up and Down brightness, and Shift moves 10%. Its value text is "Saturation 80%, brightness 54%". The hue strip is a slider from 0 to 360°. Under forced colours swatches keep their real colour (`forced-color-adjust: none`) with a 1px `CanvasText` border, or 2px for the chosen one. Under `contrast="more"` the chosen swatch gets a hard ring. Under reduced motion the mark appears in place.
14
+
15
+ **Props.**
16
+ - `ColorPicker`: `label`, `value`, `defaultValue`, `onValueChange` (a lower-case `#rrggbb`), `swatches` (`{ value, name }[]`), `allowCustom`, `checkContrast`.
17
+ - `ColorSwatchPicker` (the swatches alone): `swatches`, `value`, `defaultValue`, `onValueChange`, `aria-label`.
18
+
19
+ **You provide:** a named palette (six to eight swatches), and whether any colour is allowed.
20
+
21
+ ```tsx
22
+ <ColorPicker label="Brand colour" value={brand} onValueChange={setBrand}
23
+ swatches={[{ value: '#0f8a6b', name: 'Teal' }, { value: '#2f5fd0', name: 'Blue' }]}
24
+ allowCustom checkContrast />
25
+ ```
26
+
27
+ - Do name every swatch.
28
+ - Do turn on `checkContrast` wherever the colour will carry text (an accent, a button).
29
+ - Don't offer a free colour where a palette will do; custom colours make labels hard to tell apart.
30
+ - Don't use the `data-*` chart palette as label colours; it means series.
31
+
32
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
33
+ ## Example
34
+
35
+ `ColorPicker` as first used in its preview:
36
+
37
+ ```tsx
38
+ import { ColorPicker } from 'altitude-design';
39
+
40
+ <ColorPicker label="Brand colour" value={color} onValueChange={setColor} swatches={brand} allowCustom checkContrast />
41
+ ```
42
+
43
+ The full preview, as rendered in Storybook and checked in CI (`cpk`, `col`, `demo`, `cap` are the preview page's own layout classes, not Altitude's):
44
+
45
+ ```tsx
46
+ import * as React from 'react';
47
+ import { AltitudeProvider, Button, ColorPicker } from 'altitude-design';
48
+
49
+ const brand = [
50
+ { value: '#0f8a6b', name: 'Teal' }, { value: '#2f5fd0', name: 'Blue' }, { value: '#7d3c6f', name: 'Plum' },
51
+ { value: '#c23a6b', name: 'Rose' }, { value: '#c8571f', name: 'Ember' }, { value: '#d9a521', name: 'Ochre' },
52
+ ];
53
+ const labels = [{ value: '#66738a', name: 'Slate' }, { value: '#2f8f4e', name: 'Green' }, { value: '#c9372c', name: 'Red' }, { value: '#16869e', name: 'Cyan' }];
54
+ export default function App() {
55
+ const [color, setColor] = React.useState('#0f8a6b');
56
+ const [tag, setTag] = React.useState('#2f8f4e');
57
+ return (
58
+ <div className="cpk">
59
+ <ColorPicker label="Brand colour" value={color} onValueChange={setColor} swatches={brand} allowCustom checkContrast />
60
+ <div className="col">
61
+ <ColorPicker label="Label colour" value={tag} onValueChange={setTag} swatches={labels} />
62
+ <div className="demo"><span className="cap">Preview:</span><AltitudeProvider accent={color}><Button variant="primary">Upgrade plan</Button></AltitudeProvider></div>
63
+ <p className="cap">The check says, for white text and for dark text, whether it reaches 4.5:1 on the colour. Arrow keys move the mark between swatches; Custom… opens a full picker.</p>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
68
+ ```
69
+ <!-- /example -->
@@ -0,0 +1,51 @@
1
+ # Combobox
2
+
3
+ An editable Select: type to narrow a long list, then choose. What you type is a query, not yet a value.
4
+
5
+ **Elevation.** The field is the same opening as `Select` and `Input`. The difference is what counts as filled: typing doesn't fill a Combobox, choosing does. So while you type, the field keeps its back wall showing under your text (lit and rimmed in accent, because it has focus); only when an option is chosen does its floor rise flush. A form shows which comboboxes still hold a mere query. Invalid drops it to trench depth.
6
+
7
+ **The list** rises from the field's edge at level 3 with one gliding highlight, as in Select, and narrows as you type: options are ranked by the CommandPalette's matching (exact, then prefix, word start, substring, letters in order), the best match is first and highlighted, and the letters that matched are drawn in `accent-ink`. The chosen option wears the Checkbox family's tick. A new value settles in with a brief afterglow.
8
+
9
+ **Keyboard and screen readers.** The WAI-ARIA combobox pattern with a listbox popup: the text field has `role="combobox"`, `aria-expanded`, `aria-autocomplete="list"` and `aria-activedescendant`, so focus stays in the field. Typing opens and filters; ↓ and ↑ move (↓ also opens it, Alt+↓ without moving); Enter chooses; Escape closes and puts back the previous value. Home and End stay with the text. Leaving the field, by Tab or by clicking away, commits what's typed: an exact name chooses that option, an empty field clears the value, anything else goes back to the value it had. The chevron opens the list for pointer users.
10
+
11
+ **On a phone.** The list stays under the field you're typing in by default (`presentation="auto"` or `"anchored"`), so the on-screen keyboard hides neither. `presentation="sheet"` opens it at the bottom edge instead.
12
+
13
+ **You provide:** `items` (`{ value, label, textValue?, description?, disabled? }`), `value`/`onValueChange` or `defaultValue`, a `placeholder` that invites typing ("Search people"), optionally `filter` (return 0 or false to hide, higher to rank first), `empty` (default "No matches") and `name` for forms. Wrap it in `Field`.
14
+
15
+ - Do use it when the list is long enough that scrolling is slower than typing (people, countries, repositories).
16
+ - Don't use it for free text that may not be in the list; it only accepts listed values.
17
+
18
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
19
+ ## Example
20
+
21
+ `Combobox` as first used in its preview:
22
+
23
+ ```tsx
24
+ import { Combobox } from 'altitude-design';
25
+
26
+ <Combobox items={people} value={who} onValueChange={setWho} placeholder="Search people" />
27
+ ```
28
+
29
+ The full preview, as rendered in Storybook and checked in CI (`col`, `grid` are the preview page's own layout classes, not Altitude's):
30
+
31
+ ```tsx
32
+ import * as React from 'react';
33
+ import { Combobox, Field } from 'altitude-design';
34
+
35
+ const people = ["Ada Obi", "Eli Ward", "Ivy Chen", "Jon Reyes", "Kai Ito", "Lena Park", "Mara Quist", "Noa Levi", "Rui Costa", "Sam Okafor", "Theo Marsh"].map((n) => ({ value: n.toLowerCase().replace(" ", "-"), label: n }));
36
+ const countries = ["Brazil", "Canada", "Germany", "Japan", "Peru", "Portugal", "Singapore", "Spain"].map((c) => ({ value: c.toLowerCase(), label: c }));
37
+ export default function App() {
38
+ const [who, setWho] = React.useState("");
39
+ return <div className="col">
40
+ <div className="grid">
41
+ <Field label="Assignee" hint={who ? "Assigned." : "Start typing a name."}>
42
+ <Combobox items={people} value={who} onValueChange={setWho} placeholder="Search people" />
43
+ </Field>
44
+ <Field label="Country">
45
+ <Combobox items={countries} defaultValue="portugal" defaultOpen={true} />
46
+ </Field>
47
+ </div>
48
+ </div>;
49
+ }
50
+ ```
51
+ <!-- /example -->