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
@@ -26,3 +26,43 @@ Roles against permissions: "Admins can manage billing, Members can invite, Viewe
26
26
  - Do return a promise from `onChange` so a cell shows it's saving.
27
27
  - Don't draw inherited grants as disabled checkboxes.
28
28
  - Don't lift the active cell; it's a ring.
29
+
30
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
31
+ ## Example
32
+
33
+ `PermissionMatrix` as first used in its preview:
34
+
35
+ ```tsx
36
+ import { PermissionMatrix } from 'altitude-design';
37
+
38
+ <PermissionMatrix aria-label="Role permissions" roles={roles} permissions={perms} value={grants} inherited={inherited}
39
+ onChange={(role, perm, on) => new Promise((r) => setTimeout(() => { setGrants((g) => ({ ...g, [role]: { ...g[role], [perm]: on } })); r(); }, 600))} />
40
+ ```
41
+
42
+ The full preview, as rendered in Storybook and checked in CI (`col`, `cap` are the preview page's own layout classes, not Altitude's):
43
+
44
+ ```tsx
45
+ import * as React from 'react';
46
+ import { PermissionMatrix } from 'altitude-design';
47
+
48
+ const roles = [{ id: 'owner', name: 'Owner', locked: true }, { id: 'admin', name: 'Admin' }, { id: 'member', name: 'Member' }, { id: 'viewer', name: 'Viewer' }];
49
+ const perms = [
50
+ { id: 'billing.view', name: 'View invoices', group: 'Billing' }, { id: 'billing.manage', name: 'Manage billing', group: 'Billing' },
51
+ { id: 'members.invite', name: 'Invite members', group: 'Members' }, { id: 'members.remove', name: 'Remove members', group: 'Members' },
52
+ { id: 'projects.create', name: 'Create projects', group: 'Projects' }, { id: 'projects.view', name: 'View projects', group: 'Projects' },
53
+ ];
54
+ export default function App() {
55
+ const [grants, setGrants] = React.useState({ admin: { 'billing.view': true, 'billing.manage': true, 'members.invite': true, 'members.remove': true, 'projects.create': true },
56
+ member: { 'members.invite': true, 'projects.create': true }, viewer: {} });
57
+ // Everyone can view projects; admins inherit invoices from Billing admins in this example.
58
+ const inherited = (role, perm) => (perm === 'projects.view' ? { granted: true, from: 'Everyone' } : role === 'viewer' && perm.startsWith('billing') ? { granted: false, from: 'Guest policy' } : undefined);
59
+ return (
60
+ <div className="col">
61
+ <PermissionMatrix aria-label="Role permissions" roles={roles} permissions={perms} value={grants} inherited={inherited}
62
+ onChange={(role, perm, on) => new Promise((r) => setTimeout(() => { setGrants((g) => ({ ...g, [role]: { ...g[role], [perm]: on } })); r(); }, 600))} />
63
+ <p className="cap">Editable grants are checkboxes. Owner's and inherited grants are stamped into the record: they're decided, not unavailable. Arrow keys move; Space toggles.</p>
64
+ </div>
65
+ );
66
+ }
67
+ ```
68
+ <!-- /example -->
@@ -10,3 +10,45 @@ Shows a group of items as a stack. Its thickness says roughly how many; click it
10
10
 
11
11
  - Leave room below a pile, or near the bottom of the viewport the list may run off-screen.
12
12
  - Use piles for collections people triage: review queues, inboxes, stacks of drafts.
13
+
14
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
15
+ ## Example
16
+
17
+ `Pile` as first used in its preview:
18
+
19
+ ```tsx
20
+ import { Pile } from 'altitude-design';
21
+
22
+ <Pile count={1} className="p"><div className="face"><b>Drafts</b><span>1 document</span></div></Pile>
23
+ ```
24
+
25
+ The full preview, as rendered in Storybook and checked in CI (`col`, `piles`, `p`, `face`, `wide`, `doc`, `cap` are the preview page's own layout classes, not Altitude's):
26
+
27
+ ```tsx
28
+ import * as React from 'react';
29
+ import { Pile } from 'altitude-design';
30
+
31
+ const docs = [
32
+ ['Pricing page copy', 'Three tiers, annual toggle, FAQ.', 'Mara · 2h'], ['Onboarding emails', 'Five-step drip for new workspaces.', 'Jon · yesterday'],
33
+ ['Q4 launch plan', 'Milestones, owners and risks.', 'Lena · Mon'], ['Release notes 2.4', 'Faster cold starts, new regions.', 'Sam · Mon'],
34
+ ['Support macros', 'Twelve canned replies, reviewed.', 'Ivy · last week'], ['Brand voice', 'How we sound, with examples.', 'Ada · last week'],
35
+ ['Security review', 'Findings from the October audit.', 'Theo · Oct 2'],
36
+ ];
37
+ export default function App() {
38
+ const [picked, setPicked] = React.useState(null);
39
+ return (
40
+ <div className="col">
41
+ <div className="piles">
42
+ <Pile count={1} className="p"><div className="face"><b>Drafts</b><span>1 document</span></div></Pile>
43
+ <Pile count={3} interactive className="p"><div className="face"><b>Approved</b><span>3 documents</span></div></Pile>
44
+ <Pile items={docs} className="p wide" onSelect={(d) => setPicked(d[0])} defaultOpen maxHeight={300}
45
+ renderItem={([t, d, m]) => <div className="doc"><b>{t}</b><span>{d}</span><small>{m}</small></div>}>
46
+ <div className="face"><b>In review</b><span>{docs.length} documents · click to fan out</span></div>
47
+ </Pile>
48
+ </div>
49
+ <p className="cap">{picked ? `Opened “${picked}”.` : 'One sheet for 2–3 items, two for 4–9, three for 10+; the badge carries the number. Click a pile with items and they fan out over the page below it.'}</p>
50
+ </div>
51
+ );
52
+ }
53
+ ```
54
+ <!-- /example -->
@@ -23,3 +23,40 @@ Choose a plan (Free, Pro, Business) in sign-up, upgrade and downgrade flows, wit
23
23
  - Do say why a plan can't be chosen.
