altitude-design 0.4.0 → 0.4.2

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 (280) hide show
  1. package/README.md +1 -0
  2. package/dist/guidance/components/AILabel/README.md +35 -0
  3. package/dist/guidance/components/Accordion/README.md +25 -0
  4. package/dist/guidance/components/AgentFleet/README.md +48 -1
  5. package/dist/guidance/components/AgentLanes/README.md +32 -0
  6. package/dist/guidance/components/AgentRequests/README.md +47 -0
  7. package/dist/guidance/components/Alert/README.md +49 -0
  8. package/dist/guidance/components/AltitudeProvider/README.md +63 -0
  9. package/dist/guidance/components/AppShell/README.md +70 -0
  10. package/dist/guidance/components/AreaChart/README.md +58 -0
  11. package/dist/guidance/components/Artifact/README.md +69 -0
  12. package/dist/guidance/components/Avatar/README.md +41 -0
  13. package/dist/guidance/components/Await/README.md +48 -0
  14. package/dist/guidance/components/Badge/README.md +44 -0
  15. package/dist/guidance/components/BarChart/README.md +62 -0
  16. package/dist/guidance/components/BarList/README.md +58 -0
  17. package/dist/guidance/components/Board/README.md +67 -0
  18. package/dist/guidance/components/Breadcrumb/README.md +31 -0
  19. package/dist/guidance/components/Button/README.md +41 -0
  20. package/dist/guidance/components/Card/README.md +38 -0
  21. package/dist/guidance/components/ChartBrush/README.md +57 -0
  22. package/dist/guidance/components/ChartContainer/README.md +77 -0
  23. package/dist/guidance/components/ChartLegend/README.md +43 -0
  24. package/dist/guidance/components/ChartReference/README.md +74 -0
  25. package/dist/guidance/components/ChartTooltip/README.md +63 -0
  26. package/dist/guidance/components/Checkbox/README.md +35 -0
  27. package/dist/guidance/components/Checklist/README.md +66 -0
  28. package/dist/guidance/components/CodeBlock/README.md +44 -0
  29. package/dist/guidance/components/ColorPicker/README.md +39 -0
  30. package/dist/guidance/components/Combobox/README.md +35 -0
  31. package/dist/guidance/components/CommandPalette/README.md +177 -0
  32. package/dist/guidance/components/CommentThread/README.md +51 -0
  33. package/dist/guidance/components/Confirmation/README.md +60 -0
  34. package/dist/guidance/components/ContextMenu/README.md +51 -0
  35. package/dist/guidance/components/Conversation/README.md +124 -0
  36. package/dist/guidance/components/CopyButton/README.md +31 -0
  37. package/dist/guidance/components/DataGrid/README.md +75 -0
  38. package/dist/guidance/components/DatePicker/README.md +49 -0
  39. package/dist/guidance/components/Dialog/README.md +175 -0
  40. package/dist/guidance/components/DiffView/README.md +77 -0
  41. package/dist/guidance/components/DotPlot/README.md +44 -0
  42. package/dist/guidance/components/Drilldown/README.md +28 -0
  43. package/dist/guidance/components/DropZone/README.md +50 -0
  44. package/dist/guidance/components/DropdownMenu/README.md +44 -0
  45. package/dist/guidance/components/EditRegion/README.md +159 -0
  46. package/dist/guidance/components/EmptyState/README.md +49 -0
  47. package/dist/guidance/components/FeatureGate/README.md +39 -0
  48. package/dist/guidance/components/Field/README.md +36 -0
  49. package/dist/guidance/components/FilterBar/README.md +63 -0
  50. package/dist/guidance/components/FormattedNumber/README.md +44 -0
  51. package/dist/guidance/components/Heatmap/README.md +71 -0
  52. package/dist/guidance/components/HoldButton/README.md +32 -0
  53. package/dist/guidance/components/HoverCard/README.md +53 -0
  54. package/dist/guidance/components/InlineCitation/README.md +32 -0
  55. package/dist/guidance/components/InlineEdit/README.md +247 -1
  56. package/dist/guidance/components/Input/README.md +36 -0
  57. package/dist/guidance/components/InputOTP/README.md +46 -0
  58. package/dist/guidance/components/ItemGroup/README.md +100 -0
  59. package/dist/guidance/components/Kbd/README.md +27 -0
  60. package/dist/guidance/components/LineChart/README.md +62 -0
  61. package/dist/guidance/components/Link/README.md +41 -0
  62. package/dist/guidance/components/Markdown/README.md +81 -0
  63. package/dist/guidance/components/Meter/README.md +52 -0
  64. package/dist/guidance/components/NotificationCenter/README.md +39 -0
  65. package/dist/guidance/components/NumberInput/README.md +37 -0
  66. package/dist/guidance/components/PageHeader/README.md +45 -0
  67. package/dist/guidance/components/Pagination/README.md +33 -0
  68. package/dist/guidance/components/PasswordInput/README.md +36 -0
  69. package/dist/guidance/components/PermissionMatrix/README.md +40 -0
  70. package/dist/guidance/components/Pile/README.md +42 -0
  71. package/dist/guidance/components/PlanPicker/README.md +37 -0
  72. package/dist/guidance/components/Popover/README.md +36 -0
  73. package/dist/guidance/components/Progress/README.md +31 -0
  74. package/dist/guidance/components/PromptInput/README.md +98 -0
  75. package/dist/guidance/components/PropertyList/README.md +80 -0
  76. package/dist/guidance/components/Question/README.md +54 -0
  77. package/dist/guidance/components/RadioGroup/README.md +37 -0
  78. package/dist/guidance/components/RelativeTime/README.md +47 -0
  79. package/dist/guidance/components/RemovalReceipt/README.md +77 -0
  80. package/dist/guidance/components/Resizable/README.md +29 -0
  81. package/dist/guidance/components/SaveBar/README.md +38 -0
  82. package/dist/guidance/components/SavedViews/README.md +58 -0
  83. package/dist/guidance/components/ScatterChart/README.md +63 -0
  84. package/dist/guidance/components/Schedule/README.md +66 -0
  85. package/dist/guidance/components/ScrollArea/README.md +28 -0
  86. package/dist/guidance/components/SecretField/README.md +46 -0
  87. package/dist/guidance/components/Select/README.md +70 -0
  88. package/dist/guidance/components/Separator/README.md +44 -0
  89. package/dist/guidance/components/Sheet/README.md +94 -0
  90. package/dist/guidance/components/Skeleton/README.md +36 -0
  91. package/dist/guidance/components/Slider/README.md +35 -0
  92. package/dist/guidance/components/SortableList/README.md +34 -0
  93. package/dist/guidance/components/Sparkline/README.md +58 -0
  94. package/dist/guidance/components/SplitButton/README.md +39 -0
  95. package/dist/guidance/components/Stat/README.md +61 -0
  96. package/dist/guidance/components/Stepper/README.md +61 -0
  97. package/dist/guidance/components/Suggestions/README.md +58 -0
  98. package/dist/guidance/components/Surface/README.md +28 -0
  99. package/dist/guidance/components/Switch/README.md +29 -0
  100. package/dist/guidance/components/SyncIndicator/README.md +54 -0
  101. package/dist/guidance/components/Table/README.md +62 -0
  102. package/dist/guidance/components/Tabs/README.md +55 -0
  103. package/dist/guidance/components/TagInput/README.md +50 -0
  104. package/dist/guidance/components/Terminal/README.md +42 -0
  105. package/dist/guidance/components/ThreadList/README.md +73 -0
  106. package/dist/guidance/components/TimeInput/README.md +37 -0
  107. package/dist/guidance/components/Timeline/README.md +55 -0
  108. package/dist/guidance/components/Toaster/README.md +38 -0
  109. package/dist/guidance/components/Toggle/README.md +52 -0
  110. package/dist/guidance/components/Toolbar/README.md +69 -0
  111. package/dist/guidance/components/Tooltip/README.md +29 -0
  112. package/dist/guidance/components/Tour/README.md +44 -0
  113. package/dist/guidance/components/Trace/README.md +92 -0
  114. package/dist/guidance/components/Tree/README.md +85 -0
  115. package/dist/guidance/index.json +117 -117
  116. package/dist/guidance/llms.txt +1 -1
  117. package/dist/guidance/manifest.json +120 -120
  118. package/dist/guidance/visual/ActionsInPlace.md +5 -5
  119. package/dist/guidance/visual/Distinctiveness.md +6 -2
  120. package/dist/guidance/visual/Research.md +14 -2
  121. package/dist/guidance/visual/Theming.md +1 -1
  122. package/dist/types/agent-fleet.d.ts +28 -0
  123. package/dist/types/ai-label.d.ts +7 -0
  124. package/dist/types/app-shell.d.ts +23 -0
  125. package/dist/types/area-chart.d.ts +8 -0
  126. package/dist/types/artifact.d.ts +14 -0
  127. package/dist/types/bar-chart.d.ts +6 -0
  128. package/dist/types/bar-list.d.ts +6 -0
  129. package/dist/types/breadcrumb.d.ts +6 -0
  130. package/dist/types/chart-brush.d.ts +6 -0
  131. package/dist/types/chart.d.ts +29 -1
  132. package/dist/types/checklist.d.ts +6 -0
  133. package/dist/types/code-block.d.ts +6 -0
  134. package/dist/types/collections.d.ts +15 -0
  135. package/dist/types/color-picker.d.ts +6 -0
  136. package/dist/types/command.d.ts +13 -0
  137. package/dist/types/comment-thread.d.ts +11 -0
  138. package/dist/types/confirmation.d.ts +8 -0
  139. package/dist/types/context-menu.d.ts +11 -0
  140. package/dist/types/controls.d.ts +174 -10
  141. package/dist/types/conversation.d.ts +21 -0
  142. package/dist/types/copy-button.d.ts +6 -0
  143. package/dist/types/core.d.ts +31 -0
  144. package/dist/types/data-grid.d.ts +25 -0
  145. package/dist/types/date-picker.d.ts +6 -0
  146. package/dist/types/diff-view.d.ts +8 -0
  147. package/dist/types/dot-plot.d.ts +6 -0
  148. package/dist/types/edit-region.d.ts +23 -0
  149. package/dist/types/empty.d.ts +9 -0
  150. package/dist/types/feature-gate.d.ts +8 -0
  151. package/dist/types/filter-bar.d.ts +8 -0
  152. package/dist/types/formatted-number.d.ts +6 -0
  153. package/dist/types/heatmap.d.ts +6 -0
  154. package/dist/types/inline-citation.d.ts +6 -0
  155. package/dist/types/inline-edit.d.ts +7 -0
  156. package/dist/types/input-otp.d.ts +11 -0
  157. package/dist/types/item-group.d.ts +29 -0
  158. package/dist/types/line-chart.d.ts +6 -0
  159. package/dist/types/link.d.ts +8 -1
  160. package/dist/types/markdown.d.ts +6 -0
  161. package/dist/types/meter.d.ts +6 -0
  162. package/dist/types/nav.d.ts +6 -0
  163. package/dist/types/notifications.d.ts +6 -0
  164. package/dist/types/number-input.d.ts +6 -0
  165. package/dist/types/overlays.d.ts +61 -0
  166. package/dist/types/page-header.d.ts +14 -0
  167. package/dist/types/pagination.d.ts +6 -0
  168. package/dist/types/password-input.d.ts +6 -0
  169. package/dist/types/permission-matrix.d.ts +7 -0
  170. package/dist/types/pile.d.ts +6 -0
  171. package/dist/types/plan-picker.d.ts +6 -0
  172. package/dist/types/prompt-input.d.ts +11 -0
  173. package/dist/types/property-list.d.ts +17 -0
  174. package/dist/types/question.d.ts +11 -0
  175. package/dist/types/relative-time.d.ts +8 -1
  176. package/dist/types/removal-receipt.d.ts +18 -1
  177. package/dist/types/save-bar.d.ts +6 -0
  178. package/dist/types/saved-views.d.ts +12 -0
  179. package/dist/types/scatter-chart.d.ts +8 -0
  180. package/dist/types/schedule.d.ts +21 -0
  181. package/dist/types/secret-field.d.ts +6 -0
  182. package/dist/types/select.d.ts +12 -0
  183. package/dist/types/separator.d.ts +8 -1
  184. package/dist/types/sheet.d.ts +19 -0
  185. package/dist/types/sparkline.d.ts +6 -0
  186. package/dist/types/split-button.d.ts +7 -0
  187. package/dist/types/stat.d.ts +7 -0
  188. package/dist/types/stepper.d.ts +12 -0
  189. package/dist/types/suggestions.d.ts +10 -0
  190. package/dist/types/sync-indicator.d.ts +8 -0
  191. package/dist/types/tag-input.d.ts +8 -0
  192. package/dist/types/terminal.d.ts +6 -0
  193. package/dist/types/thread-list.d.ts +16 -0
  194. package/dist/types/time-input.d.ts +6 -0
  195. package/dist/types/timeline.d.ts +9 -0
  196. package/dist/types/toggle.d.ts +6 -0
  197. package/dist/types/toolbar.d.ts +20 -0
  198. package/dist/types/tour.d.ts +10 -0
  199. package/dist/types/trace.d.ts +16 -0
  200. package/dist/types/tree.d.ts +21 -0
  201. package/dist/types-cjs/agent-fleet.d.cts +28 -0
  202. package/dist/types-cjs/ai-label.d.cts +7 -0
  203. package/dist/types-cjs/app-shell.d.cts +23 -0
  204. package/dist/types-cjs/area-chart.d.cts +8 -0
  205. package/dist/types-cjs/artifact.d.cts +14 -0
  206. package/dist/types-cjs/bar-chart.d.cts +6 -0
  207. package/dist/types-cjs/bar-list.d.cts +6 -0
  208. package/dist/types-cjs/breadcrumb.d.cts +6 -0
  209. package/dist/types-cjs/chart-brush.d.cts +6 -0
  210. package/dist/types-cjs/chart.d.cts +29 -1
  211. package/dist/types-cjs/checklist.d.cts +6 -0
  212. package/dist/types-cjs/code-block.d.cts +6 -0
  213. package/dist/types-cjs/collections.d.cts +15 -0
  214. package/dist/types-cjs/color-picker.d.cts +6 -0
  215. package/dist/types-cjs/command.d.cts +13 -0
  216. package/dist/types-cjs/comment-thread.d.cts +11 -0
  217. package/dist/types-cjs/confirmation.d.cts +8 -0
  218. package/dist/types-cjs/context-menu.d.cts +11 -0
  219. package/dist/types-cjs/controls.d.cts +174 -10
  220. package/dist/types-cjs/conversation.d.cts +21 -0
  221. package/dist/types-cjs/copy-button.d.cts +6 -0
  222. package/dist/types-cjs/core.d.cts +31 -0
  223. package/dist/types-cjs/data-grid.d.cts +25 -0
  224. package/dist/types-cjs/date-picker.d.cts +6 -0
  225. package/dist/types-cjs/diff-view.d.cts +8 -0
  226. package/dist/types-cjs/dot-plot.d.cts +6 -0
  227. package/dist/types-cjs/edit-region.d.cts +23 -0
  228. package/dist/types-cjs/empty.d.cts +9 -0
  229. package/dist/types-cjs/feature-gate.d.cts +8 -0
  230. package/dist/types-cjs/filter-bar.d.cts +8 -0
  231. package/dist/types-cjs/formatted-number.d.cts +6 -0
  232. package/dist/types-cjs/heatmap.d.cts +6 -0
  233. package/dist/types-cjs/inline-citation.d.cts +6 -0
  234. package/dist/types-cjs/inline-edit.d.cts +7 -0
  235. package/dist/types-cjs/input-otp.d.cts +11 -0
  236. package/dist/types-cjs/item-group.d.cts +29 -0
  237. package/dist/types-cjs/line-chart.d.cts +6 -0
  238. package/dist/types-cjs/link.d.cts +8 -1
  239. package/dist/types-cjs/markdown.d.cts +6 -0
  240. package/dist/types-cjs/meter.d.cts +6 -0
  241. package/dist/types-cjs/nav.d.cts +6 -0
  242. package/dist/types-cjs/notifications.d.cts +6 -0
  243. package/dist/types-cjs/number-input.d.cts +6 -0
  244. package/dist/types-cjs/overlays.d.cts +61 -0
  245. package/dist/types-cjs/page-header.d.cts +14 -0
  246. package/dist/types-cjs/pagination.d.cts +6 -0
  247. package/dist/types-cjs/password-input.d.cts +6 -0
  248. package/dist/types-cjs/permission-matrix.d.cts +7 -0
  249. package/dist/types-cjs/pile.d.cts +6 -0
  250. package/dist/types-cjs/plan-picker.d.cts +6 -0
  251. package/dist/types-cjs/prompt-input.d.cts +11 -0
  252. package/dist/types-cjs/property-list.d.cts +17 -0
  253. package/dist/types-cjs/question.d.cts +11 -0
  254. package/dist/types-cjs/relative-time.d.cts +8 -1
  255. package/dist/types-cjs/removal-receipt.d.cts +18 -1
  256. package/dist/types-cjs/save-bar.d.cts +6 -0
  257. package/dist/types-cjs/saved-views.d.cts +12 -0
  258. package/dist/types-cjs/scatter-chart.d.cts +8 -0
  259. package/dist/types-cjs/schedule.d.cts +21 -0
  260. package/dist/types-cjs/secret-field.d.cts +6 -0
  261. package/dist/types-cjs/select.d.cts +12 -0
  262. package/dist/types-cjs/separator.d.cts +8 -1
  263. package/dist/types-cjs/sheet.d.cts +19 -0
  264. package/dist/types-cjs/sparkline.d.cts +6 -0
  265. package/dist/types-cjs/split-button.d.cts +7 -0
  266. package/dist/types-cjs/stat.d.cts +7 -0
  267. package/dist/types-cjs/stepper.d.cts +12 -0
  268. package/dist/types-cjs/suggestions.d.cts +10 -0
  269. package/dist/types-cjs/sync-indicator.d.cts +8 -0
  270. package/dist/types-cjs/tag-input.d.cts +8 -0
  271. package/dist/types-cjs/terminal.d.cts +6 -0
  272. package/dist/types-cjs/thread-list.d.cts +16 -0
  273. package/dist/types-cjs/time-input.d.cts +6 -0
  274. package/dist/types-cjs/timeline.d.cts +9 -0
  275. package/dist/types-cjs/toggle.d.cts +6 -0
  276. package/dist/types-cjs/toolbar.d.cts +20 -0
  277. package/dist/types-cjs/tour.d.cts +10 -0
  278. package/dist/types-cjs/trace.d.cts +16 -0
  279. package/dist/types-cjs/tree.d.cts +21 -0
  280. package/package.json +1 -1
