@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,149 @@
1
+ # Saved views
2
+
3
+ Named, reusable combinations of filters, sort order and column choices, also kept in the URL so a link reproduces the view.
4
+
5
+ Kind: component · Group: Data · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - People apply the same filter configuration on every visit; saving it reduces memory load and repeat work.
10
+ - A team needs shared starting points, such as "Expiring this week".
11
+ - Expert users want to customise the interface to their own needs.
12
+
13
+ ## When not to use
14
+
15
+ - For one-off filtering. Use the TwoUX Filter bar; a URL already preserves the state.
16
+ - For display choices only. Use the TwoUX Columns menu, which is saved per person per table.
17
+
18
+ ## Structure
19
+
20
+ 1. View switcher (tabs or select) with live counts
21
+ 2. Save view (rectangle button, shown when filters are on)
22
+ 3. Unsaved changes indicator
23
+ 4. Manage: rename, update, set default, delete
24
+
25
+ ## Guidelines
26
+
27
+ - A view stores filters, sort order and column choices together.
28
+ - Keep filter state in the URL. On arrival, apply the default view unless the URL carries filter parameters.
29
+ - Show unsaved changes and let people return to the saved state in one step.
30
+ - Confirm before deleting or overwriting a view, naming the view in the dialog.
31
+ - Show only the metadata people need to tell views apart, such as name and live count.
32
+
33
+ ## Content
34
+
35
+ - Name views after what they show, "Expiring this week", not "View 3".
36
+ - Use fixed action labels for the lifecycle: "Save view", "Update view", "Delete view", "Set as default".
37
+ - Confirmation text says what will change, for example that deleting removes the saved filters and columns.
38
+ - Most people will not customise, so ship sensible default views.
39
+
40
+ ## Accessibility
41
+
42
+ - 4.1.2 Name, Role, Value: the view switcher exposes the current view as selected.
43
+ - APG dialog pattern: the delete confirmation traps focus, closes on Escape and returns focus to the trigger.
44
+ - 4.1.3 Status Messages: "View saved" is announced without moving focus.
45
+
46
+ ## Keyboard
47
+
48
+ | Keys | Action |
49
+ |---|---|
50
+ | Tab, ← → | Move between views |
51
+ | Enter | Apply the view |
52
+ | Esc | Close the save or manage dialog |
53
+
54
+ ## Examples
55
+
56
+ ### views
57
+
58
+ ```tsx
59
+ import { FilterBar, FilterPill, SavedViews, type SavedView, toast } from "@twoux/ui";
60
+ import { useState } from "react";
61
+
62
+ export const meta = { title: "Views with live counts", description: "A view stores filters, sort and columns together. Change a filter and the view shows Unsaved changes, with Reset and Update one click away. The ⋮ menu renames, sets the default or deletes the current view; deleting offers Undo.", order: 1 };
63
+
64
+ const OPTIONS = ["Open", "Closing soon", "Draft", "Closed"].map((v) => ({ value: v, label: v }));
65
+
66
+ export default function Example() {
67
+ const [views, setViews] = useState<SavedView[]>([
68
+ { id: "all", name: "All listings", count: 24 },
69
+ { id: "open", name: "Open", count: 14 },
70
+ { id: "closing", name: "Closing soon", count: 4 },
71
+ ]);
72
+ const [saved, setSaved] = useState<Record<string, string[]>>({ all: [], open: ["Open", "Closing soon"], closing: ["Closing soon"] });
73
+ const [defaultId, setDefaultId] = useState("all");
74
+ const [view, setView] = useState("open");
75
+ const [status, setStatus] = useState(saved.open);
76
+ const current = saved[view] ?? [];
77
+ const dirty = status.length !== current.length || status.some((s) => !current.includes(s));
78
+ const open = (id: string) => { setView(id); setStatus(saved[id] ?? []); };
79
+ const nameOf = (id: string) => views.find((v) => v.id === id)?.name ?? "";
80
+
81
+ return (
82
+ <div className="grid w-full max-w-2xl gap-3">
83
+ <SavedViews
84
+ views={views}
85
+ value={view}
86
+ onValueChange={open}
87
+ dirty={dirty}
88
+ defaultId={defaultId}
89
+ onReset={() => setStatus(current)}
90
+ onSave={() => {
91
+ const before = current;
92
+ setSaved((s) => ({ ...s, [view]: status }));
93
+ toast.success(`Updated “${nameOf(view)}”`, { action: { label: "Undo", onClick: () => setSaved((s) => ({ ...s, [view]: before })) } });
94
+ }}
95
+ onSaveAs={(name) => {
96
+ const id = `${name.toLowerCase().replace(/\W+/g, "-")}-${views.length}`;
97
+ setSaved((s) => ({ ...s, [id]: status }));
98
+ setViews((v) => [...v, { id, name, count: 6 }]);
99
+ setView(id);
100
+ toast.success(`Saved “${name}”`);
101
+ }}
102
+ onRename={(id, name) => {
103
+ const before = nameOf(id);
104
+ const rename = (to: string) => setViews((v) => v.map((x) => (x.id === id ? { ...x, name: to } : x)));
105
+ rename(name);
106
+ toast.success(`Renamed “${before}” to “${name}”`, { action: { label: "Undo", onClick: () => rename(before) } });
107
+ }}
108
+ onSetDefault={(id) => {
109
+ const before = defaultId;
110
+ setDefaultId(id);
111
+ toast.success(`“${nameOf(id)}” opens by default`, { action: { label: "Undo", onClick: () => setDefaultId(before) } });
112
+ }}
113
+ onDelete={(id) => {
114
+ const index = views.findIndex((v) => v.id === id);
115
+ const removed = views[index];
116
+ const next = views.filter((v) => v.id !== id);
117
+ const wasDefault = id === defaultId;
118
+ setViews(next);
119
+ // Deleting the default hands it to the first view left; the open view moves to the default.
120
+ const fallback = wasDefault ? next[0].id : defaultId;
121
+ if (wasDefault) setDefaultId(fallback);
122
+ open(fallback);
123
+ toast.success(`Deleted “${removed.name}”`, {
124
+ action: {
125
+ label: "Undo",
126
+ onClick: () => {
127
+ setViews((v) => [...v.slice(0, index), removed, ...v.slice(index)]);
128
+ if (wasDefault) setDefaultId(id);
129
+ open(id);
130
+ },
131
+ },
132
+ });
133
+ }}
134
+ />
135
+ <FilterBar>
136
+ <FilterPill field="Status" options={OPTIONS} selected={status} onChange={setStatus} />
137
+ </FilterBar>
138
+ </div>
139
+ );
140
+ }
141
+ ```
142
+
143
+ ## Related
144
+
145
+ - Filter pills and filter bar
146
+ - Filter panel
147
+ - Columns menu
148
+ - List table
149
+ - Data grid
@@ -0,0 +1,68 @@
1
+ # Saved views
2
+
3
+ Store a filtered, sorted, column-chosen list under a name so people and teams return to it in one click.
4
+
5
+ Kind: pattern · Group: Patterns: general
6
+
7
+ **Problem:** People rebuild the same filters every morning, and teams share screenshots of lists instead of links. Without one way to save, each screen invents its own favourites.
8
+
9
+ **Solution:** When filters are on, Save view stores filters, sort and columns together with a name. Views show live counts, mark unsaved changes, and reproduce from the URL.
10
+
11
+ **Built from:** Saved views, Filter pills and filter bar, Columns menu, List table, Data grid, Tabs (underline)
12
+
13
+ ## How it works
14
+
15
+ 1. Save view appears only once at least one filter, sort or column change differs from the default.
16
+ 2. Save view asks for a name (required) and an optional description, validating duplicate names inline.
17
+ 3. The view appears as a tab or sidebar item with its live row count, for example "Live 14".
18
+ 4. If the person changes a saved view, its name shows an unsaved mark and the menu offers Update view, Save as new and Revert.
19
+ 5. The view's state also lives in the URL, so a copied link opens the same view for anyone with access.
20
+
21
+ ## When to use
22
+
23
+ - For lists people check repeatedly with the same filters, such as "My live listings" or "Closing this week".
24
+ - To give a team a shared starting point that everyone sees the same way.
25
+
26
+ ## When not to use
27
+
28
+ - For a one-off filter. The URL already makes it shareable; use Filtering and search.
29
+ - For per-person display choices such as row density or wrapping. Those belong in the Columns menu and are remembered automatically.
30
+
31
+ ## Guidelines
32
+
33
+ - A view stores filters, sort order, visible columns and grouping. Opening it restores all four.
34
+ - A default view loads when the page opens, unless the URL already carries filters.
35
+ - Changing a saved view never overwrites it silently. Updating needs an explicit Update view.
36
+ - Deleting a view uses a simple confirmation, because a team view can be in other people's sidebars.
37
+ - Keep the number of pinned views short; long rows of tabs slow the choice down.
38
+ - Group the views list into Team views and My views, and add a search box once there are more than about ten.
39
+ - People can set any view as their own default with Set as default. It is unavailable while the current view has unsaved changes, and the reason is shown.
40
+ - Update view shows the filters before and after in the menu, then saves with Undo in the toast.
41
+ - People can drop unsaved changes in any of three ways: Revert, choosing the view again, or Clear filters.
42
+
43
+ ## Content
44
+
45
+ - Name views by what they contain, not who made them: "Closing this week", not "Rahul's view".
46
+ - Menu actions: "Save view", "Save as new view", "Update view", "Revert changes", "Delete view".
47
+
48
+ ## Accessibility
49
+
50
+ - The unsaved mark is text ("Edited") as well as a dot, so it does not rely on colour.
51
+ - Confirm a save with a status message announced politely, not a moved focus.
52
+
53
+ ## Do
54
+
55
+ - Do show the live count on each view.
56
+ - Do mark a modified view and offer Revert.
57
+
58
+ ## Don't
59
+
60
+ - Don't save a view automatically when filters change.
61
+ - Don't store a view only in local storage; it must reproduce from its link.
62
+
63
+ ## Related
64
+
65
+ - Filtering and search
66
+ - Keyboard-first navigation
67
+ - Saved views
68
+ - Columns menu
@@ -0,0 +1,258 @@
1
+ # Scheduler
2
+
3
+ Interviews, shifts or bookings on a day, week or month grid, with people or rooms as resources. Click a free time to add, click an event to open it, and drag, use the keyboard or the event menu to move it. Below 768px it becomes an agenda list.
4
+
5
+ Kind: component · Group: Data · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - Booking and arranging time-bound records where free time matters, such as interviews across several interviewers, shifts or room bookings.
10
+ - Seeing who is busy when, side by side, before choosing a time.
11
+
12
+ ## When not to use
13
+
14
+ - For a list of upcoming items with no need to see free time; use a Grouped list or List table with Timestamps.
15
+ - For a record's history of changes; use Timeline.
16
+ - For picking one date in a form; use a date field.
17
+ - For editing recurring series; the Scheduler shows events the app has already expanded and has no recurrence editor.
18
+
19
+ ## Structure
20
+
21
+ 1. Toolbar: Today, Previous, Next, period heading, time zone, Day / Week / Month
22
+ 2. Resource legend: stripe and name
23
+ 3. Time gutter, 24-hour, with a labelled now line
24
+ 4. Column headers: days, or resources in day view
25
+ 5. All-day row
26
+ 6. 30-minute slots
27
+ 7. Event: resource stripe, title, detail, time, resource, status badge, ⋮ menu
28
+ 8. Month cells with up to three events and +N more
29
+ 9. Agenda list grouped by day, below 768px
30
+
31
+ ## Guidelines
32
+
33
+ - Switch Day, Week and Month with the segmented control; they are views of the same data.
34
+ - Open an event in the side panel with onSelectEvent, so people keep their place in the schedule.
35
+ - Create from a free time with onCreate and a dialog of up to three fields; the time comes from the slot.
36
+ - Apply every move at once with a toast offering Undo. Confirm first only if the move contacts someone outside the workspace, such as re-sending a candidate invite.
37
+ - Every drag has a keyboard path (Space, arrows, Enter, Esc) and a menu path (the event's ⋮: earlier, later, other day, longer, shorter, another resource).
38
+ - Resource colour is a left stripe from the series colours; text stays ink and the resource is always named.
39
+ - Keep day and week views to working hours (dayStart, dayEnd) and use workWeek when weekends are never booked.
40
+ - Below 768px the grid becomes an agenda grouped by day, so the schedule reflows to 320px without sideways scrolling.
41
+ - Loading shows skeleton rows inside the frame; an error says what failed with Try again; an empty period says so and stays clickable.
42
+
43
+ ## Content
44
+
45
+ - Times are 24-hour with an en dash: 09:30–10:15. Weeks start on Monday. Day headings read Thursday 12 November.
46
+ - Write the time zone once in the toolbar: Times in GMT. Details in the side panel use Timestamp in absolute format.
47
+ - Name the noun in the empty message: No interviews this week. Choose a free time to add one.
48
+ - Status is a word in a Badge (Confirmed, Tentative), never a colour alone.
49
+
50
+ ## Accessibility
51
+
52
+ - 2.1.1 Keyboard: the grid is one tab stop with roving focus; arrows walk free times and events in time order, and Enter opens or adds.
53
+ - 2.5.7 Dragging Movements: moving and resizing also work with Space and arrows, and from the event menu with single clicks.
54
+ - 4.1.3 Status Messages: pick-up, each step, drop and cancel are announced in a polite live region.
55
+ - Month view follows the APG calendar grid: arrows by day and week, Enter lists the day's events.
56
+ - 1.4.1 Use of Color: resources and status are written in words; colour repeats them.
57
+ - 1.4.10 Reflow: the agenda replaces the grid below 768px.
58
+ - 2.1.4 Character Key Shortcuts: T, J, K, D, W and M stop when single-key shortcuts are turned off; keys inside the focused grid are not affected.
59
+
60
+ ## Keyboard
61
+
62
+ | Keys | Action |
63
+ |---|---|
64
+ | T | Go to today |
65
+ | J K | Next or previous day, week or month |
66
+ | D W M | Day, week or month view |
67
+ | ↑ ↓ | Previous or next free time or event in the column |
68
+ | ← → | Same time in the previous or next column |
69
+ | Home End | First or last time in the column |
70
+ | Page up Page down | Previous or next period |
71
+ | Enter | Open the event, or add one at the free time |
72
+ | Space | Pick up the event to move it |
73
+ | ↑ ↓ ← → (moving) | Move by one slot, or to the next day or interviewer |
74
+ | ⇧↑ ⇧↓ (moving) | Make it shorter or longer |
75
+ | Enter Space (moving) | Drop it here |
76
+ | Esc | Cancel the move and put it back |
77
+ | ⇧F10 | Open the event menu |
78
+
79
+ ## Examples
80
+
81
+ ### interviews
82
+
83
+ ```tsx
84
+ import {
85
+ Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, RadioField, RadioGroup,
86
+ Scheduler, SidePanel, TextField, Timestamp, formatTimeRange, toast, type SchedulerEvent, type SchedulerSlot, type SchedulerView,
87
+ } from "@twoux/ui";
88
+ import { useState } from "react";
89
+ import { INTERVIEWERS, INTERVIEWS, NOW, STATUS_VARIANT } from "./data";
90
+
91
+ export const meta = {
92
+ title: "Interview week",
93
+ description: "Three interviewers, one week. Click an interview to open it beside the schedule; click a free time to add one. Drag to move or the bottom edge to change the length; Space, arrows and Enter do the same from the keyboard, and the ⋮ menu offers the same moves. Every move has Undo.",
94
+ order: 1,
95
+ };
96
+
97
+ const TYPES = ["Phone screen", "Technical interview", "Final interview"];
98
+ const name = (id?: string) => INTERVIEWERS.find((r) => r.id === id)?.label;
99
+
100
+ export default function Example() {
101
+ const [events, setEvents] = useState(INTERVIEWS);
102
+ const [view, setView] = useState<SchedulerView>("week");
103
+ const [date, setDate] = useState(NOW);
104
+ const [openId, setOpenId] = useState<string | null>(null);
105
+ const [draft, setDraft] = useState<SchedulerSlot | null>(null);
106
+ const [candidate, setCandidate] = useState("");
107
+ const [type, setType] = useState(TYPES[0]);
108
+ const [interviewer, setInterviewer] = useState("tom");
109
+ const [error, setError] = useState<string | undefined>();
110
+ const open = events.find((e) => e.id === openId) ?? null;
111
+
112
+ const move = (ev: SchedulerEvent, to: SchedulerSlot) => {
113
+ const before = events;
114
+ setEvents((list) => list.map((e) => (e.id === ev.id ? { ...e, start: to.start.toISOString(), end: to.end.toISOString(), resourceId: to.resourceId } : e)));
115
+ toast.success(`${ev.title} moved to ${to.start.toLocaleDateString("en-GB", { weekday: "long" })} ${formatTimeRange(to.start, to.end)}`, { action: { label: "Undo", onClick: () => setEvents(before) } });
116
+ };
117
+ const startCreate = (slot: SchedulerSlot) => {
118
+ setDraft(slot); setCandidate(""); setType(TYPES[0]); setInterviewer(slot.resourceId ?? "tom"); setError(undefined);
119
+ };
120
+ const create = (e: React.FormEvent) => {
121
+ e.preventDefault();
122
+ if (!draft) return;
123
+ if (!candidate.trim()) { setError("Enter the candidate's name."); return; }
124
+ const id = `INT-${300 + events.length}`;
125
+ setEvents((list) => [...list, { id, title: type, detail: candidate.trim(), start: draft.start.toISOString(), end: draft.end.toISOString(), resourceId: interviewer, status: "Tentative" }]);
126
+ setDraft(null);
127
+ toast.success(`${type} added for ${candidate.trim()}`, { action: { label: "Undo", onClick: () => setEvents((list) => list.filter((x) => x.id !== id)) } });
128
+ };
129
+
130
+ return (
131
+ <div className="flex w-full overflow-hidden rounded-sm">
132
+ <div className="min-w-0 flex-1">
133
+ <Scheduler
134
+ aria-label="Interviews"
135
+ noun="interviews"
136
+ resourceTitle="Interviewer"
137
+ events={events}
138
+ resources={INTERVIEWERS}
139
+ view={view}
140
+ onViewChange={setView}
141
+ date={date}
142
+ onDateChange={setDate}
143
+ now={NOW}
144
+ workWeek
145
+ height={460}
146
+ selectedId={openId ?? undefined}
147
+ statusVariant={STATUS_VARIANT}
148
+ onSelectEvent={(e) => setOpenId(e.id)}
149
+ onCreate={startCreate}
150
+ onMove={move}
151
+ />
152
+ </div>
153
+ {open && (
154
+ <SidePanel
155
+ open
156
+ onOpenChange={(o) => !o && setOpenId(null)}
157
+ mode="overlay"
158
+ label="Interview details"
159
+ title={open.detail ? `${open.title}, ${open.detail}` : open.title}
160
+ meta={<span className="flex items-center gap-2"><span className="font-mono">{open.id}</span>{open.status && <Badge variant={STATUS_VARIANT(open.status)}>{open.status}</Badge>}</span>}
161
+ footer={<Button size="sm" variant="outline">Edit interview</Button>}
162
+ >
163
+ <dl className="grid grid-cols-2 gap-x-3 gap-y-2 text-base">
164
+ <dt className="text-ink-2">Starts</dt><dd><Timestamp value={open.start} format="absolute" /></dd>
165
+ <dt className="text-ink-2">Ends</dt><dd><Timestamp value={open.end} format="absolute" /></dd>
166
+ <dt className="text-ink-2">Interviewer</dt><dd className="text-ink">{name(open.resourceId) ?? "Not assigned"}</dd>
167
+ <dt className="text-ink-2">Role</dt><dd className="text-ink">Senior Backend Engineer</dd>
168
+ </dl>
169
+ </SidePanel>
170
+ )}
171
+ <Dialog open={!!draft} onOpenChange={(o) => !o && setDraft(null)}>
172
+ <DialogContent>
173
+ <form onSubmit={create} className="grid gap-4" noValidate>
174
+ <DialogHeader>
175
+ <DialogTitle>Add interview</DialogTitle>
176
+ <DialogDescription>{draft && <Timestamp value={draft.start} format="absolute" />}{draft && `, until ${formatTimeRange(draft.start, draft.end).split("–")[1]}`}</DialogDescription>
177
+ </DialogHeader>
178
+ <TextField label="Candidate" required value={candidate} onChange={(e) => { setCandidate(e.target.value); setError(undefined); }} error={error} autoFocus />
179
+ <fieldset className="grid gap-2">
180
+ <legend className="mb-2 text-base font-medium text-ink">Interview type</legend>
181
+ <RadioGroup value={type} onValueChange={setType}>
182
+ {TYPES.map((t) => <RadioField key={t} id={`type-${t}`} value={t} label={t} />)}
183
+ </RadioGroup>
184
+ </fieldset>
185
+ <fieldset className="grid gap-2">
186
+ <legend className="mb-2 text-base font-medium text-ink">Interviewer</legend>
187
+ <RadioGroup value={interviewer} onValueChange={setInterviewer}>
188
+ {INTERVIEWERS.map((r) => <RadioField key={r.id} id={`who-${r.id}`} value={r.id} label={r.label} />)}
189
+ </RadioGroup>
190
+ </fieldset>
191
+ <DialogFooter>
192
+ <Button type="button" variant="outline" onClick={() => setDraft(null)}>Cancel</Button>
193
+ <Button type="submit">Add interview</Button>
194
+ </DialogFooter>
195
+ </form>
196
+ </DialogContent>
197
+ </Dialog>
198
+ </div>
199
+ );
200
+ }
201
+ ```
202
+
203
+ ### states
204
+
205
+ ```tsx
206
+ import { SegmentedControl, Segment, Scheduler, type SchedulerView } from "@twoux/ui";
207
+ import { useState } from "react";
208
+ import { INTERVIEWERS, INTERVIEWS, NOW, STATUS_VARIANT } from "./data";
209
+
210
+ export const meta = {
211
+ title: "Agenda, loading, empty and error",
212
+ description: "Below 768px the grid becomes this agenda, grouped by day, so the schedule reflows to 320px; it is also the list view for screen reader users who prefer one. Loading keeps the frame with skeleton rows, an error says what failed with Try again, and an empty week says so.",
213
+ order: 2,
214
+ };
215
+
216
+ type Demo = "agenda" | "loading" | "error" | "empty";
217
+
218
+ export default function Example() {
219
+ const [demo, setDemo] = useState<Demo>("agenda");
220
+ const [view, setView] = useState<SchedulerView>("week");
221
+ const [date, setDate] = useState(NOW);
222
+ return (
223
+ <div className="grid w-full max-w-md gap-3">
224
+ <SegmentedControl value={demo} onValueChange={(v) => setDemo(v as Demo)} size="sm" aria-label="State" className="justify-self-start">
225
+ <Segment value="agenda">Agenda</Segment><Segment value="loading">Loading</Segment><Segment value="error">Error</Segment><Segment value="empty">Empty</Segment>
226
+ </SegmentedControl>
227
+ <Scheduler
228
+ aria-label="Interviews"
229
+ noun="interviews"
230
+ resourceTitle="Interviewer"
231
+ events={demo === "empty" ? [] : INTERVIEWS}
232
+ resources={INTERVIEWERS}
233
+ view={view}
234
+ onViewChange={setView}
235
+ date={date}
236
+ onDateChange={setDate}
237
+ now={NOW}
238
+ workWeek
239
+ layout="agenda"
240
+ status={demo === "loading" ? "loading" : demo === "error" ? "error" : "ready"}
241
+ onRetry={() => setDemo("agenda")}
242
+ statusVariant={STATUS_VARIANT}
243
+ onSelectEvent={() => {}}
244
+ onMove={() => {}}
245
+ />
246
+ </div>
247
+ );
248
+ }
249
+ ```
250
+
251
+ ## Related
252
+
253
+ - Side panel (drawer)
254
+ - Segmented control
255
+ - Grouped list
256
+ - List table
257
+ - Dropdown menu
258
+ - Confirmation dialog
@@ -0,0 +1,72 @@
1
+ # Secondary panels: which right-hand panel
2
+
3
+ TwoUX has one right-hand panel slot. Record detail, the assistant, help and filters each have a clear job, only one is open at a time, and none is ever required to finish a task.
4
+
5
+ Kind: pattern · Group: Patterns: general
6
+
7
+ **Problem:** When every feature adds its own panel, people can't tell which icon opens what, panels pile up and crush the table, and tasks end up depending on something hidden behind a toggle.
8
+
9
+ **Solution:** Choose the panel by what the person is trying to do: look at a record (side panel), ask about the data (assistant panel), learn how something works (help panel), narrow a large search (filter panel). Opening one replaces the one already in the slot.
10
+
11
+ **Built from:** Side panel (drawer), Assistant side panel, Help panel, Filter panel, App shell and sidebar navigation
12
+
13
+ ## How it works
14
+
15
+ 1. Name the person's goal: review a record, get an answer about the data, learn the product, or narrow results.
16
+ 2. Pick the panel: side panel for a record, assistant panel for questions, help panel for guidance, filter panel for searches with many fields.
17
+ 3. Check the main task can be finished without the panel. If it can't, the content belongs in the page.
18
+ 4. Open the panel only from a person's action; opening it moves the previous panel out of the slot, and closing it brings that panel back.
19
+
20
+ ## When to use
21
+
22
+ - For supporting information or tools that relate to the page the person is on.
23
+ - When the person benefits from seeing the list and the extra content side by side.
24
+ - When the extra content should stay open while people move through records with J and K.
25
+
26
+ ## When not to use
27
+
28
+ - For a decision that must interrupt the task. Use a confirmation dialog.
29
+ - For a long, standalone task such as editing a whole listing. Use a full page.
30
+ - For content unrelated to the current page, such as account settings.
31
+
32
+ ## Guidelines
33
+
34
+ - At most three panel types on any page. If a fourth is needed, one of them is probably a page or a dialog.
35
+ - Triggers sit in a fixed order wherever they appear: record detail opens from the row, then Assistant (⌘J), then Help.
36
+ - Panels are secondary: they can speed a task up, but every task can be completed without opening one.
37
+ - Help panel content is read-only; side panel, assistant and filter panel content can be interactive.
38
+ - Below 860px the open panel takes the full width of the canvas and has a clear close button.
39
+
40
+ ## Content
41
+
42
+ - Trigger buttons have text or a tooltip with the panel name and shortcut: "Assistant ⌘J", "Help".
43
+ - The panel heading names what is inside: the record title, "Assistant", or the help topic.
44
+ - Close buttons are named for the panel: "Close help", "Close assistant".
45
+
46
+ ## Accessibility
47
+
48
+ - Every panel is a labelled complementary region, not a modal, unless it overlays content (then it follows the dialog pattern).
49
+ - Opening a panel from a control moves focus into it; Esc closes it and focus returns to the control.
50
+ - The panel never hides the focused element on the page (2.4.11).
51
+
52
+ ## Do
53
+
54
+ - Do choose the panel from the person's goal.
55
+ - Do keep panel triggers in the same order on every page.
56
+ - Do restore the previous panel when the newer one closes.
57
+
58
+ ## Don't
59
+
60
+ - Don't open two right-hand panels at once.
61
+ - Don't open a panel on page load.
62
+ - Don't hide a required step inside a panel.
63
+
64
+ ## Related
65
+
66
+ - Record detail: side panel, overlay or full page
67
+ - Ask about what's on screen
68
+ - Contextual help
69
+ - Side panel (drawer)
70
+ - Assistant side panel
71
+ - Help panel
72
+ - Filter panel
@@ -0,0 +1,88 @@
1
+ # Segmented control
2
+
3
+ Switches between two or three views or settings of the same content, such as All, Active and Backlog.
4
+
5
+ Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - To switch how the same data is shown or filtered: All / Active / Backlog, Chart / Table.
10
+ - For a view setting that always has a value: Rows per page 25 / 50 / 100, Daily / Weekly.
11
+ - When there are two or three short options and room to show them.
12
+
13
+ ## When not to use
14
+
15
+ - For separate sections of a record. Use Tabs.
16
+ - For more than three options. Use a Dropdown menu or select.
17
+ - For an on/off setting. Use Switch.
18
+ - For choices inside a form that is saved later. Use Radio.
19
+
20
+ ## Structure
21
+
22
+ 1. Group label (often visually hidden)
23
+ 2. Track
24
+ 3. Segment buttons
25
+ 4. Selected segment (white, raised)
26
+
27
+ ## Guidelines
28
+
29
+ - Track is grey with a 2px inset; segments are 24px high, 12px medium text; selected segment is white with a hairline shadow.
30
+ - The change applies immediately, like a switch.
31
+ - Equal weight for all segments; one is always selected.
32
+ - Deactivate at most one segment, and give the reason in a tooltip.
33
+
34
+ ## Content
35
+
36
+ - One word per segment where possible.
37
+ - Sentence case, parallel words: All, Active, Backlog.
38
+ - Icon-only segments need alternative text naming the view.
39
+
40
+ ## Accessibility
41
+
42
+ - TwoUX renders it as a group of toggle buttons: role="group" with aria-label, each button with aria-pressed.
43
+ - Every segment label stays the same when pressed; state comes from aria-pressed.
44
+ - Segments are 24px high, exactly the WCAG 2.5.8 minimum; do not make them smaller.
45
+ - The selected segment differs by fill and shadow, with 3:1 against the track.
46
+ - Visible focus ring on each segment.
47
+
48
+ ## Keyboard
49
+
50
+ | Keys | Action |
51
+ |---|---|
52
+ | Tab | Move to each segment |
53
+ | Enter / Space | Select the focused segment |
54
+
55
+ ## Examples
56
+
57
+ ### views
58
+
59
+ ```tsx
60
+ import { Segment, SegmentedControl } from "@twoux/ui";
61
+ import { List, Rows3 } from "lucide-react";
62
+ import { useState } from "react";
63
+ export const meta = {"title": "Views of the same data", "description": "Two or three options, one always selected. Switches how the same list is shown; it never navigates.", "order": 1};
64
+
65
+ export default function Example() {
66
+ const [v, setV] = useState("active");
67
+ const [d, setD] = useState("list");
68
+ return (
69
+ <div className="flex flex-wrap items-center gap-4">
70
+ <SegmentedControl aria-label="Listings to show" value={v} onValueChange={setV}>
71
+ <Segment value="all">All</Segment>
72
+ <Segment value="active">Active</Segment>
73
+ <Segment value="closed">Closed</Segment>
74
+ </SegmentedControl>
75
+ <SegmentedControl aria-label="Layout" size="sm" value={d} onValueChange={setD}>
76
+ <Segment value="list" aria-label="List"><List /></Segment>
77
+ <Segment value="board" aria-label="Board"><Rows3 /></Segment>
78
+ </SegmentedControl>
79
+ </div>
80
+ );
81
+ }
82
+ ```
83
+
84
+ ## Related
85
+
86
+ - Tabs (underline)
87
+ - Radio
88
+ - Switch