24
24
  - Don't lift or glow a recommended plan.
25
25
  - Don't animate prices counting up when the period changes.
26
+
27
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
28
+ ## Example
29
+
30
+ `PlanPicker` as first used in its preview:
31
+
32
+ ```tsx
33
+ import { PlanPicker } from 'altitude-design';
34
+
35
+ <PlanPicker plans={plans} value={plan} onValueChange={setPlan} current="free" recommended="pro" period={period} onPeriodChange={setPeriod} locale="en-IE" aria-label="Choose a plan" />
36
+ ```
37
+
38
+ The full preview, as rendered in Storybook and checked in CI (`col`, `row` are the preview page's own layout classes, not Altitude's):
39
+
40
+ ```tsx
41
+ import * as React from 'react';
42
+ import { Button, PlanPicker } from 'altitude-design';
43
+
44
+ const plans = [
45
+ { id: 'free', name: 'Free', price: 0, currency: 'EUR', unit: 'per seat', features: ['3 projects', 'Community support'] },
46
+ { id: 'pro', name: 'Pro', price: { month: 2400, year: 2000 }, currency: 'EUR', unit: 'per seat', features: ['Unlimited projects', 'SAML SSO', 'Audit log'] },
47
+ { id: 'business', name: 'Business', price: { month: 4800, year: 4000 }, currency: 'EUR', unit: 'per seat', features: ['Everything in Pro', 'Custom roles', '99.9% uptime SLA'] },
48
+ { id: 'enterprise', name: 'Enterprise', price: null, unavailable: 'Contact sales for more than 500 seats.' },
49
+ ];
50
+ export default function App() {
51
+ const [plan, setPlan] = React.useState('free');
52
+ const [period, setPeriod] = React.useState('year');
53
+ const name = plans.find((p) => p.id === plan).name;
54
+ return (
55
+ <div className="col">
56
+ <PlanPicker plans={plans} value={plan} onValueChange={setPlan} current="free" recommended="pro" period={period} onPeriodChange={setPeriod} locale="en-IE" aria-label="Choose a plan" />
57
+ <div className="row"><Button variant="primary" disabled={plan === 'free'}>{plan === 'free' ? 'You\'re on Free' : `Continue to ${name}`}</Button></div>
58
+ </div>
59
+ );
60
+ }
61
+ ```
62
+ <!-- /example -->
@@ -10,3 +10,39 @@ It grows from the trigger's edge (`transform-origin` on that side), rising 6px f
10
10
 
11
11
  - Do use it for small forms and pickers that belong to one control.
12
12
  - Don't use it for a decision that blocks the page; that's a Dialog.
13
+
14
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
15
+ ## Example
16
+
17
+ `Popover` as first used in its preview:
18
+
19
+ ```tsx
20
+ import { Button, Field, Input, Popover } from 'altitude-design';
21
+
22
+ <Popover defaultOpen aria-label="Share" trigger={<Button>Share</Button>}>
23
+ <div className="col" style={{ width: 280, gap: 12 }}>
24
+ <b style={{ font: '600 14px/20px var(--font-sans)' }}>Invite to project</b>
25
+ <Field label="Email address"><Input size="sm" type="email" placeholder="name@company.com" /></Field>
26
+ <div className="row" style={{ justifyContent: 'flex-end', gap: 8 }}><Button size="sm" variant="primary">Send invite</Button></div>
27
+ </div>
28
+ </Popover>
29
+ ```
30
+
31
+ The full preview, as rendered in Storybook and checked in CI (`col`, `row` are the preview page's own layout classes, not Altitude's):
32
+
33
+ ```tsx
34
+ import { Button, Field, Input, Popover } from 'altitude-design';
35
+
36
+ export default function App() {
37
+ return (
38
+ <Popover defaultOpen aria-label="Share" trigger={<Button>Share</Button>}>
39
+ <div className="col" style={{ width: 280, gap: 12 }}>
40
+ <b style={{ font: '600 14px/20px var(--font-sans)' }}>Invite to project</b>
41
+ <Field label="Email address"><Input size="sm" type="email" placeholder="name@company.com" /></Field>
42
+ <div className="row" style={{ justifyContent: 'flex-end', gap: 8 }}><Button size="sm" variant="primary">Send invite</Button></div>
43
+ </div>
44
+ </Popover>
45
+ );
46
+ }
47
+ ```
48
+ <!-- /example -->
@@ -8,3 +8,34 @@ Determinate progress glides between values on `ease-glide` (`dur-travel-3`). Wit
8
8
 