@@ -28,3 +28,80 @@ What changed between two versions: version history, audit-log detail ("Plan: Pro
28
28
  - Do keep a little context around changes, so they can be read.
29
29
  - Don't show a diff in red and green alone.
30
30
  - Don't restore from inside the diff without a confirmation.
31
+
32
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
33
+ ## Example
34
+
35
+ `DiffView` as first used in its preview:
36
+
37
+ ```tsx
38
+ import { DiffView, diffChanges, diffLines } from 'altitude-design';
39
+
40
+ <DiffView caption="src/pricing.ts · proposed by the assistant" before={pricingBefore} after={pricingAfter} review decisions={decisions}
41
+ onDecide={(id, d) => setDecisions({ ...decisions, [id]: d })}
42
+ onDecideAll={(d) => setDecisions(Object.fromEntries(diffChanges(diffLines(pricingBefore, pricingAfter)).map((c) => [c.id, decisions[c.id] ?? d])))} />
43
+ ```
44
+
45
+ The full preview, as rendered in Storybook and checked in CI (`dv` is the preview page's own layout class, not Altitude's):
46
+
47
+ ```tsx
48
+ import * as React from 'react';
49
+ import { DiffView, diffChanges, diffLines } from 'altitude-design';
50
+
51
+ const v4 = `# Deploy settings
52
+ region: eu-central-1
53
+ replicas: 2
54
+ memory: 512Mi
55
+ timeout: 30s
56
+ retries: 3
57
+ logging: info
58
+ metrics: enabled
59
+ tracing: disabled
60
+ cache: 5m
61
+ health: /healthz
62
+ owner: platform-team`;
63
+ const v5 = `# Deploy settings
64
+ region: eu-central-1
65
+ replicas: 4
66
+ memory: 1Gi
67
+ timeout: 30s
68
+ retries: 3
69
+ logging: info
70
+ metrics: enabled
71
+ tracing: disabled
72
+ cache: 5m
73
+ health: /healthz
74
+ owner: platform-team
75
+ alerts: on-call`;
76
+ const pricingBefore = `export const plans = {
77
+ starter: { monthly: 9, seats: 1 },
78
+ pro: { monthly: 29, seats: 5 },
79
+ business: { monthly: 99, seats: 25 },
80
+ };
81
+ export const trialDays = 14;
82
+ export const currency = 'USD';`;
83
+ const pricingAfter = `export const plans = {
84
+ starter: { monthly: 9, seats: 1 },
85
+ pro: { monthly: 32, seats: 5 },
86
+ business: { monthly: 99, seats: 25, sso: true },
87
+ };
88
+ export const trialDays = 30;
89
+ export const currency = 'USD';`;
90
+ function Review() {
91
+ const [decisions, setDecisions] = React.useState({ 'change-2': 'accept' });
92
+ return <DiffView caption="src/pricing.ts · proposed by the assistant" before={pricingBefore} after={pricingAfter} review decisions={decisions}
93
+ onDecide={(id, d) => setDecisions({ ...decisions, [id]: d })}
94
+ onDecideAll={(d) => setDecisions(Object.fromEntries(diffChanges(diffLines(pricingBefore, pricingAfter)).map((c) => [c.id, decisions[c.id] ?? d])))} />;
95
+ }
96
+ export default function App() {
97
+ return (
98
+ <div className="dv">
99
+ <DiffView caption="deploy.yaml · version 4 → 5" before={v4} after={v5} context={1} />
100
+ <DiffView mode="properties" caption="Subscription changed by Maya" before={{ plan: 'Pro', seats: 12, billing: 'Monthly' }} after={{ plan: 'Business', seats: 25, billing: 'Monthly' }}
101
+ fields={[{ key: 'plan', label: 'Plan' }, { key: 'seats', label: 'Seats' }, { key: 'billing', label: 'Billing' }]} />
102
+ <Review />
103
+ </div>
104
+ );
105
+ }
106
+ ```
107
+ <!-- /example -->
@@ -36,3 +36,47 @@ Values compared across categories where zero isn't part of the story: ten servic
36
36
  - Do sort the rows unless their order means something.
37
37
  - Don't use it for change over time; that's a `LineChart`, which implies continuity.
38
38
  - Don't set `zero` just to have it: if zero matters, a `BarChart` is usually the better form.
39
+
40
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
41
+ ## Example
42
+
43
+ `DotPlot` as first used in its preview:
44
+
45
+ ```tsx
46
+ import { DotPlot } from 'altitude-design';
47
+
48
+ <DotPlot data={services} x="service" range={['p10', 'p90']} rangeLabel="10th–90th percentile" sort="desc" />
49
+ ```
50
+
51
+ The full preview, as rendered in Storybook and checked in CI (`dp` is the preview page's own layout class, not Altitude's):
52
+
53
+ ```tsx
54
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, ChartContainer, DotPlot } from 'altitude-design';
55
+
56
+ const services = [['Checkout', 212, 180, 240], ['Search', 188, 170, 205], ['Billing', 236, 190, 320], ['Accounts', 199, 182, 214], ['Media', 228, 196, 268], ['Auth', 184, 176, 193], ['Exports', 221, 187, 262], ['Email', 193, 179, 209]]
57
+ .map(([service, p95, p10, p90]) => ({ service, p95, p10, p90 }));
58
+ const teams = [['Atlas', 71, 78], ['Birch', 64, 62], ['Cedar', 80, 86], ['Delta', 58, 69], ['Ember', 75, 77]].map(([team, q2, q3]) => ({ team, q2, q3 }));
59
+ export default function App() {
60
+ return (
61
+ <div className="dp">
62
+ <Card>
63
+ <CardHeader><CardTitle id="d1">p95 latency by service</CardTitle><CardDescription>All between 180 and 240 ms; the band is the 10th–90th percentile</CardDescription></CardHeader>
64
+ <CardContent>
65
+ <ChartContainer aria-labelledby="d1" aspect={1.5} config={{ p95: { label: 'p95', format: (v) => `${v} ms` } }}>
66
+ <DotPlot data={services} x="service" range={['p10', 'p90']} rangeLabel="10th–90th percentile" sort="desc" />
67
+ </ChartContainer>
68
+ </CardContent>
69
+ </Card>
70
+ <Card>
71
+ <CardHeader><CardTitle id="d2">Accessibility score, Q2 to Q3</CardTitle><CardDescription>A dumbbell: the hollow dot is Q2, the filled one Q3</CardDescription></CardHeader>
72
+ <CardContent>
73
+ <ChartContainer aria-labelledby="d2" aspect={1.5} config={{ q2: { label: 'Q2' }, q3: { label: 'Q3' } }}>
74
+ <DotPlot data={teams} x="team" from="q2" to="q3" sort="desc" />
75
+ </ChartContainer>
76
+ </CardContent>
77
+ </Card>
78
+ </div>
79
+ );
80
+ }
81
+ ```
82
+ <!-- /example -->
@@ -13,3 +13,31 @@ Navigates a hierarchy as a **stack**. Each place you go into is laid on top of t
13
13
  - Don't use it for steps in a process; a stack implies you can always lift back out.
14
14
 
15
15
  **Charts that drill.** A node with both `content` and `children` shows its content, and the content opens the children itself: a chart's `drill={(d) => childId}`, or `useDrilldown().open(id)` from your own component. After a step, focus moves to the new view's own first stop when it names one (`data-drill-focus`, as a chart's summary does), otherwise to the view.
16
+
17
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
18
+ ## Example
19
+
20
+ `Drilldown` as first used in its preview:
21
+
22
+ ```tsx
23
+ import { Drilldown } from 'altitude-design';
24
+
25
+ <Drilldown root={tree} />
26
+ ```
27
+
28
+ The full preview, as rendered in Storybook and checked in CI (`wrap` is the preview page's own layout class, not Altitude's):
29
+
30
+ ```tsx
31
+ import { Drilldown } from 'altitude-design';
32
+
33
+ const tree = { id: 'ws', title: 'Workspace', children: [
34
+ { id: 'nw', title: 'Northwind', description: 'Marketing site', children: [
35
+ { id: 'prod', title: 'Production', description: 'acme.dev', children: [
36
+ { id: 'b42', title: 'Build #42', description: 'main@8dc01b2 · 2h ago', content: 'Deployed by Mara. 14 routes, 2.1 MB, healthy.' },
37
+ { id: 'b41', title: 'Build #41', description: 'main@51ac9e0 · yesterday', content: 'Rolled back after a cold-start regression.' } ] },
38
+ { id: 'prev', title: 'Preview', description: '3 branches', content: 'Preview deployments for open pull requests.' } ] },
39
+ { id: 'bc', title: 'Beacon', description: 'Internal dashboard', content: 'No deployments yet.' },
40
+ { id: 'st', title: 'Settings', description: 'Members, billing, domains', content: 'Workspace settings.' } ] };
41
+ export default function App() { return <div className="wrap"><Drilldown root={tree} /></div>; }
42
+ ```
43
+ <!-- /example -->
@@ -19,3 +19,53 @@ Pass `queue` and each file you're handling is a slat under the zone, named by it
19
19
 
20
20
  `UploadItem` is `{ id, name, size?, state, progress?, error? }`. `onRetry`, `onCancel` and `onRemove` get the id, and their buttons name the file ("Retry upload of report.pdf"). Progress is announced at 25, 50, 75 and 100%, and on processing, finishing and failing, never continuously. Afterglows start at most four at once.
21
21
 
22
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
23
+ ## Example
24
+
25
+ `DropZone` as first used in its preview:
26
+
27
+ ```tsx
28
+ import { DropZone } from 'altitude-design';
29
+
30
+ <DropZone state="idle"><Body /></DropZone>
31
+ ```
32
+
33
+ The full preview, as rendered in Storybook and checked in CI (`three`, `lab` are the preview page's own layout classes, not Altitude's):
34
+
35
+ ```tsx
36
+ import * as React from 'react';
37
+ import { DropZone } from 'altitude-design';
38
+
39
+ const Body = () => <><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M12 16V4M7 9l5-5 5 5M5 20h14" /></svg><span>Drop files or <u>browse</u></span></>;
40
+ export default function App() {
41
+ return (
42
+ <div className="three">
43
+ <div><div className="lab">idle · flush</div><DropZone state="idle"><Body /></DropZone></div>
44
+ <div><div className="lab">armed · a file is in flight</div><DropZone state="armed"><Body /></DropZone></div>
45
+ <div><div className="lab">over · ready to receive</div><DropZone state="over"><Body /></DropZone></div>
46
+ <div style={{ gridColumn: '1 / -1' }}><Queue /></div>
47
+ </div>
48
+ );
49
+ }
50
+ // A stand-in uploader: files upload in steps, then the server processes them; one fails until retried.
51
+ const start = [
52
+ { id: 'a', name: 'Q3 board deck.pdf', size: 2400000, state: 'uploading', progress: 0 },
53
+ { id: 'b', name: 'Customer list.csv', size: 480000, state: 'uploading', progress: 0 },
54
+ { id: 'c', name: 'Brand guidelines.pdf', size: 18200000, state: 'uploading', progress: 0, fails: true },
55
+ ];
56
+ function Queue() {
57
+ const [items, setItems] = React.useState(start);
58
+ React.useEffect(() => {
59
+ const t = setInterval(() => setItems((xs) => xs.map((x) => {
60
+ if (x.state === 'uploading') { const p = Math.min(100, x.progress + (x.id === 'b' ? 34 : 13)); if (x.fails && p > 40) return { ...x, state: 'failed', error: 'The connection dropped' }; return p >= 100 ? { ...x, state: 'processing', progress: 100 } : { ...x, progress: p }; }
61
+ if (x.state === 'processing') return Math.random() < 0.35 ? { ...x, state: 'done' } : x;
62
+ return x;
63
+ })), 450);
64
+ return () => clearInterval(t);
65
+ }, []);
66
+ return <DropZone multiple queue={items} onFiles={(fs) => setItems((xs) => [...xs, ...fs.map((f, i) => ({ id: f.name + Date.now() + i, name: f.name, size: f.size, state: 'uploading', progress: 0 }))])}
67
+ onRetry={(id) => setItems((xs) => xs.map((x) => (x.id === id ? { ...x, state: 'uploading', progress: 40, fails: false, error: undefined } : x)))}
68
+ onCancel={(id) => setItems((xs) => xs.filter((x) => x.id !== id))} onRemove={(id) => setItems((xs) => xs.filter((x) => x.id !== id))}><Body /></DropZone>;
69
+ }
70
+ ```
71
+ <!-- /example -->
@@ -17,3 +17,47 @@ The menu rises like a Popover. Items stagger in at 16ms each. A single `accent-s
17
17
  - Do use `ContextMenu` for the same menu opened where something is right-clicked; the two share one implementation.
18
18
  - Do use `checked` for an item that turns something on or off: it becomes a `menuitemcheckbox` with a check, and every item keeps a slot for the check so labels line up.
19
19
  - Do put destructive actions last, after a separator, in `critical`.
20
+
21
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
22
+ ## Example
23
+
24
+ `DropdownMenu` as first used in its preview:
25
+
26
+ ```tsx
27
+ import { Button, DropdownMenu, Icons } from 'altitude-design';
28
+
29
+ <DropdownMenu defaultOpen items={items} trigger={<Button trailing={<Icons.ChevronDown />}>Actions</Button>} />
30
+ ```
31
+
32
+ The full preview, as rendered in Storybook and checked in CI (`pair` is the preview page's own layout class, not Altitude's):
33
+
34
+ ```tsx
35
+ import { Button, DropdownMenu, Icons } from 'altitude-design';
36
+
37
+ const items = [
38
+ { type: 'label', label: 'Project' },
39
+ { label: 'Rename', shortcut: 'R' },
40
+ { label: 'Duplicate', shortcut: '⌘D' },
41
+ { label: 'Move to…' },
42
+ { label: 'Archive', disabled: true },
43
+ { type: 'separator' },
44
+ { label: 'Delete project', tone: 'critical', shortcut: '⌫' },
45
+ ];
46
+ const predicted = [
47
+ { label: 'Copy link', shortcut: '⌘L', predicted: true },
48
+ { label: 'Share…', predicted: true },
49
+ { label: 'Rename', shortcut: 'R' },
50
+ { label: 'Duplicate', shortcut: '⌘D' },
51
+ { label: 'Export as PDF' },
52
+ { label: 'Version history' },
53
+ ];
54
+ export default function App() {
55
+ return (
56
+ <div className="pair">
57
+ <DropdownMenu defaultOpen items={items} trigger={<Button trailing={<Icons.ChevronDown />}>Actions</Button>} />
58
+ <DropdownMenu items={predicted} trigger={<Button trailing={<Icons.ChevronDown />}>Predicted</Button>} />
59
+ </div>
60
+ );
61
+ }
62
+ ```
63
+ <!-- /example -->
@@ -102,3 +102,162 @@ The record, the write (with a server-side revision or equivalent concurrency gua
102
102
  ## Demo and verification
103
103
 
104
104
  The `EditRegion` section of `build/previews.txt` lists three people with access; each row is an in-place EditRegion over a fictional people service with revisions. Below it, trip details are label–value rows with reserved values. A collapsed harness picks a save fixture (`success`, `slow`, `fail-once`, `conflict-during-save`) and applies an outside update. `node checks/edit-region.mjs` (from `build/`, after rendering previews; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers editing in place (fields hold their place on every frame while the editor grows, the record below is pushed through the glide, lifting never moves the record, each field opens at its value's x, reserved values open exactly where they were, the padded afterglow, the announcement that clears itself), opening and focus, unchanged saves, one write per save, `changed`, validation and error focus, Escape with an open Select, IME Enter, failure and retry, pending lock, both conflict paths, focus return, and a 320/960px layout in both schemes under reduced motion, forced colours and `contrast="more"`.
105
+
106
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
107
+ ## Example
108
+
109
+ `EditRegion` as first used in its preview:
110
+
111
+ ```tsx
112
+ import { EditRegion, EditRegionForm, EditRegionValue, EditRegionView, Field, Input, Select, Switch } from 'altitude-design';
113
+
114
+ <EditRegion layout="in-place" label={person.name} editLabel={`Edit ${person.name}`} value={erFields(person)} validate={validate}
115
+ onValueChange={canonical => setPeople(people => people.map(p => p.id === person.id ? { ...server.current.get(person.id), ...canonical } : p))}
116
+ onSave={save(person)}>
117
+ <EditRegionView>
118
+ <span className="er-name"><EditRegionValue field="name">{person.name}</EditRegionValue></span>
119
+ <span className="er-meta"><EditRegionValue field="email">{person.email}</EditRegionValue> · <EditRegionValue field="role">{ER_ROLES.find(r => r.value === person.role).label}</EditRegionValue>{person.notify ? <> · <EditRegionValue field="notify">Gets updates</EditRegionValue></> : ''}</span>
120
+ </EditRegionView>
121
+ <EditRegionForm>
122
+ {field => <>
123
+ <div className="er-name-edit"><Field label="Name" required error={field.error('name')}><Input {...field('name')} autoComplete="off" /></Field></div>
124
+ <div className="er-meta-edit">
125
+ <Field label="Email" required error={field.error('email')}><Input {...field('email')} type="email" autoComplete="off" /></Field>
126
+ <Field label="Role"><Select {...field.value('role')} items={ER_ROLES} /></Field>
127
+ <Switch {...field.checked('notify')} label="Gets updates" />
128
+ </div>
129
+ </>}
130
+ </EditRegionForm>
131
+ </EditRegion>
132
+ ```
133
+
134
+ The full preview, as rendered in Storybook and checked in CI (`er-list`, `er-name`, `er-meta`, `er-name-edit`, `er-meta-edit`, `er-harness`, `er-harness-controls`, `er-log`, `er-sheet`, `er-sheet-head`, `er-sheet-grid`, `er-demo` are the preview page's own layout classes, not Altitude's):
135
+
136
+ ```tsx
137
+ import * as React from 'react';
138
+ import { Button, EditRegion, EditRegionForm, EditRegionTrigger, EditRegionValue, EditRegionView, Field, Input, Select, Switch } from 'altitude-design';
139
+
140
+ const ER_ROLES = [{ value: 'owner', label: 'Owner' }, { value: 'editor', label: 'Editor' }, { value: 'viewer', label: 'Viewer' }];
141
+ const ER_PEOPLE = Object.freeze([
142
+ Object.freeze({ id: 'p-mara', name: 'Mara Quist', email: 'mara@northwind.example', role: 'owner', notify: true, revision: 1 }),
143
+ Object.freeze({ id: 'p-ines', name: 'Inés Ortega', email: 'ines@northwind.example', role: 'editor', notify: false, revision: 1 }),
144
+ Object.freeze({ id: 'p-tobi', name: 'Tobi Adeyemi', email: 'tobi@northwind.example', role: 'viewer', notify: true, revision: 1 }),
145
+ ]);
146
+ const ER_FIXTURES = ['success', 'slow', 'fail-once', 'conflict-during-save'];
147
+ const erWait = ms => new Promise(resolve => setTimeout(resolve, ms));
148
+ const erFields = ({ revision, id, ...fields }) => fields;
149
+
150
+ // A fictional people service with revisions: a stale write is rejected, an accepted one returns the canonical record.
151
+ function EditRegionExample({ fixture }) {
152
+ const server = React.useRef(new Map(ER_PEOPLE.map(p => [p.id, { ...p }])));
153
+ const [people, setPeople] = React.useState(() => ER_PEOPLE.map(p => ({ ...p })));
154
+ const [log, setLog] = React.useState([]);
155
+ const failed = React.useRef(false);
156
+ const trace = event => setLog(log => [...log, event]);
157
+ const reconcile = id => setPeople(people => people.map(p => p.id === id ? { ...server.current.get(id) } : p));
158
+ const externalUpdate = () => {
159
+ const current = server.current.get('p-mara');
160
+ server.current.set('p-mara', { ...current, email: 'm.quist@northwind.example', revision: current.revision + 1 });
161
+ trace({ event: 'external-update', id: 'p-mara', revision: current.revision + 1 }); reconcile('p-mara');
162
+ };
163
+ const save = person => async (draft, { changed }) => {
164
+ trace({ event: 'requested', id: person.id, expectedRevision: person.revision, changed });
165
+ await erWait(fixture === 'slow' ? 1400 : 250);
166
+ if (fixture === 'fail-once' && !failed.current) { failed.current = true; trace({ event: 'rejected', id: person.id }); return { ok: false, error: 'We couldn’t reach the people service. Your changes are still here. Try again.' }; }
167
+ if (fixture === 'conflict-during-save') { externalUpdate(); await erWait(300); }
168
+ const current = server.current.get(person.id);
169
+ if (current.revision !== person.revision) { trace({ event: 'rejected-stale', id: person.id }); return { ok: false, error: 'Someone else changed this person. Review the current details.' }; }
170
+ if ([...server.current.values()].some(p => p.id !== person.id && p.email === draft.email.trim())) return { ok: false, error: 'Check the highlighted field.', fieldErrors: { email: 'Someone else already uses this email.' } };
171
+ const next = { ...current, ...draft, name: draft.name.trim(), email: draft.email.trim(), revision: current.revision + 1 };
172
+ server.current.set(person.id, next);
173
+ trace({ event: 'committed', id: person.id, revision: next.revision, changed });
174
+ return { ok: true, value: erFields(next) };
175
+ };
176
+ const validate = draft => {
177
+ const errors = {};
178
+ if (!draft.name.trim()) errors.name = 'Enter a name.';
179
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(draft.email.trim())) errors.email = 'Enter an email address, like name@example.com.';
180
+ return Object.keys(errors).length ? errors : undefined;
181
+ };
182
+ return <>
183
+ <section aria-labelledby="er-title">
184
+ <h2 id="er-title">People with access</h2>
185
+ <ul className="er-list">
186
+ {people.map(person => <li key={person.id}>
187
+ <EditRegion layout="in-place" label={person.name} editLabel={`Edit ${person.name}`} value={erFields(person)} validate={validate}
188
+ onValueChange={canonical => setPeople(people => people.map(p => p.id === person.id ? { ...server.current.get(person.id), ...canonical } : p))}
189
+ onSave={save(person)}>
190
+ <EditRegionView>
191
+ <span className="er-name"><EditRegionValue field="name">{person.name}</EditRegionValue></span>
192
+ <span className="er-meta"><EditRegionValue field="email">{person.email}</EditRegionValue> · <EditRegionValue field="role">{ER_ROLES.find(r => r.value === person.role).label}</EditRegionValue>{person.notify ? <> · <EditRegionValue field="notify">Gets updates</EditRegionValue></> : ''}</span>
193
+ </EditRegionView>
194
+ <EditRegionForm>
195
+ {field => <>
196
+ <div className="er-name-edit"><Field label="Name" required error={field.error('name')}><Input {...field('name')} autoComplete="off" /></Field></div>
197
+ <div className="er-meta-edit">
198
+ <Field label="Email" required error={field.error('email')}><Input {...field('email')} type="email" autoComplete="off" /></Field>
199
+ <Field label="Role"><Select {...field.value('role')} items={ER_ROLES} /></Field>
200
+ <Switch {...field.checked('notify')} label="Gets updates" />
201
+ </div>
202
+ </>}
203
+ </EditRegionForm>
204
+ </EditRegion>
205
+ </li>)}
206
+ </ul>
207
+ </section>
208
+ <TripSheet />
209
+ <details className="er-harness">
210
+ <summary>Fixture controls</summary>
211
+ <p>Documentation harness only: change Mara's record on the “server” while it's being edited.</p>
212
+ <div className="er-harness-controls"><Button onClick={externalUpdate}>Apply external update to Mara</Button></div>
213
+ <output className="er-log" data-er-log="" aria-label="Fixture operation log">{JSON.stringify(log, null, 2)}</output>
214
+ </details>
215
+ </>;
216
+ }
217
+
218
+ // Labelled rows whose values already have their controls' height (EditRegionValue reserve): Edit all moves nothing.
219
+ const ER_SEATS = [{ value: 'aisle', label: 'Aisle' }, { value: 'window', label: 'Window' }, { value: 'any', label: 'No preference' }];
220
+ function TripSheet() {
221
+ const [trip, setTrip] = React.useState({ traveller: 'Mara Quist', email: 'mara@northwind.example', phone: '', seat: 'aisle' });
222
+ const validate = draft => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(draft.email.trim()) ? undefined : { email: 'Enter an email address, like name@example.com.' };
223
+ const rows = [['traveller', 'Traveller'], ['email', 'Email'], ['phone', 'Phone'], ['seat', 'Seat']];
224
+ return <section className="er-sheet" aria-labelledby="er-sheet-title">
225
+ <EditRegion layout="in-place" label="Trip details" editLabel="Edit all trip details" value={trip} validate={validate} onValueChange={setTrip}
226
+ onSave={async draft => { await erWait(250); return { ok: true, value: { ...draft, traveller: draft.traveller.trim(), email: draft.email.trim(), phone: draft.phone.trim() } }; }}>
227
+ <EditRegionView trigger={false}>
228
+ <div className="er-sheet-head"><h2 id="er-sheet-title">Trip details</h2><EditRegionTrigger>Edit all</EditRegionTrigger></div>
229
+ <dl className="er-sheet-grid">{rows.map(([key, label]) => <React.Fragment key={key}><dt>{label}</dt><dd>
230
+ <EditRegionValue field={key} reserve>{key === 'seat' ? ER_SEATS.find(s => s.value === trip.seat).label : trip[key] || <span className="er-meta">Not set</span>}</EditRegionValue>
231
+ </dd></React.Fragment>)}</dl>
232
+ </EditRegionView>
233
+ <EditRegionForm>
234
+ {field => <>
235
+ <div className="er-sheet-head"><h2>Trip details</h2></div>
236
+ <dl className="er-sheet-grid">
237
+ <dt>Traveller</dt><dd><Field label="Traveller" required error={field.error('traveller')}><Input {...field('traveller')} autoComplete="off" /></Field></dd>
238
+ <dt>Email</dt><dd><Field label="Email" required error={field.error('email')}><Input {...field('email')} type="email" autoComplete="off" /></Field></dd>
239
+ <dt>Phone</dt><dd><Field label="Phone"><Input {...field('phone')} type="tel" placeholder="Add a phone number" autoComplete="off" /></Field></dd>
240
+ <dt>Seat</dt><dd><Field label="Seat"><Select {...field.value('seat')} items={ER_SEATS} /></Field></dd>
241
+ </dl>
242
+ </>}
243
+ </EditRegionForm>
244
+ </EditRegion>
245
+ </section>;
246
+ }
247
+
248
+ export default function App() {
249
+ const [fixture, setFixture] = React.useState('success');
250
+ const [mounted, setMounted] = React.useState({ fixture: 'success', epoch: 0 });
251
+ return <div className="er-demo">
252
+ <EditRegionExample key={mounted.epoch} fixture={mounted.fixture} />
253
+ <details className="er-harness">
254
+ <summary>Choose the next fixture</summary>
255
+ <div className="er-harness-controls">
256
+ <label>Save fixture<select value={fixture} onChange={event => setFixture(event.target.value)}>{ER_FIXTURES.map(name => <option key={name}>{name}</option>)}</select></label>
257
+ <Button onClick={() => setMounted({ fixture, epoch: mounted.epoch + 1 })}>Reset example</Button>
258
+ </div>
259
+ </details>
260
+ </div>;
261
+ }
262
+ ```
263
+ <!-- /example -->
@@ -15,3 +15,52 @@ What a list, board, search or page shows when it has nothing to show: a statemen
15
15
  - Do give it one primary action at most; a second one is `secondary`, quieter.
16
16
  - Don't use it for an error that stopped content loading: that's a problem to fix, an `Alert`.
17
17
  - Don't raise it into a card, or make it the only thing on a page that's still loading: show a `Skeleton` until you know it's empty.
18
+
19
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
20
+ ## Example
21
+
22
+ `EmptyState` as first used in its preview:
23
+
24
+ ```tsx
25
+ import { Button, EmptyState } from 'altitude-design';
26
+
27
+ <EmptyState icon={<Gift />} title="No gift ideas yet" headingLevel={2}
28
+ description="Ideas you save stay private to you until you share them."
29
+ action={<Button variant="primary">Add idea</Button>}
30
+ secondary={<Button variant="ghost">Import a list</Button>} />
31
+ ```
32
+
33
+ The full preview, as rendered in Storybook and checked in CI (`two`, `lab`, `side`, `cap` are the preview page's own layout classes, not Altitude's):
34
+
35
+ ```tsx
36
+ import * as React from 'react';
37
+ import { Button, EmptyState } from 'altitude-design';
38
+
39
+ const Gift = () => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="8" width="18" height="4" rx="1" /><path d="M12 8v13M19 12v7a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-7M7.5 8a2.5 2.5 0 0 1 0-5C10 3 12 8 12 8s2-5 4.5-5a2.5 2.5 0 0 1 0 5" /></svg>;
40
+ const Search = () => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round"><circle cx="11" cy="11" r="7" /><path d="M20 20l-3.5-3.5" /></svg>;
41
+ const Bell = () => <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10.3 21a1.94 1.94 0 0 0 3.4 0" /></svg>;
42
+ export default function App() {
43
+ const [q, setQ] = React.useState('lamp');
44
+ return (
45
+ <div className="two">
46
+ <div><div className="lab">md · a page with nothing yet</div>
47
+ <EmptyState icon={<Gift />} title="No gift ideas yet" headingLevel={2}
48
+ description="Ideas you save stay private to you until you share them."
49
+ action={<Button variant="primary">Add idea</Button>}
50
+ secondary={<Button variant="ghost">Import a list</Button>} />
51
+ </div>
52
+ <div className="side">
53
+ <div><div className="lab">sm · a widget</div>
54
+ <EmptyState size="sm" icon={<Bell />} title="You're all caught up" description="New mentions and replies appear here." />
55
+ </div>
56
+ <div><div className="lab">sm · after a search</div>
57
+ {q ? <EmptyState size="sm" icon={<Search />} title={`No ideas match \u201C${q}\u201D`} announce="No matching ideas"
58
+ description="Try another word, or clear the search." action={<Button size="sm" onClick={() => setQ('')}>Clear search</Button>} />
59
+ : <p className="cap">Search cleared.</p>}
60
+ </div>
61
+ </div>
62
+ </div>
63
+ );
64
+ }
65
+ ```
66
+ <!-- /example -->
@@ -26,3 +26,42 @@ Pressing it opens a `Popover` (level 3): "Available on Business", what the plan
26
26
  - Do say what the plan adds, not just its name.
27
27
  - Don't use `disabled` for a feature that needs another plan.
28
28
  - Don't hide the upgrade from people who can't buy it; tell them who can.
29
+
30
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
31
+ ## Example
32
+
33
+ `FeatureGate` as first used in its preview:
34
+
35
+ ```tsx
36
+ import { Button, FeatureGate } from 'altitude-design';
37
+
38
+ <FeatureGate requires="Business" description="Export any view to CSV, and schedule exports by email." onUpgrade={() => setSaid('Opening plans…')} compareHref="#/billing/plans">
39
+ <Button variant="secondary">Export to CSV</Button>
40
+ </FeatureGate>
41
+ ```
42
+
43
+ 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):
44
+
45
+ ```tsx
46
+ import * as React from 'react';
47
+ import { Button, FeatureGate } from 'altitude-design';
48
+
49
+ export default function App() {
50
+ const [said, setSaid] = React.useState('');
51
+ return (
52
+ <div className="col">
53
+ <div className="row">
54
+ <Button variant="secondary">Export view</Button>
55
+ <FeatureGate requires="Business" description="Export any view to CSV, and schedule exports by email." onUpgrade={() => setSaid('Opening plans…')} compareHref="#/billing/plans">
56
+ <Button variant="secondary">Export to CSV</Button>
57
+ </FeatureGate>
58
+ <FeatureGate requires="Enterprise" description="Sign in with your identity provider (Okta, Azure AD, Google)." canUpgrade={false}>
59
+ <Button variant="ghost">SAML SSO</Button>
60
+ </FeatureGate>
61
+ </div>
62
+ <p className="cap" role="status">{said || 'Locked features keep their ink, lift when pointed at and are reachable by keyboard; pressing one explains the plan. Without permission to upgrade, it says to ask an admin.'}</p>
63
+ </div>
64
+ );
65
+ }
66
+ ```
67
+ <!-- /example -->
@@ -8,3 +8,39 @@ Pairs one control with its label, an optional hint and an error message that eme
8
8
 
9
9
  - Do write the error as the fix: "Use at least 5 characters", not "Invalid input".
10
10
  - Don't show an error before the person has finished with the field; validate on blur or submit.
11
+
12
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
13
+ ## Example
14
+
15
+ `Field` as first used in its preview:
16
+
17
+ ```tsx
18
+ import { Field, Input } from 'altitude-design';
19
+
20
+ <Field label="Workspace name" hint="Shown to everyone you invite.">
21
+ <Input defaultValue="Northwind Studio" />
22
+ </Field>
23
+ ```
24
+
25
+ The full preview, as rendered in Storybook and checked in CI (`grid` is the preview page's own layout class, not Altitude's):
26
+
27
+ ```tsx
28
+ import * as React from 'react';
29
+ import { Field, Input } from 'altitude-design';
30
+
31
+ export default function App() {
32
+ const [v, setV] = React.useState('acme');
33
+ const err = v.length < 5 ? 'Use at least 5 characters.' : null;
34
+ return (
35
+ <div className="grid">
36
+ <Field label="Workspace name" hint="Shown to everyone you invite.">
37
+ <Input defaultValue="Northwind Studio" />
38
+ </Field>
39
+ <Field label="URL slug" required error={err}>
40
+ <Input value={v} onChange={(e) => setV(e.target.value)} />
41
+ </Field>
42
+ </div>
43
+ );
44
+ }
45
+ ```
46
+ <!-- /example -->
@@ -67,3 +67,66 @@ Automation rules, segments and advanced search need nested logic: "Plan is Pro *
67
67
 
68
68
  A group is drawn as **a flush bracket on the ground**: a 1px `line-control` rule around its own conditions, with its own all/any switch (shown even with one condition) and its own Add filter. Groups are part of the record of the query, not live surfaces. Depth is capped at one level of groups so it never becomes a code editor, and groups follow the top level's conditions. "+ Add group" starts an "any of" group, and removing a group's last condition removes the group. `filterSentence(fields, tree)` reads the whole expression in words, and it's announced as it changes. `value: Filter[]` keeps working as before.
69
69
 
70
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
71
+ ## Example
72
+
73
+ `FilterBar` as first used in its preview:
74
+
75
+ ```tsx
76
+ import { FilterBar } from 'altitude-design';
77
+
78
+ <FilterBar aria-label="Filter issues" fields={FB_FIELDS} value={filters} onValueChange={setFilters} match={match} onMatchChange={setMatch} resultCount={count} />
79
+ ```
80
+
81
+ The full preview, as rendered in Storybook and checked in CI (`fb-demo` is the preview page's own layout class, not Altitude's):
82
+
83
+ ```tsx
84
+ import * as React from 'react';
85
+ import { DataTable, FilterBar } from 'altitude-design';
86
+
87
+ const FB_STATUSES = [{ value: 'todo', label: 'To do' }, { value: 'progress', label: 'In progress' }, { value: 'review', label: 'In review' }, { value: 'done', label: 'Done' }];
88
+ const FB_PEOPLE = [{ value: 'mara', label: 'Mara Quist' }, { value: 'ines', label: 'Inés Ortega' }, { value: 'tobi', label: 'Tobi Adeyemi' }, { value: 'wen', label: 'Li Wen' }];
89
+ const FB_FIELDS = [
90
+ { key: 'status', label: 'Status', type: 'enum', options: FB_STATUSES },
91
+ { key: 'assignee', label: 'Assignee', type: 'enum', options: FB_PEOPLE, searchable: true },
92
+ { key: 'updated', label: 'Updated', type: 'date' },
93
+ { key: 'title', label: 'Title', type: 'text' },
94
+ { key: 'points', label: 'Points', type: 'number' },
95
+ ];
96
+ const FB_ROWS = [
97
+ { id: 'a', title: 'Search results page', status: 'review', assignee: 'mara', updated: '2026-10-21', points: 5 },
98
+ { id: 'b', title: 'Billing address form', status: 'progress', assignee: 'ines', updated: '2026-10-12', points: 3 },
99
+ { id: 'c', title: 'Search filters', status: 'todo', assignee: 'tobi', updated: '2026-09-30', points: 8 },
100
+ { id: 'd', title: 'Invoice emails', status: 'done', assignee: 'wen', updated: '2026-10-02', points: 2 },
101
+ { id: 'e', title: 'Onboarding checklist', status: 'review', assignee: 'ines', updated: '2026-10-19', points: 3 },
102
+ ];
103
+ const fbTest = (row, f) => {
104
+ if (f.value === undefined || f.value === '') return true;
105
+ const v = row[f.field];
106
+ switch (f.operator) {
107
+ case 'is': return v === f.value; case 'is not': return v !== f.value;
108
+ case 'contains': return v.toLowerCase().includes(String(f.value).toLowerCase()); case 'does not contain': return !v.toLowerCase().includes(String(f.value).toLowerCase());
109
+ case '=': return v === f.value; case '>': return v > f.value; case '<': return v < f.value;
110
+ case 'before': return v < f.value; case 'after': return v > f.value; case 'between': return v >= f.value.start && v <= f.value.end;
111
+ }
112
+ return true;
113
+ };
114
+ export default function App() {
115
+ const [filters, setFilters] = React.useState([{ id: 'f1', field: 'status', operator: 'is', value: 'review' }]);
116
+ const [match, setMatch] = React.useState('all');
117
+ const matches = row => match === 'all' ? filters.every(f => fbTest(row, f)) : filters.some(f => fbTest(row, f));
118
+ const count = FB_ROWS.filter(matches).length;
119
+ return <div className="fb-demo">
120
+ <FilterBar aria-label="Filter issues" fields={FB_FIELDS} value={filters} onValueChange={setFilters} match={match} onMatchChange={setMatch} resultCount={count} />
121
+ <DataTable aria-label="Issues" rows={FB_ROWS} getRowId={r => r.id} relevance={r => filters.length ? (matches(r) ? 'match' : 'miss') : undefined}
122
+ columns={[
123
+ { key: 'title', header: 'Title' },
124
+ { key: 'status', header: 'Status', cell: r => FB_STATUSES.find(s => s.value === r.status).label },
125
+ { key: 'assignee', header: 'Assignee', cell: r => FB_PEOPLE.find(p => p.value === r.assignee).label },
126
+ { key: 'updated', header: 'Updated' },
127
+ { key: 'points', header: 'Points', align: 'end' },
128
+ ]} />
129
+ </div>;
130
+ }
131
+ ```
132
+ <!-- /example -->