@twoux/ui 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/LICENSE.md +61 -0
  2. package/LICENSES/Apache-2.0.txt +202 -0
  3. package/NOTICE.md +64 -0
  4. package/README.md +211 -2
  5. package/SHADCN-DIFF.md +228 -0
  6. package/agent/AGENTS.md +358 -0
  7. package/agent/docs-md/accessibility.md +49 -0
  8. package/agent/docs-md/action-catalogue.md +81 -0
  9. package/agent/docs-md/actions.md +74 -0
  10. package/agent/docs-md/agent-activity-log.md +70 -0
  11. package/agent/docs-md/agent-permissions.md +82 -0
  12. package/agent/docs-md/agent-run.md +90 -0
  13. package/agent/docs-md/agents-on-behalf.md +74 -0
  14. package/agent/docs-md/ai-column.md +85 -0
  15. package/agent/docs-md/ai-origin-label.md +70 -0
  16. package/agent/docs-md/announcing-features.md +73 -0
  17. package/agent/docs-md/answer-actions.md +81 -0
  18. package/agent/docs-md/app-shell.md +114 -0
  19. package/agent/docs-md/artifact-card.md +89 -0
  20. package/agent/docs-md/ask-on-screen.md +72 -0
  21. package/agent/docs-md/assistant-page.md +100 -0
  22. package/agent/docs-md/assistant-panel.md +142 -0
  23. package/agent/docs-md/avatar.md +82 -0
  24. package/agent/docs-md/banner.md +106 -0
  25. package/agent/docs-md/board.md +163 -0
  26. package/agent/docs-md/breadcrumb.md +89 -0
  27. package/agent/docs-md/breakdown-table.md +97 -0
  28. package/agent/docs-md/bulk-action-bar.md +112 -0
  29. package/agent/docs-md/button.md +293 -0
  30. package/agent/docs-md/card.md +115 -0
  31. package/agent/docs-md/change-list.md +99 -0
  32. package/agent/docs-md/change-request.md +166 -0
  33. package/agent/docs-md/chart-area.md +87 -0
  34. package/agent/docs-md/chart-bar.md +100 -0
  35. package/agent/docs-md/chart-box-plot.md +88 -0
  36. package/agent/docs-md/chart-bullet.md +89 -0
  37. package/agent/docs-md/chart-candlestick.md +112 -0
  38. package/agent/docs-md/chart-card-line.md +120 -0
  39. package/agent/docs-md/chart-donut.md +90 -0
  40. package/agent/docs-md/chart-drill-down.md +72 -0
  41. package/agent/docs-md/chart-funnel.md +71 -0
  42. package/agent/docs-md/chart-heatmap.md +76 -0
  43. package/agent/docs-md/chart-histogram.md +81 -0
  44. package/agent/docs-md/chart-map.md +165 -0
  45. package/agent/docs-md/chart-mixed.md +88 -0
  46. package/agent/docs-md/chart-price.md +92 -0
  47. package/agent/docs-md/chart-radial-bars.md +109 -0
  48. package/agent/docs-md/chart-radial-lines.md +111 -0
  49. package/agent/docs-md/chart-sankey.md +104 -0
  50. package/agent/docs-md/chart-scatter.md +87 -0
  51. package/agent/docs-md/chart-share-bar.md +89 -0
  52. package/agent/docs-md/chart-treemap.md +98 -0
  53. package/agent/docs-md/chart-waterfall.md +93 -0
  54. package/agent/docs-md/checkbox.md +131 -0
  55. package/agent/docs-md/choosing-selection-control.md +71 -0
  56. package/agent/docs-md/citations-sources.md +79 -0
  57. package/agent/docs-md/citing-sources.md +63 -0
  58. package/agent/docs-md/clarify-before-acting.md +69 -0
  59. package/agent/docs-md/clarifying-question.md +92 -0
  60. package/agent/docs-md/code-block.md +174 -0
  61. package/agent/docs-md/colours.md +87 -0
  62. package/agent/docs-md/columns-menu.md +140 -0
  63. package/agent/docs-md/combobox.md +161 -0
  64. package/agent/docs-md/command-palette.md +126 -0
  65. package/agent/docs-md/confidence-note.md +87 -0
  66. package/agent/docs-md/configurable-dashboard.md +76 -0
  67. package/agent/docs-md/confirmation-dialog.md +93 -0
  68. package/agent/docs-md/connected-tools.md +80 -0
  69. package/agent/docs-md/connector-picker.md +127 -0
  70. package/agent/docs-md/consistency.md +53 -0
  71. package/agent/docs-md/content-density.md +61 -0
  72. package/agent/docs-md/conversation-history.md +78 -0
  73. package/agent/docs-md/create-record.md +80 -0
  74. package/agent/docs-md/csv-import.md +88 -0
  75. package/agent/docs-md/dashboard-items.md +78 -0
  76. package/agent/docs-md/data-export.md +61 -0
  77. package/agent/docs-md/data-grid.md +137 -0
  78. package/agent/docs-md/data-vis-colours.md +76 -0
  79. package/agent/docs-md/data-visualisation.md +76 -0
  80. package/agent/docs-md/date-picker.md +178 -0
  81. package/agent/docs-md/delete-record.md +75 -0
  82. package/agent/docs-md/dense-table.md +495 -0
  83. package/agent/docs-md/design-tokens.md +53 -0
  84. package/agent/docs-md/disabled-read-only.md +75 -0
  85. package/agent/docs-md/drag-and-drop.md +74 -0
  86. package/agent/docs-md/dropdown-menu.md +137 -0
  87. package/agent/docs-md/edit-record.md +73 -0
  88. package/agent/docs-md/empty-loading-error.md +78 -0
  89. package/agent/docs-md/empty-state.md +128 -0
  90. package/agent/docs-md/error-messages.md +75 -0
  91. package/agent/docs-md/error-state.md +123 -0
  92. package/agent/docs-md/export-menu.md +86 -0
  93. package/agent/docs-md/feedback-prompt.md +88 -0
  94. package/agent/docs-md/field-mapping.md +78 -0
  95. package/agent/docs-md/file-drop.md +110 -0
  96. package/agent/docs-md/filter-bar.md +138 -0
  97. package/agent/docs-md/filter-panel.md +103 -0
  98. package/agent/docs-md/filtering-search.md +79 -0
  99. package/agent/docs-md/follow-ups-starters.md +79 -0
  100. package/agent/docs-md/form-validation.md +95 -0
  101. package/agent/docs-md/generated-columns.md +65 -0
  102. package/agent/docs-md/generated-drafts-files.md +71 -0
  103. package/agent/docs-md/grouped-list.md +89 -0
  104. package/agent/docs-md/guide-cue.md +170 -0
  105. package/agent/docs-md/help-panel.md +103 -0
  106. package/agent/docs-md/help-system.md +74 -0
  107. package/agent/docs-md/iconography.md +49 -0
  108. package/agent/docs-md/inbox.md +108 -0
  109. package/agent/docs-md/inline-editing-validation.md +80 -0
  110. package/agent/docs-md/inline-input-in-answer.md +70 -0
  111. package/agent/docs-md/items-palette.md +116 -0
  112. package/agent/docs-md/keyboard-model.md +47 -0
  113. package/agent/docs-md/keyboard-navigation.md +68 -0
  114. package/agent/docs-md/keyboard-shortcut-hint.md +87 -0
  115. package/agent/docs-md/keyboard-shortcut-sheet.md +90 -0
  116. package/agent/docs-md/labelling-ai-output.md +66 -0
  117. package/agent/docs-md/layout.md +47 -0
  118. package/agent/docs-md/list-table.md +254 -0
  119. package/agent/docs-md/loading-skeleton.md +91 -0
  120. package/agent/docs-md/managing-agents.md +71 -0
  121. package/agent/docs-md/message-composer.md +105 -0
  122. package/agent/docs-md/migrating.md +49 -0
  123. package/agent/docs-md/motion.md +93 -0
  124. package/agent/docs-md/navigation.md +76 -0
  125. package/agent/docs-md/notification-channels.md +65 -0
  126. package/agent/docs-md/notifications-decision.md +100 -0
  127. package/agent/docs-md/onboarding-step.md +114 -0
  128. package/agent/docs-md/order-book.md +108 -0
  129. package/agent/docs-md/page-header.md +96 -0
  130. package/agent/docs-md/pagination.md +88 -0
  131. package/agent/docs-md/pane-layout.md +105 -0
  132. package/agent/docs-md/period-comparison.md +66 -0
  133. package/agent/docs-md/plan-card.md +120 -0
  134. package/agent/docs-md/popover-rules.md +88 -0
  135. package/agent/docs-md/prompt-editor.md +90 -0
  136. package/agent/docs-md/propose-apply.md +65 -0
  137. package/agent/docs-md/provider-logo.md +97 -0
  138. package/agent/docs-md/radio.md +83 -0
  139. package/agent/docs-md/ranked-list.md +102 -0
  140. package/agent/docs-md/reasoning-progress.md +72 -0
  141. package/agent/docs-md/record-detail.md +71 -0
  142. package/agent/docs-md/regenerating-answers.md +70 -0
  143. package/agent/docs-md/run-status-bar.md +94 -0
  144. package/agent/docs-md/saved-tasks-schedules.md +63 -0
  145. package/agent/docs-md/saved-views.md +149 -0
  146. package/agent/docs-md/saving-views.md +68 -0
  147. package/agent/docs-md/scheduler.md +258 -0
  148. package/agent/docs-md/secondary-panels.md +72 -0
  149. package/agent/docs-md/segmented-control.md +88 -0
  150. package/agent/docs-md/selection-bulk-actions.md +70 -0
  151. package/agent/docs-md/separator.md +75 -0
  152. package/agent/docs-md/setup-checklist.md +92 -0
  153. package/agent/docs-md/shape-elevation.md +55 -0
  154. package/agent/docs-md/sheet-grid.md +211 -0
  155. package/agent/docs-md/side-panel.md +134 -0
  156. package/agent/docs-md/sign-in.md +114 -0
  157. package/agent/docs-md/spacing.md +55 -0
  158. package/agent/docs-md/split-button.md +100 -0
  159. package/agent/docs-md/stat-compare.md +82 -0
  160. package/agent/docs-md/static-dashboard.md +75 -0
  161. package/agent/docs-md/status-badge.md +140 -0
  162. package/agent/docs-md/step-card.md +75 -0
  163. package/agent/docs-md/stepper.md +92 -0
  164. package/agent/docs-md/suggested-changes.md +91 -0
  165. package/agent/docs-md/switch.md +78 -0
  166. package/agent/docs-md/table-settings.md +81 -0
  167. package/agent/docs-md/tabs.md +85 -0
  168. package/agent/docs-md/tag-input.md +108 -0
  169. package/agent/docs-md/tag.md +79 -0
  170. package/agent/docs-md/task-builder.md +87 -0
  171. package/agent/docs-md/team-instructions.md +72 -0
  172. package/agent/docs-md/templates-gallery.md +85 -0
  173. package/agent/docs-md/text-input.md +160 -0
  174. package/agent/docs-md/thinking-steps.md +89 -0
  175. package/agent/docs-md/timeline.md +189 -0
  176. package/agent/docs-md/timestamp.md +84 -0
  177. package/agent/docs-md/timestamps.md +70 -0
  178. package/agent/docs-md/toast.md +117 -0
  179. package/agent/docs-md/tooltip.md +90 -0
  180. package/agent/docs-md/typography.md +62 -0
  181. package/agent/docs-md/undo-destructive.md +66 -0
  182. package/agent/docs-md/unsaved-changes.md +71 -0
  183. package/agent/docs-md/usage-meter.md +80 -0
  184. package/agent/docs-md/user-feedback.md +71 -0
  185. package/agent/docs-md/voice-and-tone.md +60 -0
  186. package/agent/docs-md/why-choice.md +93 -0
  187. package/agent/docs-md/workspace-onboarding.md +74 -0
  188. package/agent/docs-md/writing.md +48 -0
  189. package/agent/llms-full.txt +18369 -0
  190. package/agent/llms.txt +198 -0
  191. package/agent/skills/twoux-audit/SKILL.md +49 -0
  192. package/agent/skills/twoux-build/SKILL.md +39 -0
  193. package/agent/skills/twoux-design/SKILL.md +54 -0
  194. package/agent/skills/twoux-write/SKILL.md +32 -0
  195. package/bin/check.mjs +123 -0
  196. package/bin/docs.mjs +24 -0
  197. package/bin/guard.mjs +27 -0
  198. package/bin/init.mjs +113 -0
  199. package/bin/knowledge.mjs +87 -0
  200. package/bin/mcp.mjs +116 -0
  201. package/bin/theme.mjs +168 -0
  202. package/bin/twoux.mjs +25 -0
  203. package/package.json +80 -3
  204. package/registry/jobs.json +1028 -0
  205. package/registry/palette.json +770 -0
  206. package/registry/rules.json +913 -0
  207. package/src/components/agent-review.tsx +364 -0
  208. package/src/components/ai-basics.tsx +212 -0
  209. package/src/components/ai-work.tsx +304 -0
  210. package/src/components/ai.tsx +227 -0
  211. package/src/components/alert-dialog.tsx +203 -0
  212. package/src/components/alert.tsx +86 -0
  213. package/src/components/avatar.tsx +114 -0
  214. package/src/components/badge.tsx +59 -0
  215. package/src/components/board.tsx +397 -0
  216. package/src/components/breadcrumb.tsx +114 -0
  217. package/src/components/button-group.tsx +90 -0
  218. package/src/components/button.tsx +109 -0
  219. package/src/components/calendar.tsx +247 -0
  220. package/src/components/card.tsx +102 -0
  221. package/src/components/chart-map.tsx +336 -0
  222. package/src/components/charts-more.tsx +1331 -0
  223. package/src/components/charts-radial.tsx +328 -0
  224. package/src/components/charts.tsx +390 -0
  225. package/src/components/checkbox.tsx +40 -0
  226. package/src/components/code.tsx +139 -0
  227. package/src/components/collapsible.tsx +37 -0
  228. package/src/components/combobox.tsx +339 -0
  229. package/src/components/command-extras.tsx +11 -0
  230. package/src/components/command.tsx +196 -0
  231. package/src/components/data-grid.tsx +213 -0
  232. package/src/components/data-table.tsx +334 -0
  233. package/src/components/data-views.tsx +740 -0
  234. package/src/components/date-picker.tsx +686 -0
  235. package/src/components/dense-table.tsx +207 -0
  236. package/src/components/dialog.tsx +162 -0
  237. package/src/components/dropdown-menu.tsx +264 -0
  238. package/src/components/empty.tsx +114 -0
  239. package/src/components/feedback-prompt.tsx +80 -0
  240. package/src/components/field.tsx +254 -0
  241. package/src/components/fields.tsx +112 -0
  242. package/src/components/flow.tsx +840 -0
  243. package/src/components/guide-cue.tsx +273 -0
  244. package/src/components/inbox.tsx +113 -0
  245. package/src/components/input-group.tsx +176 -0
  246. package/src/components/input.tsx +26 -0
  247. package/src/components/kbd.tsx +34 -0
  248. package/src/components/label.tsx +27 -0
  249. package/src/components/labels.tsx +176 -0
  250. package/src/components/motion.tsx +136 -0
  251. package/src/components/onboarding.tsx +454 -0
  252. package/src/components/order-book.tsx +185 -0
  253. package/src/components/pane-layout.tsx +417 -0
  254. package/src/components/popover.tsx +96 -0
  255. package/src/components/provider-logos.data.ts +49 -0
  256. package/src/components/radio-group.tsx +52 -0
  257. package/src/components/scheduler.tsx +997 -0
  258. package/src/components/segmented-control.tsx +28 -0
  259. package/src/components/select.tsx +195 -0
  260. package/src/components/separator.tsx +31 -0
  261. package/src/components/sheet.tsx +148 -0
  262. package/src/components/shell.tsx +426 -0
  263. package/src/components/shortcut-sheet.tsx +83 -0
  264. package/src/components/skeleton.tsx +18 -0
  265. package/src/components/sonner.tsx +107 -0
  266. package/src/components/spinner.tsx +13 -0
  267. package/src/components/split-button.tsx +58 -0
  268. package/src/components/stepper.tsx +78 -0
  269. package/src/components/switch.tsx +43 -0
  270. package/src/components/table.tsx +126 -0
  271. package/src/components/tabs.tsx +118 -0
  272. package/src/components/tag-input.tsx +253 -0
  273. package/src/components/textarea.tsx +21 -0
  274. package/src/components/theme.tsx +80 -0
  275. package/src/components/timeline.tsx +408 -0
  276. package/src/components/toggle-group.tsx +87 -0
  277. package/src/components/toggle.tsx +54 -0
  278. package/src/components/tooltip.tsx +65 -0
  279. package/src/components/workspace-actions.tsx +294 -0
  280. package/src/components/workspace-import.tsx +465 -0
  281. package/src/components/workspace.tsx +1884 -0
  282. package/src/index.ts +80 -0
  283. package/src/lib/active-indicator.tsx +53 -0
  284. package/src/lib/chart-kit.tsx +72 -0
  285. package/src/lib/cn.ts +6 -0
  286. package/src/lib/shortcuts.tsx +61 -0
  287. package/src/lib/workspace.tsx +10 -0
  288. package/src/shadcn/alert-dialog.tsx +196 -0
  289. package/src/shadcn/alert.tsx +65 -0
  290. package/src/shadcn/avatar.tsx +108 -0
  291. package/src/shadcn/badge.tsx +47 -0
  292. package/src/shadcn/breadcrumb.tsx +108 -0
  293. package/src/shadcn/button-group.tsx +83 -0
  294. package/src/shadcn/button.tsx +63 -0
  295. package/src/shadcn/calendar.tsx +220 -0
  296. package/src/shadcn/card.tsx +91 -0
  297. package/src/shadcn/checkbox.tsx +31 -0
  298. package/src/shadcn/collapsible.tsx +33 -0
  299. package/src/shadcn/command.tsx +184 -0
  300. package/src/shadcn/dialog.tsx +158 -0
  301. package/src/shadcn/dropdown-menu.tsx +256 -0
  302. package/src/shadcn/empty.tsx +103 -0
  303. package/src/shadcn/field.tsx +248 -0
  304. package/src/shadcn/input-group.tsx +170 -0
  305. package/src/shadcn/input.tsx +20 -0
  306. package/src/shadcn/kbd.tsx +28 -0
  307. package/src/shadcn/label.tsx +23 -0
  308. package/src/shadcn/popover.tsx +88 -0
  309. package/src/shadcn/radio-group.tsx +44 -0
  310. package/src/shadcn/select.tsx +189 -0
  311. package/src/shadcn/separator.tsx +27 -0
  312. package/src/shadcn/sheet.tsx +142 -0
  313. package/src/shadcn/skeleton.tsx +13 -0
  314. package/src/shadcn/sonner.tsx +40 -0
  315. package/src/shadcn/switch.tsx +34 -0
  316. package/src/shadcn/table.tsx +115 -0
  317. package/src/shadcn/tabs.tsx +90 -0
  318. package/src/shadcn/textarea.tsx +17 -0
  319. package/src/shadcn/toggle-group.tsx +83 -0
  320. package/src/shadcn/toggle.tsx +46 -0
  321. package/src/shadcn/tooltip.tsx +56 -0
  322. package/src/styles/palette.css +127 -0
  323. package/src/styles/theme.css +35 -0
  324. package/src/styles/twoux.css +236 -0
  325. package/theme.json +14 -0