9
9
  - Do switch from indeterminate to determinate as soon as you know the total.
10
10
  - Don't use it for a reading such as storage used of a quota: that's the record, not a task, so it's a [`Meter`](../Meter/README.md).
11
+
12
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
13
+ ## Example
14
+
15
+ `Progress` as first used in its preview:
16
+
17
+ ```tsx
18
+ import { Progress } from 'altitude-design';
19
+
20
+ <Progress value={v} aria-label="Upload" />
21
+ ```
22
+
23
+ The full preview, as rendered in Storybook and checked in CI (`col`, `wrap`, `cap` are the preview page's own layout classes, not Altitude's):
24
+
25
+ ```tsx
26
+ import * as React from 'react';
27
+ import { Progress } from 'altitude-design';
28
+
29
+ export default function App() {
30
+ const [v, setV] = React.useState(20);
31
+ React.useEffect(() => { const t = setInterval(() => setV((x) => (x >= 100 ? 8 : x + 23)), 1100); return () => clearInterval(t); }, []);
32
+ return (
33
+ <div className="col wrap">
34
+ <Progress value={v} aria-label="Upload" />
35
+ <Progress aria-label="Loading" />
36
+ <p className="cap">Determinate glides between values; indeterminate travels.</p>
37
+ </div>
38
+ );
39
+ }
40
+ ```
41
+ <!-- /example -->
@@ -38,3 +38,101 @@ Where a person writes to an assistant or agent: a textarea that grows, attachmen
38
38
  - Do return a promise from `onAttach` so an upload shows it's in flight.
39
39
  - Don't send on Enter during IME composition (it's handled; don't override `onKeyDown` to send).
40
40
  - Don't announce "Message sent".
41
+
42
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
43
+ ## Example
44
+
45
+ `PromptInput` as first used in its preview:
46
+
47
+ ```tsx
48
+ import { PromptInput, PromptInputSubmit, PromptInputTextarea, PromptInputToolbar } from 'altitude-design';
49
+
50
+ <PromptInput onSubmit={({ text, mentions, command }) => setSent(`${command ? `/${command} · ` : ''}${text}${mentions ? ` · context: ${mentions.map((m) => m.label).join(', ')}` : ''}`)}
51
+ mentionable={(q) => new Promise((r) => setTimeout(() => r(context.filter((c) => c.label.toLowerCase().includes(q.toLowerCase()))), 250))}
52
+ commands={[{ name: 'review', description: 'Review the current changes' }, { name: 'compact', description: 'Summarise the conversation so far' }, { name: 'clear', description: 'Start a new conversation' }]}>
53
+ <PromptInputTextarea placeholder="@ to add context, / for commands" />
54
+ <PromptInputToolbar><PromptInputSubmit /></PromptInputToolbar>
55
+ </PromptInput>
56
+ ```
57
+
58
+ The full preview, as rendered in Storybook and checked in CI (`col`, `cap`, `pi` are the preview page's own layout classes, not Altitude's):
59
+
60
+ ```tsx
61
+ import * as React from 'react';
62
+ import { PromptInput, PromptInputAttach, PromptInputAttachments, PromptInputDictate, PromptInputSubmit, PromptInputTextarea, PromptInputToolbar, Select } from 'altitude-design';
63
+
64
+ const context = [
65
+ { id: 'f1', label: 'pricing.ts', kind: 'file', description: 'src/pricing.ts' },
66
+ { id: 'f2', label: 'plans.test.ts', kind: 'file', description: 'src/plans.test.ts' },
67
+ { id: 'r1', label: 'Q3 report', kind: 'record', description: 'Shared by Maya, 2 days ago' },
68
+ { id: 'p1', label: 'Maya Chen', kind: 'person', description: 'Billing team' },
69
+ ];
70
+ function Pointed() {
71
+ const [sent, setSent] = React.useState('');
72
+ return (
73
+ <div className="col">
74
+ <PromptInput onSubmit={({ text, mentions, command }) => setSent(`${command ? `/${command} · ` : ''}${text}${mentions ? ` · context: ${mentions.map((m) => m.label).join(', ')}` : ''}`)}
75
+ mentionable={(q) => new Promise((r) => setTimeout(() => r(context.filter((c) => c.label.toLowerCase().includes(q.toLowerCase()))), 250))}
76
+ commands={[{ name: 'review', description: 'Review the current changes' }, { name: 'compact', description: 'Summarise the conversation so far' }, { name: 'clear', description: 'Start a new conversation' }]}>
77
+ <PromptInputTextarea placeholder="@ to add context, / for commands" />
78
+ <PromptInputToolbar><PromptInputSubmit /></PromptInputToolbar>
79
+ </PromptInput>
80
+ <p className="cap">{sent ? `Sent: ${sent}` : 'Type @ to point at a file, record or person, or / at the start for a command.'}</p>
81
+ </div>
82
+ );
83
+ }
84
+ // Dictation: here a scripted transcriber stands in for the app's speech recognition.
85
+ function Dictated() {
86
+ const [status, setStatus] = React.useState('idle');
87
+ const [interim, setInterim] = React.useState('');
88
+ const [final, setFinal] = React.useState();
89
+ const timers = React.useRef([]);
90
+ const start = () => {
91
+ setStatus('listening');
92
+ const words = 'Summarise the open risks and who owns each one'.split(' ');
93
+ words.forEach((_, k) => timers.current.push(setTimeout(() => setInterim(words.slice(0, k + 1).join(' ')), 180 * (k + 1))));
94
+ timers.current.push(setTimeout(() => { setFinal({ id: Date.now(), text: words.join(' ') + '.' }); setInterim(''); setStatus('idle'); }, 180 * words.length + 400));
95
+ };
96
+ const stop = () => { timers.current.forEach(clearTimeout); timers.current = []; setInterim(''); setStatus('idle'); };
97
+ return (
98
+ <div className="col">
99
+ <PromptInput onSubmit={() => {}}>
100
+ <PromptInputTextarea placeholder="Type, or press the microphone and speak" />
101
+ <PromptInputToolbar>
102
+ <PromptInputDictate status={status} interim={interim} final={final} onStart={start} onStop={stop} />
103
+ <PromptInputDictate status="denied" label="Dictate (blocked example)" />
104
+ <PromptInputSubmit />
105
+ </PromptInputToolbar>
106
+ </PromptInput>
107
+ </div>
108
+ );
109
+ }
110
+ export default function App() {
111
+ const [status, setStatus] = React.useState('ready');
112
+ const [last, setLast] = React.useState('');
113
+ const timer = React.useRef();
114
+ const send = ({ text, files }) => {
115
+ setLast(`Sent “${text}”${files.length ? ` with ${files.length} file${files.length > 1 ? 's' : ''}` : ''}.`);
116
+ setStatus('submitted');
117
+ timer.current = setTimeout(() => { setStatus('streaming'); timer.current = setTimeout(() => setStatus('ready'), 2500); }, 600);
118
+ };
119
+ return (
120
+ <div className="col pi">
121
+ <PromptInput onSubmit={send} status={status} onStop={() => { clearTimeout(timer.current); setStatus('ready'); }} accept="image/*,.pdf,.csv"
122
+ onAttach={() => new Promise((r) => setTimeout(r, 900))}>
123
+ <PromptInputAttachments />
124
+ <PromptInputTextarea placeholder="Ask about this workspace" />
125
+ <PromptInputToolbar>
126
+ <PromptInputAttach />
127
+ <Select size="sm" aria-label="Model" defaultValue="opus" items={[{ value: 'opus', label: 'Opus 5.5' }, { value: 'sonnet', label: 'Sonnet 5.5' }, { value: 'haiku', label: 'Haiku 5.5' }]} />
128
+ <PromptInputSubmit />
129
+ </PromptInputToolbar>
130
+ </PromptInput>
131
+ <p className="cap">{status === 'ready' ? (last || 'Enter sends, Shift+Enter adds a line. Paste or drop a file to attach it.') : status === 'submitted' ? 'Sending…' : 'Generating… Escape or Stop ends it.'}</p>
132
+ <Pointed />
133
+ <Dictated />
134
+ </div>
135
+ );
136
+ }
137
+ ```
138
+ <!-- /example -->
@@ -55,3 +55,83 @@ The record and each property's write, and for Edit all the whole-record write an
55
55
  The `PropertyList` section of `build/previews.txt` shows an issue sidebar (status, assignee, priority unset, estimate, created) with shortcuts on, and a “Check your answers” summary with an unset phone number and Edit all. `node checks/property-list.mjs` (from `build/`; `ALT_FIXTURE_ROOT` selects React 19 fixtures) covers the `dl` structure, value buttons and their names, read-only text, unset openings, on-choose saves and announcements, filling an unset value, validation and Escape, shortcuts, Edit all with a cross-field rule and focus return, and 320/960px layouts in both schemes under reduced motion, forced colours and `contrast="more"`.
56
56
  **Narrow.** Under 480px of its own width, an inline list stacks each label over its value, and a proposed change stacks its values, who proposed it and its Reject and Accept.
57
57
 