@@ -0,0 +1,87 @@
1
+ # Stacked area chart
2
+
3
+ Two or more series stacked over time, so the top edge is the total and each band is one part of it, such as applications per week split by source.
4
+
5
+ Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - When the total over time matters and so does how it splits, such as weekly applications by source (LinkedIn, Indeed, Careers page, Referral).
10
+ - When the parts always add up to a meaningful whole, such as candidates in the pipeline by stage at the end of each week.
11
+
12
+ ## When not to use
13
+
14
+ - For one series. Use the Line chart card; a single filled area only adds ink.
15
+ - For values that can go negative, such as net change in open roles. Use the Line chart card or Bar chart.
16
+ - For series that do not add up to a whole, such as applications for two unrelated listings. Overlapping areas hide each other; use lines.
17
+ - When people need to compare the middle bands exactly. Only the bottom band sits on a flat baseline; use the Line chart card or a Breakdown table.
18
+
19
+ ## Structure
20
+
21
+ 1. Title (what is measured) and subtitle (period)
22
+ 2. Controls row: time range, Chart or Table
23
+ 3. Plot: stacked bands from a zero baseline, a thin surface-colour line between bands, one y-axis
24
+ 4. Legend: one item per band, in stack order, each a toggle
25
+ 5. Crosshair tooltip: every band's value and the total at the hovered date
26
+ 6. Optional threshold: dashed line with a text label
27
+ 7. Footer: source and updated time
28
+
29
+ ## Guidelines
30
+
31
+ - Always stack, always from zero. The top edge must read as the true total.
32
+ - Use two to five bands. Fold the smallest sources into "Other" at the top of the stack rather than adding a sixth colour.
33
+ - Put the largest or steadiest band at the bottom so the most important part has a flat baseline, and keep the stack order the same on every chart.
34
+ - Use categorical colours in fixed order from colour 1, separated by a thin line in the surface colour so neighbouring bands stay distinct.
35
+ - The tooltip lists bands in the same order as the stack, top to bottom, then the total.
36
+ - Show missing weeks as a gap, never as a drop to zero.
37
+ - Hiding a band from the legend restacks the rest and updates the total in the tooltip. Colours do not move.
38
+
39
+ ## Content
40
+
41
+ - Title names the total and the split: "Applications by source". Subtitle gives the period: "Last 12 weeks".
42
+ - Legend items use the names people use in the product: "Careers page", not "careers_site". Never truncate a name if the end is what tells two apart.
43
+ - Skip x-axis labels at a regular interval (every other week) instead of crowding them, and abbreviate months: "3 Nov".
44
+
45
+ ## Accessibility
46
+
47
+ - The chart has an accessible name matching its title and a description such as "Stacked area chart showing applications by source, last 12 weeks."
48
+ - The Table view lists every band per week plus the total, and is the long description.
49
+ - Bands are named in the tooltip and legend, so colour is never the only way to tell them apart.
50
+ - Legend items are toggle buttons with a pressed state; hiding one is announced politely: "Referral hidden".
51
+
52
+ ## Keyboard
53
+
54
+ | Keys | Action |
55
+ |---|---|
56
+ | Tab | Focus the plot, then each legend item |
57
+ | ← → | Move the crosshair to the previous or next week |
58
+ | Enter | Pin the tooltip so its links can be reached with Tab |
59
+ | Esc | Close the tooltip and return focus to the plot |
60
+ | Space (legend item) | Show or hide that band |
61
+
62
+ ## Examples
63
+
64
+ ### pipeline
65
+
66
+ ```tsx
67
+ import { CartesianChart, ChartFrame, ChartLegend, chartTable, seriesColor } from "@twoux/ui";
68
+ import { APPLICATIONS, WEEKS } from "../chart-card-line/data";
69
+
70
+ export const meta = { title: "Applications, stacked by team", description: "Stacked from zero, so the top edge is the total and each band is one team. Use it when the total matters as much as the parts; for comparing teams, use lines.", order: 1 };
71
+
72
+ export default function Example() {
73
+ return (
74
+ <ChartFrame className="w-full" title="Applications per week" description="Stacked by team" table={chartTable("Week", WEEKS, APPLICATIONS)} legend={<ChartLegend items={APPLICATIONS.map((s, i) => ({ id: s.id, label: s.label, color: seriesColor(i) }))} />}>
75
+ <CartesianChart kind="area" stacked x={WEEKS} series={APPLICATIONS} aria-label="Applications per week, stacked by team" />
76
+ </ChartFrame>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ## Related
82
+
83
+ - Line chart card
84
+ - Bar chart
85
+ - Data visualisation colours
86
+ - Breakdown table
87
+ - Data visualisation
@@ -0,0 +1,100 @@
1
+ # Bar chart
2
+
3
+ Vertical bars from a zero baseline for comparing amounts per period or category, with the latest bar labelled and an incomplete period shown lighter.
4
+
5
+ Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - Comparing amounts across items or periods; simple bars are quickly processed and understood.
10
+ - Emphasising the total of each data point.
11
+
12
+ ## When not to use
13
+
14
+ - Showing a trend over time where shape matters more than totals. Use the TwoUX Line chart card.
15
+ - Two dimensions at once. Use the TwoUX Heatmap.
16
+ - Breaking down task outcomes as stacks; stacked bars are hard to read. Use separate simple bars.
17
+
18
+ ## Structure
19
+
20
+ 1. Title and subtitle
21
+ 2. Zero baseline
22
+ 3. Bars (4px rounded top, gap between)
23
+ 4. Latest-bar label
24
+ 5. Incomplete period bar (lighter, labelled)
25
+ 6. Hover tooltip
26
+
27
+ ## Guidelines
28
+
29
+ - Bars always start at zero.
30
+ - Use horizontal bars when category names are long.
31
+ - Label only the latest bar; hover shows the rest.
32
+ - The current, incomplete period is lighter and says so in a label.
33
+ - A single series uses colour 1 and no legend.
34
+ - Bar charts have the same four states as line charts: skeleton plot while loading, empty, no match with Clear filters, and error with Try again.
35
+ - Use horizontal bars when there are many categories or long names, such as applications by listing title; vertical bars for periods.
36
+ - With several series, group bars to compare the parts. Stack only when the total is the main reading and there are four parts or fewer, with a 2px surface-colour gap between segments.
37
+ - A single series uses one colour for every bar. Never colour bars by month or by category for variety.
38
+ - A goal or limit is a dashed threshold line with a text label, such as "Target 120".
39
+ - The pinned popover can break a bar down into up to seven parts, with a link on the name or the value, not both.
40
+
41
+ ## Content
42
+
43
+ - Title names the measure; subtitle gives the period and scope.
44
+ - Keep tick increments fixed and match time labels to the data's period.
45
+ - Label the incomplete period plainly, "This week so far".
46
+
47
+ ## Accessibility
48
+
49
+ - 1.1.1 and WAI complex images: provide a short alternative and a table with the values.
50
+ - 1.4.11 Non-text Contrast: bars reach 3:1 against the background.
51
+ - 1.4.1 Use of Color: the incomplete period is marked in text, not lightness alone.
52
+ - 2.1.1 Keyboard: arrow keys move between bars and show each value.
53
+ - Description: "Bar chart showing applications per week, last 12 weeks." It matches the visible title and period.
54
+ - Grouped and stacked bars name each part in the tooltip and table, so colour is not the only cue.
55
+
56
+ ## Keyboard
57
+
58
+ | Keys | Action |
59
+ |---|---|
60
+ | Tab | Focus the chart |
61
+ | ← → | Move to the previous or next bar |
62
+
63
+ ## Examples
64
+
65
+ ### hires
66
+
67
+ ```tsx
68
+ import { CartesianChart, ChartFrame, ChartLegend, SegmentedControl, Segment, chartTable, seriesColor } from "@twoux/ui";
69
+ import { useState } from "react";
70
+ import { HIRES, WEEKS } from "../chart-card-line/data";
71
+
72
+ export const meta = { title: "Hires per week", description: "Grouped bars compare teams week by week; stacked shows the total. Bars start at zero with 4px rounded ends and 2px gaps.", order: 1 };
73
+
74
+ export default function Example() {
75
+ const [mode, setMode] = useState("grouped");
76
+ return (
77
+ <ChartFrame
78
+ className="w-full"
79
+ title="Hires per week"
80
+ description="Offers accepted, by team"
81
+ table={chartTable("Week", WEEKS, HIRES)}
82
+ actions={
83
+ <SegmentedControl value={mode} onValueChange={setMode} size="sm" aria-label="Bar layout">
84
+ <Segment value="grouped">Grouped</Segment>
85
+ <Segment value="stacked">Stacked</Segment>
86
+ </SegmentedControl>
87
+ }
88
+ legend={<ChartLegend items={HIRES.map((s, i) => ({ id: s.id, label: s.label, color: seriesColor(i) }))} />}
89
+ >
90
+ <CartesianChart kind="bar" stacked={mode === "stacked"} x={WEEKS} series={HIRES} aria-label="Hires per week by team" />
91
+ </ChartFrame>
92
+ );
93
+ }
94
+ ```
95
+
96
+ ## Related
97
+
98
+ - Line chart card
99
+ - Breakdown table
100
+ - Data visualisation colours
@@ -0,0 +1,88 @@
1
+ # Box plot
2
+
3
+ Distributions of several groups on one axis: a box for the middle half, a line for the median, whiskers to 1.5 times the IQR and dots for outliers.
4
+
5
+ Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - Comparing how spread out several groups are, such as response times by team.
10
+ - Finding outliers worth opening, such as the slowest tickets.
11
+
12
+ ## When not to use
13
+
14
+ - For one group's shape. Use the TwoUX Histogram.
15
+ - For audiences new to statistics; show median and range in a table or a range plot instead.
16
+
17
+ ## Structure
18
+
19
+ 1. Title and subtitle
20
+ 2. Group labels
21
+ 3. Shared value axis with title and unit
22
+ 4. Box from Q1 to Q3
23
+ 5. Median line
24
+ 6. Whiskers with caps
25
+ 7. Outlier dots
26
+ 8. Legend explaining the parts
27
+ 9. Tooltip: median, middle half, whiskers, outliers
28
+
29
+ ## Guidelines
30
+
31
+ - Put every group on one shared axis.
32
+ - Whiskers reach the furthest value within 1.5 times the IQR; values beyond are outlier dots.
33
+ - Always show the legend explaining box, median, whiskers and outliers.
34
+ - Order groups by meaning or by median, not alphabetically by default.
35
+ - Use ChartFrame for the four states: loading keeps the frame, an error says what failed with Try again, an empty period says so, and no match offers Clear filters.
36
+
37
+ ## Content
38
+
39
+ - Say Middle half (Q1 to Q3) rather than only IQR.
40
+ - Axis title gives the measure and unit, such as Hours to first response.
41
+
42
+ ## Accessibility
43
+
44
+ - 1.1.1 and WAI complex images: the chart has a short generated summary as its accessible name and a Table view with every value.
45
+ - 2.1.1 Keyboard: the chart is one tab stop; arrow keys move between marks and show the same tooltip as hover.
46
+ - 1.4.13 Content on Hover or Focus: the tooltip never covers the mark it describes, stays while the pointer is over the chart and closes with Esc.
47
+ - Accessible description: "Box plot showing first response time by team, last 40 tickets."
48
+
49
+ ## Keyboard
50
+
51
+ | Keys | Action |
52
+ |---|---|
53
+ | Tab | Focus the chart |
54
+ | ← → ↑ ↓ | Move to the previous or next mark and show its values |
55
+ | Home End | Jump to the first or last mark |
56
+ | Esc | Hide the tooltip |
57
+
58
+ ## Examples
59
+
60
+ ### response-times
61
+
62
+ ```tsx
63
+ import { BoxPlot, boxTable, ChartFrame } from "@twoux/ui";
64
+
65
+ export const meta = { title: "First response time by team", description: "Four teams on one axis: the box is the middle half of tickets, the line the median, dots the slow outliers worth opening.", order: 1 };
66
+
67
+ const TEAMS = [
68
+ { id: "billing", label: "Billing", values: [4.7, 1.3, 2.9, 3.3, 4.0, 4.2, 3.4, 2.1, 3.4, 5.1, 2.6, 1.9, 2.4, 5.4, 2.1, 3.7, 2.2, 0.9, 2.4, 3.3, 1.7, 10.8, 3.2, 2.4, 2.7, 5.0, 2.8, 1.3, 4.3, 5.4, 1.6, 2.1, 6.8, 2.9, 2.9, 2.7, 3.0, 1.8, 1.6, 7.0] },
69
+ { id: "onboarding", label: "Onboarding", values: [9.5, 3.2, 3.3, 11.4, 7.0, 2.4, 4.1, 9.9, 3.3, 18.5, 2.0, 15.3, 5.2, 11.0, 9.5, 6.5, 9.4, 6.5, 8.4, 7.9, 4.3, 2.1, 5.7, 4.8, 4.1, 4.7, 6.5, 2.3, 3.5, 4.4, 5.6, 5.3, 31.2, 8.4, 11.4, 4.3, 6.2, 5.9, 2.1, 5.7] },
70
+ { id: "integrations", label: "Integrations", values: [27.7, 7.3, 6.3, 18.5, 10.2, 22.2, 8.0, 22.1, 6.1, 3.5, 8.3, 12.8, 6.9, 10.9, 12.7, 11.7, 12.8, 9.4, 6.0, 8.5, 15.8, 5.0, 5.4, 20.1, 7.7, 15.4, 7.4, 7.5, 13.6, 2.9, 12.4, 3.6, 8.6, 9.0, 4.1, 2.2, 20.4, 11.0, 13.6, 11.3] },
71
+ { id: "enterprise", label: "Enterprise", values: [0.9, 1.8, 2.3, 1.1, 2.6, 3.0, 1.8, 2.4, 3.9, 0.8, 2.1, 2.3, 1.2, 0.8, 2.0, 1.3, 2.6, 3.6, 2.2, 4.0, 0.9, 1.5, 2.8, 2.4, 1.4, 2.4, 1.8, 4.9, 3.3, 4.0, 2.5, 2.9, 4.1, 1.5, 2.4, 1.9, 1.4, 1.6, 3.1, 1.5] },
72
+ ];
73
+ const hours = (n: number) => `${Math.round(n * 10) / 10}h`;
74
+
75
+ export default function Example() {
76
+ return (
77
+ <ChartFrame className="w-full" title="First response time by team" description="Last 40 tickets per team" table={boxTable(TEAMS, hours, "Team")}>
78
+ <BoxPlot groups={TEAMS} format={hours} xTitle="Hours to first response" aria-label="First response time by team, last 40 tickets" />
79
+ </ChartFrame>
80
+ );
81
+ }
82
+ ```
83
+
84
+ ## Related
85
+
86
+ - Histogram
87
+ - Scatter chart
88
+ - Breakdown table
@@ -0,0 +1,89 @@
1
+ # Bullet chart
2
+
3
+ Actual against target for a few measures: a thin bar over grey qualitative bands with the target as a dark tick, in a fraction of a gauge's space.
4
+
5
+ Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - Showing progress against a target for several measures in one card, such as bookings this quarter.
10
+ - Where a gauge or dial was asked for: a bullet chart says the same in a line.
11
+
12
+ ## When not to use
13
+
14
+ - For a single number with no target. Use the TwoUX Stat compare.
15
+ - For usage against a hard limit. Use the TwoUX Usage meter.
16
+
17
+ ## Structure
18
+
19
+ 1. Title and subtitle
20
+ 2. Row: measure name, actual and target as text
21
+ 3. Qualitative bands in neutral grey steps, darkest for the worst
22
+ 4. Actual: thin bar in c1
23
+ 5. Target: dark tick
24
+ 6. Scale under each bar
25
+ 7. Legend: actual, target, bands
26
+
27
+ ## Guidelines
28
+
29
+ - Use a bullet chart, never a gauge or dial.
30
+ - Bands are neutral grey steps, darkest for the worst range; they never use status colours.
31
+ - The actual is a thin bar in the first series colour, the target a dark tick across it.
32
+ - Print actual and target as text on each row, so the bar is never the only way to read them.
33
+ - Each row has its own scale; never compare bar lengths between rows with different scales.
34
+ - Use ChartFrame for the four states: loading keeps the frame, an error says what failed with Try again, an empty period says so, and no match offers Clear filters.
35
+
36
+ ## Content
37
+
38
+ - Name the bands by meaning (Poor, Satisfactory, Good), worst first.
39
+ - Write actual of target, such as £412k of £450k.
40
+
41
+ ## Accessibility
42
+
43
+ - 1.4.1 Use of Color: values are printed; bands are named in the legend and in each row's name.
44
+ - 1.1.1 and WAI complex images: each row's accessible name gives the actual, target, percentage of target and band.
45
+ - 2.1.1 Keyboard: each row is focusable and shows the same tooltip as hover.
46
+ - 1.4.11 Non-text Contrast: the actual bar and target tick meet 3:1 against every band.
47
+
48
+ ## Keyboard
49
+
50
+ | Keys | Action |
51
+ |---|---|
52
+ | Tab | Move to the next measure |
53
+ | Esc | Hide the tooltip |
54
+
55
+ ## Examples
56
+
57
+ ### quarter-targets
58
+
59
+ ```tsx
60
+ import { BulletChart, ChartFrame } from "@twoux/ui";
61
+
62
+ export const meta = { title: "Quarter to date against target", description: "Each measure is a thin bar over grey bands, with the target as a dark tick. It replaces a row of gauges in a third of the space.", order: 1 };
63
+
64
+ const ROWS = [
65
+ { id: "new", label: "New business", value: 412_000, target: 450_000, bands: [300_000, 400_000, 500_000] },
66
+ { id: "expansion", label: "Expansion", value: 188_000, target: 160_000, bands: [100_000, 150_000, 220_000] },
67
+ { id: "renewals", label: "Renewals", value: 731_000, target: 820_000, bands: [700_000, 800_000, 900_000] },
68
+ ];
69
+ const gbp = (n: number) => (n === 0 ? "£0" : `£${Math.round(n / 1000)}k`);
70
+
71
+ export default function Example() {
72
+ return (
73
+ <ChartFrame
74
+ className="w-full"
75
+ title="Bookings against target"
76
+ description="Quarter to date, Q4 2026"
77
+ table={{ columns: ["Measure", "Actual", "Target", "Of target"], rows: ROWS.map((r) => [r.label, gbp(r.value), gbp(r.target), `${Math.round((r.value / r.target) * 100)}%`]) }}
78
+ >
79
+ <BulletChart rows={ROWS} format={gbp} aria-label="Bookings against target, quarter to date" />
80
+ </ChartFrame>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ## Related
86
+
87
+ - Stat card with comparison
88
+ - Waterfall chart
89
+ - Bar chart
@@ -0,0 +1,112 @@
1
+ # Candlestick chart
2
+
3
+ Open, high, low and close per period for a price. Up candles are hollow and down candles filled, in two categorical colours, or green and red on trading screens; volume is its own chart below.
4
+
5
+ Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - Showing how a traded price moved each day, including its range, for people who read markets.
10
+ - Spotting days with wide ranges or large volume.
11
+
12
+ ## When not to use
13
+
14
+ - For a general audience or one price over time. Use the TwoUX Price chart or Line chart card.
15
+ - For anything that is not open, high, low and close.
16
+
17
+ ## Structure
18
+
19
+ 1. Title and subtitle
20
+ 2. Price axis on the right, not from zero, with a note
21
+ 3. Candles: wick for high and low, body for open and close
22
+ 4. Optional last-price tag on the axis, with a dashed line
23
+ 5. Up: hollow c1; down: filled c2 (green and red with colors="market")
24
+ 6. Volume chart below with its own axis, sharing the crosshair
25
+ 7. Legend: up and down
26
+ 8. Tooltip: change, open, high, low, close, volume
27
+
28
+ ## Guidelines
29
+
30
+ - Up and down take two categorical colours and differ in fill too: up hollow, down filled.
31
+ - Use two categorical colours by default. On trading screens, where people expect it, use green for up and red for down (colors="market"); the hollow and filled bodies stay, so colour is never the only cue.
32
+ - Put volume in its own chart below with its own axis; never a second y axis.
33
+ - The price axis need not start at zero, and says so.
34
+ - One crosshair for both charts, so a day's price and volume read together.
35
+ - Use ChartFrame for the four states: loading keeps the frame, an error says what failed with Try again, an empty period says so, and no match offers Clear filters.
36
+ - On a live market, tag the last price on the axis (showLast) and move only the forming candle as prices arrive.
37
+
38
+ ## Content
39
+
40
+ - Dates as day and short month (5 Nov); the unit in the subtitle.
41
+ - Legend says what up means: Up (close above open).
42
+
43
+ ## Accessibility
44
+
45
+ - 1.4.1 Use of Color: direction is carried by hollow or filled bodies, the tooltip and the table.
46
+ - 1.1.1 and WAI complex images: the chart has a short generated summary as its accessible name and a Table view with every value.
47
+ - 2.1.1 Keyboard: the chart is one tab stop; arrow keys move between marks and show the same tooltip as hover.
48
+ - 1.4.13 Content on Hover or Focus: the tooltip never covers the mark it describes, stays while the pointer is over the chart and closes with Esc.
49
+ - Accessible description: "Candlestick chart showing Ashdown plc daily prices, last 30 trading days."
50
+
51
+ ## Keyboard
52
+
53
+ | Keys | Action |
54
+ |---|---|
55
+ | Tab | Focus the chart |
56
+ | ← → ↑ ↓ | Move to the previous or next mark and show its values |
57
+ | Home End | Jump to the first or last mark |
58
+ | Esc | Hide the tooltip |
59
+
60
+ ## Examples
61
+
62
+ ### daily
63
+
64
+ ```tsx
65
+ import { type Candle, CandlestickChart, candleTable, ChartFrame } from "@twoux/ui";
66
+
67
+ export const meta = { title: "Daily prices with volume", description: "Ninety trading days. Up days are hollow, down days filled, and volume sits in its own panel under the prices with the same crosshair.", order: 1 };
68
+
69
+ // 90 trading days from a seeded random walk, so the example looks like a real price series and stays the same on every load.
70
+ function tradingDays(count: number, endDate: Date) {
71
+ const days: Date[] = [];
72
+ const d = new Date(endDate);
73
+ while (days.length < count) {
74
+ if (d.getDay() !== 0 && d.getDay() !== 6) days.unshift(new Date(d));
75
+ d.setDate(d.getDate() - 1);
76
+ }
77
+ return days;
78
+ }
79
+ function makeCandles(): Candle[] {
80
+ let seed = 7;
81
+ const rand = () => ((seed = (seed * 16807) % 2147483647) / 2147483647);
82
+ let close = 742;
83
+ return tradingDays(90, new Date(2026, 10, 5)).map((day) => {
84
+ const open = close + (rand() - 0.5) * 4;
85
+ const pull = (742 - open) * 0.06; // drifts back towards a middle price, like a stock trading in a range
86
+ close = open + pull + (rand() - 0.5) * 22 + (rand() > 0.93 ? (rand() - 0.5) * 34 : 0);
87
+ const high = Math.max(open, close) + rand() * 9;
88
+ const low = Math.min(open, close) - rand() * 9;
89
+ const big = Math.abs(close - open) > 12;
90
+ const v = Math.round((1.4 + rand() * 1.4 + (big ? 1.2 : 0)) * 1_000_000);
91
+ const r = (n: number) => Math.round(n * 10) / 10;
92
+ return { t: day.toLocaleDateString("en-GB", { day: "numeric", month: "short" }), o: r(open), h: r(high), l: r(low), c: r(close), v };
93
+ });
94
+ }
95
+ const DAYS: Candle[] = makeCandles();
96
+ const pence = (n: number) => `${n.toLocaleString("en-GB", { minimumFractionDigits: 1, maximumFractionDigits: 1 })}p`;
97
+
98
+ export default function Example() {
99
+ return (
100
+ <ChartFrame className="w-full" title="Ashdown plc, daily" description="Last 90 trading days, in pence" table={candleTable(DAYS, pence)} height={320}>
101
+ <CandlestickChart candles={DAYS} format={pence} aria-label="Ashdown plc daily prices, last 90 trading days" />
102
+ </ChartFrame>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ## Related
108
+
109
+ - Price chart
110
+ - Line chart card
111
+ - Bar chart
112
+ - Order book
@@ -0,0 +1,120 @@
1
+ # Line chart card
2
+
3
+ A card that shows how one or more series change over time, with a title, period controls, a Chart or Table switch, direct labels, a shared crosshair tooltip and a source footer.
4
+
5
+ Kind: component · Group: Data visualisation · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - Showing how a value changes over time.
10
+ - Comparing trends across a few series, such as applies per day by team.
11
+
12
+ ## When not to use
13
+
14
+ - Comparing amounts between categories. Use the TwoUX Bar chart.
15
+ - Two measures with different units. Use two charts side by side, never two y-axes.
16
+ - One number that matters. Use the TwoUX Stat card with comparison.
17
+ - More than 8 series. Fold extras into "Other" or filter.
18
+
19
+ ## Structure
20
+
21
+ 1. Title (what is measured)
22
+ 2. Subtitle (period)
23
+ 3. Controls row: time range (7d, 30d, 90d), Chart or Table
24
+ 4. Plot: 2px lines, faint grid, one y-axis
25
+ 5. Direct labels at line ends
26
+ 6. Legend (2 or more series)
27
+ 7. Crosshair tooltip
28
+ 8. Footer: source, updated time, Export CSV, Add to dashboard
29
+
30
+ ## Guidelines
31
+
32
+ - Title says what is measured; the subtitle gives the period.
33
+ - Label line ends directly, and add a legend only for two or more series.
34
+ - One tooltip lists every series at the hovered day, sorted by value, with both axis values.
35
+ - Keep the grid faint and lines 2px; remove anything that does not carry data.
36
+ - A line chart may start above zero, unlike a bar chart.
37
+ - Every chart can switch to a table, which is also its accessible version.
38
+ - Every line chart has four states in the plot area, never blank: Loading: skeleton of the plot (axes and faint grid) with "Loading applications" as the region label. Empty: "No applications in this period" with one next step. No match: "No data matches these filters" with Clear filters. Error: "Couldn't load applications. Your filters are kept." with Try again.
39
+ - Missing days show as a break in the line, not a drop to zero.
40
+ - Legend items are toggles that show or hide a series; hidden series keep their colour slot. With three or more series, add a Series filter (all selected by default); with one, no filter and no legend.
41
+ - Selecting a point or pressing Enter pins the tooltip as a popover. A pinned popover can hold a breakdown of up to seven items and up to two footer buttons; see Chart drill-down.
42
+ - A target or limit is a threshold: a dashed line at a fixed value with a text label ("Target 40 a day"), not an extra series.
43
+ - Skip x-axis date labels at a regular interval rather than crowding or rotating them. The y-axis never shows time units.
44
+ - A forecast after today continues in the same colour but dashed, with a vertical "Today" marker, so the split is not shown by colour alone. Keep grey dashed for the previous period only.
45
+
46
+ ## Content
47
+
48
+ - Use nouns in titles and keep axis titles to one to three words with units.
49
+ - Labels, values and legends use text colours; the series colour sits beside them as a swatch.
50
+ - The footer names the data source and when it updated.
51
+
52
+ ## Accessibility
53
+
54
+ - 1.1.1 and WAI complex images: a short alternative names the chart type, data and period; the table view is the long description.
55
+ - 1.4.1 Use of Color: direct labels identify series, so colour is not the only cue.
56
+ - 1.4.11 Non-text Contrast: lines needed to read the data reach 3:1 against the background.
57
+ - 2.1.1 Keyboard: the plot is focusable and arrow keys move the crosshair day by day.
58
+ - The accessible name matches the visible title; the description reads "Line chart showing applications per day by team, last 30 days."
59
+ - Legend toggles are buttons with aria-pressed, and hiding a series is announced politely.
60
+ - Enter pins the tooltip, Tab moves into its content, Esc closes it and returns focus to the same day.
61
+
62
+ ## Keyboard
63
+
64
+ | Keys | Action |
65
+ |---|---|
66
+ | Tab | Focus the plot |
67
+ | ← → | Move the crosshair to the previous or next day |
68
+ | Esc | Hide the tooltip |
69
+
70
+ ## Examples
71
+
72
+ ### applications
73
+
74
+ ```tsx
75
+ import { CartesianChart, ChartFrame, ChartLegend, type ChartStatus, SegmentedControl, Segment, chartTable, seriesColor } from "@twoux/ui";
76
+ import { useState } from "react";
77
+ import { APPLICATIONS, WEEKS } from "./data";
78
+
79
+ export const meta = { title: "Applications by team", description: "Hover, or focus the chart and use the arrow keys, to read every value. Click a legend key to hide a series. The table button shows the same numbers as a table.", order: 1 };
80
+
81
+ export default function Example() {
82
+ const [hidden, setHidden] = useState<string[]>([]);
83
+ const [state, setState] = useState<ChartStatus>("ready");
84
+ const [period, setPeriod] = useState("8w");
85
+ const n = period === "4w" ? 4 : 8;
86
+ const x = WEEKS.slice(-n);
87
+ const series = APPLICATIONS.map((s) => ({ ...s, data: s.data.slice(-n) }));
88
+ return (
89
+ <div className="grid w-full gap-3">
90
+ <ChartFrame
91
+ title="Applications per week"
92
+ description={`All open listings, last ${n} weeks`}
93
+ status={state}
94
+ onRetry={() => setState("ready")}
95
+ table={chartTable("Week", x, series)}
96
+ actions={
97
+ <SegmentedControl value={period} onValueChange={setPeriod} size="sm" aria-label="Period">
98
+ <Segment value="4w">4 weeks</Segment>
99
+ <Segment value="8w">8 weeks</Segment>
100
+ </SegmentedControl>
101
+ }
102
+ legend={<ChartLegend items={APPLICATIONS.map((s, i) => ({ id: s.id, label: s.label, color: seriesColor(i), shape: "line" }))} hidden={hidden} onToggle={(id) => setHidden((h) => (h.includes(id) ? h.filter((x) => x !== id) : [...h, id]))} />}
103
+ >
104
+ <CartesianChart kind="line" x={x} series={series} hidden={hidden} aria-label="Applications per week by team" />
105
+ </ChartFrame>
106
+ <SegmentedControl value={state} onValueChange={(v) => setState(v as ChartStatus)} size="sm" aria-label="Preview state" className="justify-self-start">
107
+ {(["ready", "loading", "empty", "error"] as const).map((s) => <Segment key={s} value={s}>{s[0].toUpperCase() + s.slice(1)}</Segment>)}
108
+ </SegmentedControl>
109
+ </div>
110
+ );
111
+ }
112
+ ```
113
+
114
+ ## Related
115
+
116
+ - Bar chart
117
+ - Stat card with comparison
118
+ - Breakdown table
119
+ - Data visualisation colours
120
+ - Export menu