58
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
59
+ ## Example
60
+
61
+ `PropertyList` as first used in its preview:
62
+
63
+ ```tsx
64
+ import { Badge, Combobox, Input, PropertyList, PropertyListItem, Select } from 'altitude-design';
65
+
66
+ <PropertyList aria-labelledby="pl-issue" shortcuts>
67
+ <PropertyListItem label="Status" value={issue.status} shortcut="s" commit="on-choose" onSave={save('status')} onValueChange={set('status')}
68
+ renderValue={s => <Badge tone={PL_TONE[s]}>{plLabel(PL_STATUS, s)}</Badge>} editor={c => <Select {...c} items={PL_STATUS} size="sm" />} />
69
+ <PropertyListItem label="Assignee" value={issue.assignee} shortcut="a" commit="on-choose" onSave={save('assignee')} onValueChange={set('assignee')}
70
+ renderValue={p => plLabel(PL_PEOPLE, p)} editor={c => <Combobox {...c} items={PL_PEOPLE} size="sm" />} />
71
+ <PropertyListItem label="Priority" value={issue.priority} emptyLabel="Add priority" commit="on-choose" onSave={save('priority')} onValueChange={set('priority')}
72
+ renderValue={p => plLabel(PL_PRIORITY, p)} editor={c => <Select {...c} items={PL_PRIORITY} size="sm" />} />
73
+ <PropertyListItem label="Estimate" value={issue.estimate} onSave={save('estimate')} onValueChange={set('estimate')} required
74
+ validate={n => Number.isInteger(n) && n >= 0 ? undefined : 'Use a whole number, 0 or more.'} renderValue={n => `${n} points`}
75
+ editor={({ value, onValueChange, ...c }) => <Input {...c} type="number" size="sm" value={value ?? ''} onChange={e => onValueChange(e.target.value === '' ? null : Number(e.target.value))} />} />
76
+ <PropertyListItem label="Created" value={issue.created} />
77
+ </PropertyList>
78
+ ```
79
+
80
+ The full preview, as rendered in Storybook and checked in CI (`pl-demo`, `pl-side` are the preview page's own layout classes, not Altitude's):
81
+
82
+ ```tsx
83
+ import * as React from 'react';
84
+ import { Badge, Combobox, Field, Input, PropertyList, PropertyListItem, Select } from 'altitude-design';
85
+
86
+ const PL_STATUS = [{ value: 'todo', label: 'To do' }, { value: 'progress', label: 'In progress' }, { value: 'review', label: 'In review' }, { value: 'done', label: 'Done' }];
87
+ const PL_TONE = { todo: 'neutral', progress: 'accent', review: 'caution', done: 'positive' };
88
+ const PL_PEOPLE = [{ value: 'mara', label: 'Mara Quist' }, { value: 'ines', label: 'Inés Ortega' }, { value: 'tobi', label: 'Tobi Adeyemi' }];
89
+ const PL_PRIORITY = [{ value: 'low', label: 'Low' }, { value: 'medium', label: 'Medium' }, { value: 'high', label: 'High' }];
90
+ const plWait = ms => new Promise(resolve => setTimeout(resolve, ms));
91
+ const plLabel = (list, v) => list.find(o => o.value === v)?.label;
92
+ export default function App() {
93
+ const [issue, setIssue] = React.useState({ status: 'review', assignee: 'mara', priority: '', estimate: 3, created: '21 Oct 2026' });
94
+ const [trip, setTrip] = React.useState({ name: 'Mara Quist', email: 'mara@northwind.example', start: '2026-11-02', end: '2026-11-06', phone: '' });
95
+ const set = key => v => setIssue(i => ({ ...i, [key]: v }));
96
+ const save = key => async draft => { await plWait(250); if (key === 'status' && draft === 'done' && !issue.assignee) return { ok: false, error: 'Assign someone before marking it done.' }; return { ok: true, value: draft }; };
97
+ const tripSet = key => v => setTrip(t => ({ ...t, [key]: v }));
98
+ const tripSave = async draft => { await plWait(200); return { ok: true, value: draft }; };
99
+ return <div className="pl-demo">
100
+ <section className="pl-side">
101
+ <h3 id="pl-issue">Issue details</h3>
102
+ <PropertyList aria-labelledby="pl-issue" shortcuts>
103
+ <PropertyListItem label="Status" value={issue.status} shortcut="s" commit="on-choose" onSave={save('status')} onValueChange={set('status')}
104
+ renderValue={s => <Badge tone={PL_TONE[s]}>{plLabel(PL_STATUS, s)}</Badge>} editor={c => <Select {...c} items={PL_STATUS} size="sm" />} />
105
+ <PropertyListItem label="Assignee" value={issue.assignee} shortcut="a" commit="on-choose" onSave={save('assignee')} onValueChange={set('assignee')}
106
+ renderValue={p => plLabel(PL_PEOPLE, p)} editor={c => <Combobox {...c} items={PL_PEOPLE} size="sm" />} />
107
+ <PropertyListItem label="Priority" value={issue.priority} emptyLabel="Add priority" commit="on-choose" onSave={save('priority')} onValueChange={set('priority')}
108
+ renderValue={p => plLabel(PL_PRIORITY, p)} editor={c => <Select {...c} items={PL_PRIORITY} size="sm" />} />
109
+ <PropertyListItem label="Estimate" value={issue.estimate} onSave={save('estimate')} onValueChange={set('estimate')} required
110
+ validate={n => Number.isInteger(n) && n >= 0 ? undefined : 'Use a whole number, 0 or more.'} renderValue={n => `${n} points`}
111
+ editor={({ value, onValueChange, ...c }) => <Input {...c} type="number" size="sm" value={value ?? ''} onChange={e => onValueChange(e.target.value === '' ? null : Number(e.target.value))} />} />
112
+ <PropertyListItem label="Created" value={issue.created} />
113
+ </PropertyList>
114
+ </section>
115
+ <section>
116
+ <h3 id="pl-trip">Check your answers</h3>
117
+ <PropertyList aria-labelledby="pl-trip" layout="inline"
118
+ editAll={{ label: 'trip details', value: trip, onValueChange: setTrip,
119
+ validate: d => d.end < d.start ? { end: 'The last day must be on or after the first.' } : undefined,
120
+ onSave: async d => { await plWait(250); return { ok: true, value: d }; },
121
+ renderEditor: field => <>
122
+ <Field label="Name" error={field.error('name')}><Input {...field('name')} /></Field>
123
+ <Field label="Email" error={field.error('email')}><Input {...field('email')} type="email" /></Field>
124
+ <Field label="First day" error={field.error('start')}><Input {...field('start')} type="date" /></Field>
125
+ <Field label="Last day" error={field.error('end')}><Input {...field('end')} type="date" /></Field>
126
+ <Field label="Phone"><Input {...field('phone')} type="tel" /></Field>
127
+ </> }}>
128
+ <PropertyListItem label="Name" value={trip.name} onSave={tripSave} onValueChange={tripSet('name')} required />
129
+ <PropertyListItem label="Email" value={trip.email} onSave={tripSave} onValueChange={tripSet('email')} required />
130
+ <PropertyListItem label="Dates" value={`${trip.start} to ${trip.end}`} />
131
+ <PropertyListItem label="Phone" value={trip.phone} emptyLabel="Add phone number" onSave={tripSave} onValueChange={tripSet('phone')} />
132
+ </PropertyList>
133
+ </section>
134
+ </div>;
135
+ }
136
+ ```
137
+ <!-- /example -->
@@ -25,3 +25,57 @@ An agent asks the person something before it carries on: "Which environment shou
25
25
  - Do describe what each option means for the run.
26
26
  - Do offer a way to let the assistant decide when a default exists.
27
27
  - Don't preselect an option or move focus to the question.
28
+
29
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
30
+ ## Example
31
+
32
+ `Question` as first used in its preview:
33
+
34
+ ```tsx
35
+ import { Question, QuestionOption } from 'altitude-design';
36
+
37
+ <Question title="Which environment should I deploy to?" state={q.state} answer={q.answer} answeredAt={q.at} allowOther
38
+ answerLabel="Deploy" skipLabel="Let the assistant decide" onSkip={() => setQ({ state: 'expired' })}
39
+ onAnswer={(answer) => setQ({ state: 'answered', answer, at: Date.now() })}>
40
+ <QuestionOption value="staging" label="Staging" description="Resets nightly. Safe to try things." />
41
+ <QuestionOption value="production" label="Production" description="Customers will see it immediately." />
42
+ </Question>
43
+ ```
44
+
45
+ The full preview, as rendered in Storybook and checked in CI (`qs` is the preview page's own layout class, not Altitude's):
46
+
47
+ ```tsx
48
+ import * as React from 'react';
49
+ import { Question, QuestionOption, QuestionSet } from 'altitude-design';
50
+
51
+ export default function App() {
52
+ const [q, setQ] = React.useState({ state: 'asking' });
53
+ const [set, setSet] = React.useState({ state: 'asking' });
54
+ return (
55
+ <div className="qs">
56
+ <Question title="Which environment should I deploy to?" state={q.state} answer={q.answer} answeredAt={q.at} allowOther
57
+ answerLabel="Deploy" skipLabel="Let the assistant decide" onSkip={() => setQ({ state: 'expired' })}
58
+ onAnswer={(answer) => setQ({ state: 'answered', answer, at: Date.now() })}>
59
+ <QuestionOption value="staging" label="Staging" description="Resets nightly. Safe to try things." />
60
+ <QuestionOption value="production" label="Production" description="Customers will see it immediately." />
61
+ </Question>
62
+ <QuestionSet state={set.state} onAnswer={(answers) => setSet({ state: 'answered', answers })}>
63
+ <Question name="column" title="Keep the old status column, or drop it?" answer={set.answers?.column}>
64
+ <QuestionOption value="keep" label="Keep it" description="Writes go to both columns until you remove it." />
65
+ <QuestionOption value="drop" label="Drop it" description="The migration removes it after copying." />
66
+ </Question>
67
+ <Question name="notify" title="Who should hear about it?" multiple answer={set.answers?.notify}>
68
+ <QuestionOption value="eng" label="#eng-billing" />
69
+ <QuestionOption value="support" label="#support" />
70
+ <QuestionOption value="owner" label="The account owner" />
71
+ </Question>
72
+ </QuestionSet>
73
+ <Question title="Which layout should I use for the invoice?" state="answered" answer={{ values: ['compact'] }} answeredAt={Date.now() - 3 * 60000}>
74
+ <QuestionOption value="compact" label="Compact" />
75
+ <QuestionOption value="detailed" label="Detailed" />
76
+ </Question>
77
+ </div>
78
+ );
79
+ }
80
+ ```
81
+ <!-- /example -->
@@ -8,3 +8,40 @@ Each option is a round trench, the same as the switch track. The group owns a si
8
8
 
9
9
  - Default to a sensible choice; a group with no thumb is rarely what you want.
10
10
  - Don't use radios for more than about six options; use a menu.
11
+
12
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
13
+ ## Example
14
+
15
+ `RadioGroup` as first used in its preview:
16
+
17
+ ```tsx
18
+ import { Radio, RadioGroup } from 'altitude-design';
19
+
20
+ <RadioGroup defaultValue="team" aria-label="Plan">
21
+ <Radio value="solo" label="Solo" description="One seat, unlimited projects." />
22
+ <Radio value="team" label="Team" description="Up to 20 seats and shared libraries." />
23
+ <Radio value="org" label="Organization" disabled description="Talk to us." />
24
+ </RadioGroup>
25
+ ```
26
+
27
+ The full preview, as rendered in Storybook and checked in CI (`pair` is the preview page's own layout class, not Altitude's):
28
+
29
+ ```tsx
30
+ import { Radio, RadioGroup } from 'altitude-design';
31
+
32
+ export default function App() {
33
+ return (
34
+ <div className="pair">
35
+ <RadioGroup defaultValue="team" aria-label="Plan">
36
+ <Radio value="solo" label="Solo" description="One seat, unlimited projects." />
37
+ <Radio value="team" label="Team" description="Up to 20 seats and shared libraries." />
38
+ <Radio value="org" label="Organization" disabled description="Talk to us." />
39
+ </RadioGroup>
40
+ <RadioGroup defaultValue="m" orientation="horizontal" aria-label="Size">
41
+ <Radio value="s" label="Small" /><Radio value="m" label="Medium" /><Radio value="l" label="Large" />
42
+ </RadioGroup>
43
+ </div>
44
+ );
45
+ }
46
+ ```
47
+ <!-- /example -->
@@ -24,3 +24,50 @@ A moment shown as "3 minutes ago", for activity feeds, audit logs, comments and
24
24
  - Do keep the tooltip on wherever the exact time matters (audit logs, billing).
25
25
  - Don't put a RelativeTime in a live region, or announce it ticking.
26
26
  - Don't format times by hand with string concatenation.
27
+
28
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
29
+ ## Example
30
+
31
+ `RelativeTime` as first used in its preview:
32
+
33
+ ```tsx
34
+ import { RelativeTime } from 'altitude-design';
35
+
36
+ <RelativeTime date={when} format="auto" />
37
+ ```
38
+
39
+ The full preview, as rendered in Storybook and checked in CI (`rt`, `col`, `row`, `cap` are the preview page's own layout classes, not Altitude's):
40
+
41
+ ```tsx
42
+ import * as React from 'react';
43
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, RelativeTime } from 'altitude-design';
44
+
45
+ const ago = (ms) => new Date(Date.now() - ms);
46
+ const events = [
47
+ ['Deploy #482 finished', ago(20_000)],
48
+ ['Maya invited Theo', ago(3 * 60_000)],
49
+ ['Invoice INV-0042 paid', ago(5 * 3_600_000)],
50
+ ['API key rotated', ago(26 * 3_600_000)],
51
+ ['Workspace created', ago(40 * 86_400_000)],
52
+ ];
53
+ export default function App() {
54
+ const [edited, setEdited] = React.useState(() => ago(12 * 60_000));
55
+ return (
56
+ <div className="rt">
57
+ <ul aria-label="Activity">
58
+ {events.map(([what, when]) => <li key={what}><span>{what}</span><RelativeTime date={when} format="auto" /></li>)}
59
+ </ul>
60
+ <Card>
61
+ <CardHeader><CardTitle>Q3 board deck</CardTitle><CardDescription>Last edited <RelativeTime date={edited} /></CardDescription></CardHeader>
62
+ <CardContent>
63
+ <div className="col">
64
+ <div className="row"><Button size="sm" onClick={() => setEdited(new Date())}>Edit again</Button></div>
65
+ <p className="cap">Time passing changes the words with nothing else. A new moment (an edit) settles with a tint. Point at or tab to a time for its exact date, time and zone. Past a week, the feed shows a date.</p>
66
+ </div>
67
+ </CardContent>
68
+ </Card>
69
+ </div>
70
+ );
71
+ }
72
+ ```
73
+ <!-- /example -->
@@ -32,3 +32,80 @@ The slot of an item that can leave its list: a file archived, a webhook removed,
32
32
  - Don't hold the receipt for longer to "make sure": the person can stop it by pointing at it or focusing it.
33
33
  - Don't use it for a line removed while typing (an empty `Checklist` line); nothing was lost, and the hold would sit in the way. Checklist glides the line shut instead.
34
34
  - Don't use it for permanent deletion without saying so: a deletion's receipt has no Undo.
35
+
36
+ <!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
37
+ ## Example
38
+
39
+ `RemovalReceipt` as first used in its preview:
40
+
41
+ ```tsx
42
+ import { Button, RemovalReceipt, toast } from 'altitude-design';
43
+
44
+ <RemovalReceipt as="li" key={hook.id} removed={!!receipt} pending={receipt?.pending} arrive={back === hook.id}
45
+ title={receipt?.undoing ? `Restoring ${hook.name}…` : receipt?.pending ? `Removing ${hook.name}…` : `${hook.name} removed.`}
46
+ action={receipt && (!receipt.pending || receipt.undoing) &&
47
+ <Button size="sm" loading={!!receipt.undoing} aria-label={`Undo removing ${hook.name}`} onClick={() => undo(hook)}>Undo</Button>}
48
+ onClosed={() => {
49
+ put(hook.id, null); setHooks((list) => list.filter((h) => h.id !== hook.id));
50
+ toast({ title: `${hook.name} removed.`, action: { label: 'Undo', onClick: () => restore(hook) } });
51
+ }}>
52
+ <div className="rr-body"><div className="rr-name">{hook.name}</div><div className="rr-url">{hook.url}</div></div>
53
+ <Button size="sm" variant="ghost" aria-label={`Remove ${hook.name}`} onClick={() => remove(hook)}>Remove</Button>
54
+ </RemovalReceipt>
55
+ ```
56
+
57
+ The full preview, as rendered in Storybook and checked in CI (`rr`, `rr-hint`, `rr-body`, `rr-name`, `rr-url`, `rr-empty` are the preview page's own layout classes, not Altitude's):
58
+
59
+ ```tsx
60
+ import * as React from 'react';
61
+ import { Button, RemovalReceipt, Toaster, toast } from 'altitude-design';
62
+
63
+ const HOOKS = [
64
+ { id: 'h1', name: 'Deploy notifications', url: 'https://hooks.example.com/deploys' },
65
+ { id: 'h2', name: 'Billing events', url: 'https://hooks.example.com/billing' },
66
+ { id: 'h3', name: 'New sign-ups', url: 'https://hooks.example.com/signups' },
67
+ { id: 'h4', name: 'Error alerts', url: 'https://hooks.example.com/errors' },
68
+ ];
69
+ // A fictional service: removing and restoring a webhook each take 400ms.
70
+ const later = (value) => new Promise((resolve) => setTimeout(() => resolve(value), 400));
71
+ export default function App() {
72
+ const [hooks, setHooks] = React.useState(HOOKS);
73
+ const [receipts, setReceipts] = React.useState({});
74
+ const [back, setBack] = React.useState(null);
75
+ const put = (id, value) => setReceipts((r) => { const next = { ...r }; if (value) next[id] = value; else delete next[id]; return next; });
76
+ const remove = async (hook) => { put(hook.id, { pending: true }); await later(); put(hook.id, { pending: false }); };
77
+ const undo = async (hook) => { put(hook.id, { pending: true, undoing: true }); await later(); put(hook.id, null); };
78
+ const restore = async (hook) => {
79
+ await later();
80
+ setHooks((list) => HOOKS.filter((h) => h.id === hook.id || list.some((x) => x.id === h.id)));
81
+ setBack(hook.id);
82
+ };
83
+ return (
84
+ <section className="rr" aria-labelledby="rr-title">
85
+ <h3 id="rr-title">Webhooks</h3>
86
+ <p className="rr-hint">Remove one: its result stays in its place, then the list closes up. Point at the result to keep it.</p>
87
+ <ul>
88
+ {hooks.map((hook) => {
89
+ const receipt = receipts[hook.id];
90
+ return (
91
+ <RemovalReceipt as="li" key={hook.id} removed={!!receipt} pending={receipt?.pending} arrive={back === hook.id}
92
+ title={receipt?.undoing ? `Restoring ${hook.name}…` : receipt?.pending ? `Removing ${hook.name}…` : `${hook.name} removed.`}
93
+ action={receipt && (!receipt.pending || receipt.undoing) &&
94
+ <Button size="sm" loading={!!receipt.undoing} aria-label={`Undo removing ${hook.name}`} onClick={() => undo(hook)}>Undo</Button>}
95
+ onClosed={() => {
96
+ put(hook.id, null); setHooks((list) => list.filter((h) => h.id !== hook.id));
97
+ toast({ title: `${hook.name} removed.`, action: { label: 'Undo', onClick: () => restore(hook) } });
98
+ }}>
99
+ <div className="rr-body"><div className="rr-name">{hook.name}</div><div className="rr-url">{hook.url}</div></div>
100
+ <Button size="sm" variant="ghost" aria-label={`Remove ${hook.name}`} onClick={() => remove(hook)}>Remove</Button>
101
+ </RemovalReceipt>
102
+ );
103
+ })}
104
+ </ul>
105
+ {!hooks.length && <p className="rr-empty">No webhooks.</p>}
106
+ <Toaster />
107
+ </section>
108
+ );
109
+ }
110
+ ```
111
+ <!-- /example -->