@twoux/ui 0.0.0-stage → 0.1.0

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/SHADCN-DIFF.md +228 -0
  5. package/agent/AGENTS.md +358 -0
  6. package/agent/docs-md/accessibility.md +49 -0
  7. package/agent/docs-md/action-catalogue.md +81 -0
  8. package/agent/docs-md/actions.md +74 -0
  9. package/agent/docs-md/agent-activity-log.md +70 -0
  10. package/agent/docs-md/agent-permissions.md +82 -0
  11. package/agent/docs-md/agent-run.md +90 -0
  12. package/agent/docs-md/agents-on-behalf.md +74 -0
  13. package/agent/docs-md/ai-column.md +85 -0
  14. package/agent/docs-md/ai-origin-label.md +70 -0
  15. package/agent/docs-md/announcing-features.md +73 -0
  16. package/agent/docs-md/answer-actions.md +81 -0
  17. package/agent/docs-md/app-shell.md +114 -0
  18. package/agent/docs-md/artifact-card.md +89 -0
  19. package/agent/docs-md/ask-on-screen.md +72 -0
  20. package/agent/docs-md/assistant-page.md +100 -0
  21. package/agent/docs-md/assistant-panel.md +142 -0
  22. package/agent/docs-md/avatar.md +82 -0
  23. package/agent/docs-md/banner.md +106 -0
  24. package/agent/docs-md/board.md +163 -0
  25. package/agent/docs-md/breadcrumb.md +89 -0
  26. package/agent/docs-md/breakdown-table.md +97 -0
  27. package/agent/docs-md/bulk-action-bar.md +112 -0
  28. package/agent/docs-md/button.md +293 -0
  29. package/agent/docs-md/card.md +115 -0
  30. package/agent/docs-md/change-list.md +99 -0
  31. package/agent/docs-md/change-request.md +166 -0
  32. package/agent/docs-md/chart-area.md +87 -0
  33. package/agent/docs-md/chart-bar.md +100 -0
  34. package/agent/docs-md/chart-box-plot.md +88 -0
  35. package/agent/docs-md/chart-bullet.md +89 -0
  36. package/agent/docs-md/chart-candlestick.md +112 -0
  37. package/agent/docs-md/chart-card-line.md +120 -0
  38. package/agent/docs-md/chart-donut.md +90 -0
  39. package/agent/docs-md/chart-drill-down.md +72 -0
  40. package/agent/docs-md/chart-funnel.md +71 -0
  41. package/agent/docs-md/chart-heatmap.md +76 -0
  42. package/agent/docs-md/chart-histogram.md +81 -0
  43. package/agent/docs-md/chart-map.md +165 -0
  44. package/agent/docs-md/chart-mixed.md +88 -0
  45. package/agent/docs-md/chart-price.md +92 -0
  46. package/agent/docs-md/chart-radial-bars.md +109 -0
  47. package/agent/docs-md/chart-radial-lines.md +111 -0
  48. package/agent/docs-md/chart-sankey.md +104 -0
  49. package/agent/docs-md/chart-scatter.md +87 -0
  50. package/agent/docs-md/chart-share-bar.md +89 -0
  51. package/agent/docs-md/chart-treemap.md +98 -0
  52. package/agent/docs-md/chart-waterfall.md +93 -0
  53. package/agent/docs-md/checkbox.md +131 -0
  54. package/agent/docs-md/choosing-selection-control.md +71 -0
  55. package/agent/docs-md/citations-sources.md +79 -0
  56. package/agent/docs-md/citing-sources.md +63 -0
  57. package/agent/docs-md/clarify-before-acting.md +69 -0
  58. package/agent/docs-md/clarifying-question.md +92 -0
  59. package/agent/docs-md/code-block.md +174 -0
  60. package/agent/docs-md/colours.md +87 -0
  61. package/agent/docs-md/columns-menu.md +140 -0
  62. package/agent/docs-md/combobox.md +161 -0
  63. package/agent/docs-md/command-palette.md +126 -0
  64. package/agent/docs-md/confidence-note.md +87 -0
  65. package/agent/docs-md/configurable-dashboard.md +76 -0
  66. package/agent/docs-md/confirmation-dialog.md +93 -0
  67. package/agent/docs-md/connected-tools.md +80 -0
  68. package/agent/docs-md/connector-picker.md +127 -0
  69. package/agent/docs-md/consistency.md +53 -0
  70. package/agent/docs-md/content-density.md +61 -0
  71. package/agent/docs-md/conversation-history.md +78 -0
  72. package/agent/docs-md/create-record.md +80 -0
  73. package/agent/docs-md/csv-import.md +88 -0
  74. package/agent/docs-md/dashboard-items.md +78 -0
  75. package/agent/docs-md/data-export.md +61 -0
  76. package/agent/docs-md/data-grid.md +137 -0
  77. package/agent/docs-md/data-vis-colours.md +76 -0
  78. package/agent/docs-md/data-visualisation.md +76 -0
  79. package/agent/docs-md/date-picker.md +178 -0
  80. package/agent/docs-md/delete-record.md +75 -0
  81. package/agent/docs-md/dense-table.md +495 -0
  82. package/agent/docs-md/design-tokens.md +53 -0
  83. package/agent/docs-md/disabled-read-only.md +75 -0
  84. package/agent/docs-md/drag-and-drop.md +74 -0
  85. package/agent/docs-md/dropdown-menu.md +137 -0
  86. package/agent/docs-md/edit-record.md +73 -0
  87. package/agent/docs-md/empty-loading-error.md +78 -0
  88. package/agent/docs-md/empty-state.md +128 -0
  89. package/agent/docs-md/error-messages.md +75 -0
  90. package/agent/docs-md/error-state.md +123 -0
  91. package/agent/docs-md/export-menu.md +86 -0
  92. package/agent/docs-md/feedback-prompt.md +88 -0
  93. package/agent/docs-md/field-mapping.md +78 -0
  94. package/agent/docs-md/file-drop.md +110 -0
  95. package/agent/docs-md/filter-bar.md +138 -0
  96. package/agent/docs-md/filter-panel.md +103 -0
  97. package/agent/docs-md/filtering-search.md +79 -0
  98. package/agent/docs-md/follow-ups-starters.md +79 -0
  99. package/agent/docs-md/form-validation.md +95 -0
  100. package/agent/docs-md/generated-columns.md +65 -0
  101. package/agent/docs-md/generated-drafts-files.md +71 -0
  102. package/agent/docs-md/grouped-list.md +89 -0
  103. package/agent/docs-md/guide-cue.md +170 -0
  104. package/agent/docs-md/help-panel.md +103 -0
  105. package/agent/docs-md/help-system.md +74 -0
  106. package/agent/docs-md/iconography.md +49 -0
  107. package/agent/docs-md/inbox.md +108 -0
  108. package/agent/docs-md/inline-editing-validation.md +80 -0
  109. package/agent/docs-md/inline-input-in-answer.md +70 -0
  110. package/agent/docs-md/items-palette.md +116 -0
  111. package/agent/docs-md/keyboard-model.md +47 -0
  112. package/agent/docs-md/keyboard-navigation.md +68 -0
  113. package/agent/docs-md/keyboard-shortcut-hint.md +87 -0
  114. package/agent/docs-md/keyboard-shortcut-sheet.md +90 -0
  115. package/agent/docs-md/labelling-ai-output.md +66 -0
  116. package/agent/docs-md/layout.md +47 -0
  117. package/agent/docs-md/list-table.md +254 -0
  118. package/agent/docs-md/loading-skeleton.md +91 -0
  119. package/agent/docs-md/managing-agents.md +71 -0
  120. package/agent/docs-md/message-composer.md +105 -0
  121. package/agent/docs-md/migrating.md +49 -0
  122. package/agent/docs-md/motion.md +93 -0
  123. package/agent/docs-md/navigation.md +76 -0
  124. package/agent/docs-md/notification-channels.md +65 -0
  125. package/agent/docs-md/notifications-decision.md +100 -0
  126. package/agent/docs-md/onboarding-step.md +114 -0
  127. package/agent/docs-md/order-book.md +108 -0
  128. package/agent/docs-md/page-header.md +96 -0
  129. package/agent/docs-md/pagination.md +88 -0
  130. package/agent/docs-md/pane-layout.md +105 -0
  131. package/agent/docs-md/period-comparison.md +66 -0
  132. package/agent/docs-md/plan-card.md +120 -0
  133. package/agent/docs-md/popover-rules.md +88 -0
  134. package/agent/docs-md/prompt-editor.md +90 -0
  135. package/agent/docs-md/propose-apply.md +65 -0
  136. package/agent/docs-md/provider-logo.md +97 -0
  137. package/agent/docs-md/radio.md +83 -0
  138. package/agent/docs-md/ranked-list.md +102 -0
  139. package/agent/docs-md/reasoning-progress.md +72 -0
  140. package/agent/docs-md/record-detail.md +71 -0
  141. package/agent/docs-md/regenerating-answers.md +70 -0
  142. package/agent/docs-md/run-status-bar.md +94 -0
  143. package/agent/docs-md/saved-tasks-schedules.md +63 -0
  144. package/agent/docs-md/saved-views.md +149 -0
  145. package/agent/docs-md/saving-views.md +68 -0
  146. package/agent/docs-md/scheduler.md +258 -0
  147. package/agent/docs-md/secondary-panels.md +72 -0
  148. package/agent/docs-md/segmented-control.md +88 -0
  149. package/agent/docs-md/selection-bulk-actions.md +70 -0
  150. package/agent/docs-md/separator.md +75 -0
  151. package/agent/docs-md/setup-checklist.md +92 -0
  152. package/agent/docs-md/shape-elevation.md +55 -0
  153. package/agent/docs-md/sheet-grid.md +211 -0
  154. package/agent/docs-md/side-panel.md +134 -0
  155. package/agent/docs-md/sign-in.md +114 -0
  156. package/agent/docs-md/spacing.md +55 -0
  157. package/agent/docs-md/split-button.md +100 -0
  158. package/agent/docs-md/stat-compare.md +82 -0
  159. package/agent/docs-md/static-dashboard.md +75 -0
  160. package/agent/docs-md/status-badge.md +140 -0
  161. package/agent/docs-md/step-card.md +75 -0
  162. package/agent/docs-md/stepper.md +92 -0
  163. package/agent/docs-md/suggested-changes.md +91 -0
  164. package/agent/docs-md/switch.md +78 -0
  165. package/agent/docs-md/table-settings.md +81 -0
  166. package/agent/docs-md/tabs.md +85 -0
  167. package/agent/docs-md/tag-input.md +108 -0
  168. package/agent/docs-md/tag.md +79 -0
  169. package/agent/docs-md/task-builder.md +87 -0
  170. package/agent/docs-md/team-instructions.md +72 -0
  171. package/agent/docs-md/templates-gallery.md +85 -0
  172. package/agent/docs-md/text-input.md +160 -0
  173. package/agent/docs-md/thinking-steps.md +89 -0
  174. package/agent/docs-md/timeline.md +189 -0
  175. package/agent/docs-md/timestamp.md +84 -0
  176. package/agent/docs-md/timestamps.md +70 -0
  177. package/agent/docs-md/toast.md +117 -0
  178. package/agent/docs-md/tooltip.md +90 -0
  179. package/agent/docs-md/typography.md +62 -0
  180. package/agent/docs-md/undo-destructive.md +66 -0
  181. package/agent/docs-md/unsaved-changes.md +71 -0
  182. package/agent/docs-md/usage-meter.md +80 -0
  183. package/agent/docs-md/user-feedback.md +71 -0
  184. package/agent/docs-md/voice-and-tone.md +60 -0
  185. package/agent/docs-md/why-choice.md +93 -0
  186. package/agent/docs-md/workspace-onboarding.md +74 -0
  187. package/agent/docs-md/writing.md +48 -0
  188. package/agent/llms-full.txt +18369 -0
  189. package/agent/llms.txt +198 -0
  190. package/agent/skills/twoux-audit/SKILL.md +49 -0
  191. package/agent/skills/twoux-build/SKILL.md +39 -0
  192. package/agent/skills/twoux-design/SKILL.md +54 -0
  193. package/agent/skills/twoux-write/SKILL.md +32 -0
  194. package/bin/check.mjs +123 -0
  195. package/bin/docs.mjs +24 -0
  196. package/bin/guard.mjs +27 -0
  197. package/bin/init.mjs +113 -0
  198. package/bin/knowledge.mjs +87 -0
  199. package/bin/mcp.mjs +116 -0
  200. package/bin/theme.mjs +168 -0
  201. package/bin/twoux.mjs +25 -0
  202. package/package.json +80 -3
  203. package/registry/jobs.json +1028 -0
  204. package/registry/palette.json +770 -0
  205. package/registry/rules.json +913 -0
  206. package/src/components/agent-review.tsx +364 -0
  207. package/src/components/ai-basics.tsx +212 -0
  208. package/src/components/ai-work.tsx +304 -0
  209. package/src/components/ai.tsx +227 -0
  210. package/src/components/alert-dialog.tsx +203 -0
  211. package/src/components/alert.tsx +86 -0
  212. package/src/components/avatar.tsx +114 -0
  213. package/src/components/badge.tsx +59 -0
  214. package/src/components/board.tsx +397 -0
  215. package/src/components/breadcrumb.tsx +114 -0
  216. package/src/components/button-group.tsx +90 -0
  217. package/src/components/button.tsx +109 -0
  218. package/src/components/calendar.tsx +247 -0
  219. package/src/components/card.tsx +102 -0
  220. package/src/components/chart-map.tsx +336 -0
  221. package/src/components/charts-more.tsx +1331 -0
  222. package/src/components/charts-radial.tsx +328 -0
  223. package/src/components/charts.tsx +390 -0
  224. package/src/components/checkbox.tsx +40 -0
  225. package/src/components/code.tsx +139 -0
  226. package/src/components/collapsible.tsx +37 -0
  227. package/src/components/combobox.tsx +339 -0
  228. package/src/components/command-extras.tsx +11 -0
  229. package/src/components/command.tsx +196 -0
  230. package/src/components/data-grid.tsx +213 -0
  231. package/src/components/data-table.tsx +334 -0
  232. package/src/components/data-views.tsx +740 -0
  233. package/src/components/date-picker.tsx +686 -0
  234. package/src/components/dense-table.tsx +207 -0
  235. package/src/components/dialog.tsx +162 -0
  236. package/src/components/dropdown-menu.tsx +264 -0
  237. package/src/components/empty.tsx +114 -0
  238. package/src/components/feedback-prompt.tsx +80 -0
  239. package/src/components/field.tsx +254 -0
  240. package/src/components/fields.tsx +112 -0
  241. package/src/components/flow.tsx +840 -0
  242. package/src/components/guide-cue.tsx +273 -0
  243. package/src/components/inbox.tsx +113 -0
  244. package/src/components/input-group.tsx +176 -0
  245. package/src/components/input.tsx +26 -0
  246. package/src/components/kbd.tsx +34 -0
  247. package/src/components/label.tsx +27 -0
  248. package/src/components/labels.tsx +176 -0
  249. package/src/components/motion.tsx +136 -0
  250. package/src/components/onboarding.tsx +454 -0
  251. package/src/components/order-book.tsx +185 -0
  252. package/src/components/pane-layout.tsx +417 -0
  253. package/src/components/popover.tsx +96 -0
  254. package/src/components/provider-logos.data.ts +49 -0
  255. package/src/components/radio-group.tsx +52 -0
  256. package/src/components/scheduler.tsx +997 -0
  257. package/src/components/segmented-control.tsx +28 -0
  258. package/src/components/select.tsx +195 -0
  259. package/src/components/separator.tsx +31 -0
  260. package/src/components/sheet.tsx +148 -0
  261. package/src/components/shell.tsx +426 -0
  262. package/src/components/shortcut-sheet.tsx +83 -0
  263. package/src/components/skeleton.tsx +18 -0
  264. package/src/components/sonner.tsx +107 -0
  265. package/src/components/spinner.tsx +13 -0
  266. package/src/components/split-button.tsx +58 -0
  267. package/src/components/stepper.tsx +78 -0
  268. package/src/components/switch.tsx +43 -0
  269. package/src/components/table.tsx +126 -0
  270. package/src/components/tabs.tsx +118 -0
  271. package/src/components/tag-input.tsx +253 -0
  272. package/src/components/textarea.tsx +21 -0
  273. package/src/components/theme.tsx +80 -0
  274. package/src/components/timeline.tsx +408 -0
  275. package/src/components/toggle-group.tsx +87 -0
  276. package/src/components/toggle.tsx +54 -0
  277. package/src/components/tooltip.tsx +65 -0
  278. package/src/components/workspace-actions.tsx +294 -0
  279. package/src/components/workspace-import.tsx +465 -0
  280. package/src/components/workspace.tsx +1884 -0
  281. package/src/index.ts +80 -0
  282. package/src/lib/active-indicator.tsx +53 -0
  283. package/src/lib/chart-kit.tsx +72 -0
  284. package/src/lib/cn.ts +6 -0
  285. package/src/lib/shortcuts.tsx +61 -0
  286. package/src/lib/workspace.tsx +10 -0
  287. package/src/shadcn/alert-dialog.tsx +196 -0
  288. package/src/shadcn/alert.tsx +65 -0
  289. package/src/shadcn/avatar.tsx +108 -0
  290. package/src/shadcn/badge.tsx +47 -0
  291. package/src/shadcn/breadcrumb.tsx +108 -0
  292. package/src/shadcn/button-group.tsx +83 -0
  293. package/src/shadcn/button.tsx +63 -0
  294. package/src/shadcn/calendar.tsx +220 -0
  295. package/src/shadcn/card.tsx +91 -0
  296. package/src/shadcn/checkbox.tsx +31 -0
  297. package/src/shadcn/collapsible.tsx +33 -0
  298. package/src/shadcn/command.tsx +184 -0
  299. package/src/shadcn/dialog.tsx +158 -0
  300. package/src/shadcn/dropdown-menu.tsx +256 -0
  301. package/src/shadcn/empty.tsx +103 -0
  302. package/src/shadcn/field.tsx +248 -0
  303. package/src/shadcn/input-group.tsx +170 -0
  304. package/src/shadcn/input.tsx +20 -0
  305. package/src/shadcn/kbd.tsx +28 -0
  306. package/src/shadcn/label.tsx +23 -0
  307. package/src/shadcn/popover.tsx +88 -0
  308. package/src/shadcn/radio-group.tsx +44 -0
  309. package/src/shadcn/select.tsx +189 -0
  310. package/src/shadcn/separator.tsx +27 -0
  311. package/src/shadcn/sheet.tsx +142 -0
  312. package/src/shadcn/skeleton.tsx +13 -0
  313. package/src/shadcn/sonner.tsx +40 -0
  314. package/src/shadcn/switch.tsx +34 -0
  315. package/src/shadcn/table.tsx +115 -0
  316. package/src/shadcn/tabs.tsx +90 -0
  317. package/src/shadcn/textarea.tsx +17 -0
  318. package/src/shadcn/toggle-group.tsx +83 -0
  319. package/src/shadcn/toggle.tsx +46 -0
  320. package/src/shadcn/tooltip.tsx +56 -0
  321. package/src/styles/palette.css +127 -0
  322. package/src/styles/theme.css +35 -0
  323. package/src/styles/twoux.css +236 -0
  324. package/theme.json +14 -0
  325. package/README.md +0 -3
@@ -0,0 +1,92 @@
1
+ # Clarifying question card
2
+
3
+ A card docked above the message box that asks up to three questions with recommended defaults when the answer would change the result.
4
+
5
+ Kind: component · Group: AI and agents · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - When the request is ambiguous and the answer changes the result, for example which listings to rewrite.
10
+ - Before high-impact changes or when confidence is low, collect details first.
11
+
12
+ ## When not to use
13
+
14
+ - Information already in context or retrievable from the data. Do not re-ask; use the context chips (message-composer).
15
+ - Suggesting what to ask next after an answer. Use Follow-ups (follow-ups-starters).
16
+ - Approving a change. Use Suggested changes (suggested-changes).
17
+
18
+ ## Structure
19
+
20
+ 1. Counter ("1 / 2") and question
21
+ 2. Options (title and one line each, recommended first, number key)
22
+ 3. Other (free text, last)
23
+ 4. Footer (Previous, Skip, Next ↵)
24
+ 5. Thread line "Waiting for your answer" with timer
25
+
26
+ ## Guidelines
27
+
28
+ - Ask only when the answer changes the result, at most three questions, one at a time.
29
+ - Every question has a default, shown first and marked "(Recommended)". Skip uses the defaults for the rest.
30
+ - Options first, free text last. Structured choices beat free text.
31
+ - Dock the card above the message box so the thread does not fill with back and forth.
32
+ - Stop ends the questions and says "Stopped. Nothing was changed."
33
+
34
+ ## Content
35
+
36
+ - Questions are short and specific: "Which titles should I rewrite?"
37
+ - Each option has a title and one line of consequence ("Leaves listings that are doing well alone").
38
+ - Use "choose" or "select", not "click".
39
+
40
+ ## Accessibility
41
+
42
+ - Options are a radiogroup labelled by the question; each option is role="radio" with aria-checked.
43
+ - Arrow keys move and select within the group; Tab moves to Other and the footer buttons in order (SC 2.4.3).
44
+ - Number keys 1 to 4 are single character shortcuts, so they must be active only while the card is open and focus is not in a text field, and must respect the setting that turns single-key shortcuts off (SC 2.1.4). The mock listens while the card is open but ignores that setting, which needs fixing.
45
+ - The waiting timer must not limit time to answer (SC 2.2.1); it only shows elapsed time.
46
+ - Announce the new question when Next is pressed (SC 4.1.3).
47
+
48
+ ## Keyboard
49
+
50
+ | Keys | Action |
51
+ |---|---|
52
+ | 1 to 3 | Choose an option |
53
+ | 4 | Move focus to Other |
54
+ | Arrow keys | Move between options |
55
+ | Enter | Next question, or run when it is the last |
56
+ | Esc | Close the panel (Stop ends the questions) |
57
+
58
+ ## Examples
59
+
60
+ ### ask
61
+
62
+ ```tsx
63
+ import { ClarifyingQuestion, toast } from "@twoux/ui";
64
+ import { useState } from "react";
65
+
66
+ export const meta = { title: "Ask only what changes the result", description: "One question at a time, at most three. The recommended option comes first; number keys choose; Other is last; Skip keeps the defaults.", order: 1 };
67
+
68
+ export default function Example() {
69
+ const [k, setK] = useState(0);
70
+ return (
71
+ <div className="w-full max-w-lg">
72
+ <ClarifyingQuestion
73
+ key={k}
74
+ questions={[
75
+ { question: "Which listings should I compare?", options: [{ id: "team", title: "Same team, last 90 days", detail: "22 listings" }, { id: "all", title: "All listings this year", detail: "141 listings" }] },
76
+ { question: "How should I show the result?", options: [{ id: "table", title: "A short table" }, { id: "chart", title: "A chart by week" }] },
77
+ ]}
78
+ onDone={(a) => { toast(`Continuing with: ${a.join(", ")}`); setK((x) => x + 1); }}
79
+ />
80
+ </div>
81
+ );
82
+ }
83
+ ```
84
+
85
+ ## Related
86
+
87
+ - Assistant side panel
88
+ - Message composer
89
+ - Thinking and steps
90
+ - Agent run timeline
91
+ - Plan preview
92
+ - Confidence note
@@ -0,0 +1,174 @@
1
+ # Code block
2
+
3
+ Shows code, commands, IDs or log output exactly as written, so people can read and copy it. Inline Code marks a token inside a sentence.
4
+
5
+ Kind: component · Group: Content · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - To let people copy a command, token, API key name or snippet exactly.
10
+ - To show a change as a diff, with added and removed lines.
11
+ - To show tool or agent output, such as a run log, using the small size.
12
+ - Inline Code for a prop, token or file name inside a sentence: `bg-surface`, `jobs.json`.
13
+
14
+ ## When not to use
15
+
16
+ - When people need to edit the text. Use a Text input or the Prompt editor.
17
+ - For ordinary values in a table, such as an ID column. Use the mono table cell.
18
+ - Inline Code across several lines. Use a code block.
19
+
20
+ ## Structure
21
+
22
+ 1. Header (optional): file name or label
23
+ 2. Actions: copy button, more if needed
24
+ 3. Content: lines, with optional numbers
25
+ 4. Line marks: highlighted, added (+), removed (−)
26
+ 5. Footer (optional): show more, captions
27
+
28
+ ## Guidelines
29
+
30
+ - Long single-line commands scroll sideways rather than wrapping, so they stay copyable as one line. Use wrap for prose-like output.
31
+ - Past about nine lines, cap the height and offer Show more in the footer.
32
+ - Without a header, the copy button floats top right and appears on hover or focus, and always on touch screens.
33
+ - Line numbers are optional, never copied and hidden from screen readers.
34
+ - Use the destructive variant for errors and failed output.
35
+ - Light border, grey fill: the block is read, not raised, so it takes no shadow.
36
+
37
+ ## Content
38
+
39
+ - Label the header with the file name or what the code is: “twoux.css”, “Install”, “Run log”.
40
+ - Show code exactly as it should be typed. No ellipses inside commands.
41
+
42
+ ## Accessibility
43
+
44
+ - The scrolling area is focusable (tabindex 0) so keyboard users can scroll it.
45
+ - Added and removed lines show + and − as well as colour.
46
+ - Copying announces “Copied” through a status message without moving focus; focus stays on the button.
47
+ - Wrapping is available so long output does not force two-dimensional scrolling at 320px.
48
+ - Code text keeps 4.5:1 on the grey fill; line numbers use ink-3 (5.4:1 on surface-2).
49
+
50
+ ## Keyboard
51
+
52
+ | Keys | Action |
53
+ |---|---|
54
+ | Tab | Focus the code area, then the copy button |
55
+ | Arrow keys | Scroll the code area when it has focus |
56
+ | Enter or Space | Copy, when the copy button has focus |
57
+
58
+ ## Examples
59
+
60
+ ### command
61
+
62
+ ```tsx
63
+ import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton } from "@twoux/ui";
64
+
65
+ export const meta = { title: "Command", description: "One line, no header. Copy appears on hover or focus. Long commands scroll instead of wrapping.", order: 2 };
66
+
67
+ const cmd = "pnpm add @twoux/ui && pnpm llm";
68
+
69
+ export default function Example() {
70
+ return (
71
+ <CodeBlock className="w-full max-w-lg">
72
+ <CodeBlockActions floating>
73
+ <CodeBlockCopyButton value={cmd} />
74
+ </CodeBlockActions>
75
+ <CodeBlockContent code={`$ ${cmd}`} />
76
+ </CodeBlock>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ### diff
82
+
83
+ ```tsx
84
+ import { CodeBlock, CodeBlockContent, CodeBlockHeader, CodeBlockTitle } from "@twoux/ui";
85
+
86
+ export const meta = { title: "Diff", description: "Added and removed lines carry + and − as well as colour, so the change is clear without colour.", order: 3 };
87
+
88
+ export default function Example() {
89
+ return (
90
+ <CodeBlock className="w-full max-w-lg">
91
+ <CodeBlockHeader>
92
+ <CodeBlockTitle>Suggested change · listing title</CodeBlockTitle>
93
+ </CodeBlockHeader>
94
+ <CodeBlockContent code={`title: "Operations Coordinator"\ntitle: "Junior Operations Coordinator"\nteam: "Operations"`} removed={[1]} added={[2]} />
95
+ </CodeBlock>
96
+ );
97
+ }
98
+ ```
99
+
100
+ ### file
101
+
102
+ ```tsx
103
+ import { CodeBlock, CodeBlockActions, CodeBlockContent, CodeBlockCopyButton, CodeBlockHeader, CodeBlockTitle } from "@twoux/ui";
104
+ import { FileCode2 } from "lucide-react";
105
+ // twoux-check-disable raw-colour: the colours are code sample text, not styles
106
+
107
+ export const meta = { title: "With file name", description: "The header names what the code is. Copy sits at the end of the header.", order: 1 };
108
+
109
+ const code = `@theme {
110
+ --color-primary: var(--color-indigo-600);
111
+ --color-line: var(--color-neutral-100);
112
+ --shadow-card: 0 1px 2px rgb(23 23 40 / 0.04);
113
+ }`;
114
+
115
+ export default function Example() {
116
+ return (
117
+ <CodeBlock className="w-full max-w-lg">
118
+ <CodeBlockHeader>
119
+ <CodeBlockTitle icon={<FileCode2 />}>twoux.css</CodeBlockTitle>
120
+ <CodeBlockActions>
121
+ <CodeBlockCopyButton value={code} />
122
+ </CodeBlockActions>
123
+ </CodeBlockHeader>
124
+ <CodeBlockContent code={code} lineNumbers highlight={[2]} />
125
+ </CodeBlock>
126
+ );
127
+ }
128
+ ```
129
+
130
+ ### inline
131
+
132
+ ```tsx
133
+ import { Code } from "@twoux/ui";
134
+
135
+ export const meta = { title: "Inline", description: "For a token, prop or file inside a sentence. It never breaks mid-token.", order: 5 };
136
+
137
+ export default function Example() {
138
+ return (
139
+ <p className="max-w-md text-base text-ink">
140
+ Style only with role tokens such as <Code>bg-surface</Code> and <Code>text-ink-2</Code>, defined in <Code>twoux.css</Code>.
141
+ </p>
142
+ );
143
+ }
144
+ ```
145
+
146
+ ### output
147
+
148
+ ```tsx
149
+ import { CodeBlock, CodeBlockContent, CodeBlockFooter, CodeBlockHeader, CodeBlockTitle } from "@twoux/ui";
150
+
151
+ export const meta = { title: "Run output and errors", description: "Small size for agent and tool output. The destructive variant marks a failed run.", order: 4 };
152
+
153
+ export default function Example() {
154
+ return (
155
+ <div className="grid w-full max-w-lg gap-3">
156
+ <CodeBlock size="sm">
157
+ <CodeBlockHeader>
158
+ <CodeBlockTitle>Run log · Rename low performers</CodeBlockTitle>
159
+ </CodeBlockHeader>
160
+ <CodeBlockContent code={"09:00:02 Found 2 listings under 3%\n09:00:05 Drafted 2 titles\n09:00:05 Waiting for approval"} />
161
+ </CodeBlock>
162
+ <CodeBlock size="sm" variant="destructive">
163
+ <CodeBlockContent code={"09:00:07 Could not reach twoux.co.uk/jobs (timeout after 10s)"} wrap />
164
+ <CodeBlockFooter>Try again from the run, or check the careers page link.</CodeBlockFooter>
165
+ </CodeBlock>
166
+ </div>
167
+ );
168
+ }
169
+ ```
170
+
171
+ ## Related
172
+
173
+ - Text input
174
+ - Toast
@@ -0,0 +1,87 @@
1
+ # Colours
2
+
3
+ TwoUX is a mostly neutral interface, light by default with a dark theme people can choose. Colour is spent on two things only: the one accent that marks the main action and current selection, and three reserved status families. Every colour is applied through a role token, never a hex value.
4
+
5
+ Kind: foundation · Group: Visual foundation
6
+
7
+ ## Principles
8
+
9
+ - Neutral first. Surfaces, text and lines are cool greys with a slight violet bias, so data and status carry the colour. Most of the UI stays white and greyscale, and colour is kept for calls to action and status.
10
+ - Light by default, dark by choice. Every role token holds a light and a dark value (CSS light-dark()), so components are written once and switch together when <html> gets data-theme="dark", or "system" to follow the device. Dark values are chosen for dark, not inverted: surfaces step lighter as they rise (page, surface, popover), status text moves to the 400 steps, status fills are mixed into the surface, and chart colours are a separate validated selection. Each pair is contrast-checked against its own theme's surface.
11
+ - One accent with one job. --accent marks the primary action, the current selection and focus. It never means good, bad or anything else.
12
+ - Status colours mean status. --ok, --warn and --bad (and --mute for neutral) describe the state of a record, such as a listing that is live, expiring or closed. They are never decoration and never a chart series.
13
+ - Choose by role, not by look. Pick the token whose name matches the job, even when another token happens to render the same hex.
14
+ - Colour is never the only cue. Every state that uses colour also has text, an icon or a shape change.
15
+
16
+ ## When to use
17
+
18
+ - Use --accent for the single primary button in a view, the selected nav item, links and the focus ring.
19
+ - Use a status family (text, -bg, -line together) for badges, banners, inline validation and row state.
20
+
21
+ ## When not to use
22
+
23
+ - Do not use status colours to make something look important; use type weight or position instead.
24
+ - Do not use --c1 to --c8 or --q1 to --q5 in UI chrome; those belong to charts only.
25
+
26
+ ## Guidelines
27
+
28
+ - Text: --ink (17.8:1 on white) for primary text, --ink-2 (9.1:1) for secondary text and labels, --ink-3 (5.7:1) for metadata and placeholders. All three pass the 4.5:1 text minimum on both --surface and --bg.
29
+ - Ground: --bg (#F5F5F8) is the app chrome; --surface (#FFFFFF) is the inset canvas where work happens; --surface-2 is a quiet well inside a surface. --hover and --selected are row and item states.
30
+ - Lines: --line and --line-strong are separators for grouping. At 1.24:1 and 1.44:1 on white they are too faint to be the only thing that identifies a control, so inputs and buttons must also have a fill, label or text that meets 3:1 or 4.5:1.
31
+ - Status families come in threes: text (--ok #1E7F47), background (--ok-bg) and border (--ok-line). Use the three together so the pairing is always the tested one: --ok on --ok-bg is 4.51:1, --warn on --warn-bg 5.30:1, --bad on --bad-bg 4.79:1.
32
+ - Selected and active states pair colour with a second cue: a check, a filled background, a weight change or an underline.
33
+ - --progress (#D9A118) is 2.32:1 on white. It may only fill a bar or dot that sits next to a text value; it must never carry meaning alone.
34
+ - AI has no colour of its own. The origin mark and its text use the muted text colour; generated cells use the neutral generated tint.
35
+ - No gradients, glows or animated fills on AI surfaces, avatars or loading. Progress is shown with words, a timer and the standard spinner or progress bar.
36
+ - Form controls, links and the accent keep their usual colours inside the assistant.
37
+
38
+ ## Accessibility
39
+
40
+ - Body text must reach 4.5:1 and large text 3:1 against its actual background; ratios are not rounded up, so 4.49:1 fails.
41
+ - Control boundaries, icons that carry meaning and the focus ring must reach 3:1 against adjacent colours.
42
+ - Disabled controls are exempt from contrast, but they must still be identifiable by label.
43
+ - Never convey status by colour alone. A red row also says 'Closed'; a green dot also says 'Live'.
44
+
45
+ ## Do
46
+
47
+ - Use one --accent button per view so the main action is obvious.
48
+ - Pair every status colour with a word.
49
+ - Reference tokens by name in code.
50
+
51
+ ## Don't
52
+
53
+ - Do not write hex, rgb or named colours in component code.
54
+ - Do not use --bad for anything that is not an error, a destructive action or a closed state.
55
+ - Do not pick a token because its colour matches what you want; pick it because its name matches the job.
56
+
57
+ ## Tokens
58
+
59
+ | Token | Value | Use |
60
+ |---|---|---|
61
+ | `--bg` | `#F5F5F8` | App chrome behind the canvas |
62
+ | `--surface` | `#FFFFFF` | Inset canvas, cards, panels, popovers |
63
+ | `--surface-2` | `#FAFAFC` | Wells and quiet areas inside a surface |
64
+ | `--hover` | `#ECECF2` | Row and item hover on a surface (tables, lists, menus) |
65
+ | `--selected` | `#E6E6EC` | Hover on the app background (sidebar, top bar) and the cell under the pointer in a grid; the current nav item sits raised on --surface instead |
66
+ | `--line` | `#E6E6EC` | Default separator |
67
+ | `--line-strong` | `#D6D6DF` | Stronger separator, keycap border |
68
+ | `--ink` | `#17171F` | Primary text (17.8:1 on white) |
69
+ | `--ink-2` | `#474756` | Secondary text and labels (9.1:1) |
70
+ | `--ink-3` | `#656574` | Metadata, placeholder (5.7:1) |
71
+ | `--accent` | `#5753E0` | Primary action, selection, links (5.65:1) |
72
+ | `--accent-hover` | `#4A46D1` | Primary action hover |
73
+ | `--accent-soft` | `#EEEDFD` | Accent tint for selected and AI surfaces |
74
+ | `--accent-ink` | `#4340C4` | Accent text on --accent-soft (6.56:1) |
75
+ | `--on-accent` | `#FFFFFF` | Text on --accent |
76
+ | `--ok / --ok-bg / --ok-line` | `#1E7F47 / #E8F6EE / #BFE3CD` | Positive status |
77
+ | `--warn / --warn-bg / --warn-line` | `#965606 / #FDF4E3 / #F0D7A8` | Caution status |
78
+ | `--bad / --bad-bg / --bad-line` | `#C2333B / #FCECED / #F1C3C6` | Error, destructive, closed |
79
+ | `--mute / --mute-bg / --mute-line` | `#474756 / #F2F2F5 / #E1E1E8` | Neutral status such as Draft |
80
+ | `--progress` | `#D9A118` | Progress fill only, always next to a text value |
81
+
82
+ ## Related
83
+
84
+ - Data visualisation colours
85
+ - Design tokens
86
+ - Accessibility
87
+ - Shape and elevation
@@ -0,0 +1,140 @@
1
+ # Columns menu
2
+
3
+ A menu that shows, hides and reorders the columns of a sheet or list table, with search, Show all and Hide all, type icons and eye toggles, plus wrapping and rows per page on list tables. Changes apply at once.
4
+
5
+ Kind: component · Group: Data · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - On a sheet or list table where people change which columns they see while they work and expect the change at once.
10
+ - A table has more columns than most people need at once; let each person show, hide and reorder them.
11
+ - People choose page size or line wrapping for their own work on a list table.
12
+
13
+ ## When not to use
14
+
15
+ - To change what a column holds. Use the column menu in the sheet.
16
+ - For filtering rows. Use the TwoUX Filter bar.
17
+ - For saving a full working setup with filters. Use TwoUX Saved views.
18
+ - As the main way to make a table usable; most people never customise, so defaults must already work.
19
+
20
+ ## Structure
21
+
22
+ 1. Button: Columns
23
+ 2. Search field
24
+ 3. Count with Show all and Hide all
25
+ 4. Row per column: drag handle, type icon, name, Move up, Move down, eye toggle or lock
26
+ 5. List tables: Wrap long text and Rows per page (25, 50, 100)
27
+ 6. Where the choice is kept ("Saved for you on this table") and the help line for keyboard reordering
28
+
29
+ ## Guidelines
30
+
31
+ - Apply each change at once; there is nothing to confirm. Changing it back is the undo.
32
+ - Every drag has a keyboard path (Space to pick up, arrows, Space to drop, Esc to cancel) and Move up and Move down buttons.
33
+ - The column that names the record is locked: always shown, marked with a lock and the words Always shown.
34
+ - Show the type icon of each column so people recognise it among similar names.
35
+ - List columns in the same order they appear in the table.
36
+ - Store the choice per person and per table, restore it on return, and say so in the menu: "Saved for you on this table".
37
+ - Rows per page is a radio group, not a select, so the menu never opens a second popover.
38
+
39
+ ## Content
40
+
41
+ - Use sentence case and plain labels, for example "Wrap long text".
42
+ - Zero results: No columns match "cost".
43
+
44
+ ## Accessibility
45
+
46
+ - Eye toggles are buttons with aria-pressed and a name that says what they do: Hide Email.
47
+ - Moves are announced: Email moved to position 3 of 10.
48
+ - 2.5.7 Dragging movements: the Move buttons do the same as dragging.
49
+ - Wrap long text is a labelled switch; Rows per page is a radio group named by its label.
50
+
51
+ ## Keyboard
52
+
53
+ | Keys | Action |
54
+ |---|---|
55
+ | Space / Enter on a handle | Pick up or drop |
56
+ | ↑ ↓ (picked up) | Move the column |
57
+ | Esc (picked up) | Cancel the move |
58
+ | Esc | Close the menu; focus returns to Columns |
59
+
60
+ ## Examples
61
+
62
+ ### list-table
63
+
64
+ ```tsx
65
+ import { ColumnsMenu, type ColumnsMenuItem, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@twoux/ui";
66
+ import { useState } from "react";
67
+ import { LISTINGS, type Listing, formatDate } from "../list-table/data";
68
+
69
+ export const meta = { title: "On a list table", description: "The same menu on a list table, with Wrap long text and Rows per page. Role is locked so every row stays recognisable. Every change applies at once.", order: 2 };
70
+
71
+ const CELL: Record<string, (l: Listing) => string | number> = { role: (l) => l.role, location: (l) => l.location, status: (l) => l.status, applicants: (l) => l.applicants, closes: (l) => formatDate(l.closes) };
72
+
73
+ export default function Example() {
74
+ const [columns, setColumns] = useState<ColumnsMenuItem[]>([
75
+ { id: "role", label: "Role", locked: true },
76
+ { id: "location", label: "Location" },
77
+ { id: "status", label: "Status" },
78
+ { id: "applicants", label: "Applicants", hidden: true },
79
+ { id: "closes", label: "Closes" },
80
+ ]);
81
+ const [wrap, setWrap] = useState(false);
82
+ const [pageSize, setPageSize] = useState(25);
83
+ const cols = columns.filter((c) => !c.hidden);
84
+ return (
85
+ <div className="grid w-full max-w-2xl gap-2">
86
+ <div className="flex justify-end">
87
+ <ColumnsMenu columns={columns} onChange={setColumns} wrap={wrap} onWrapChange={setWrap} pageSize={pageSize} onPageSizeChange={setPageSize} note="Saved for you on this table" />
88
+ </div>
89
+ <div className="overflow-hidden rounded-sm border border-line bg-surface">
90
+ <Table aria-label="Listings">
91
+ <TableHeader>
92
+ <TableRow className="hover:bg-transparent">{cols.map((c) => <TableHead key={c.id}>{c.label}</TableHead>)}</TableRow>
93
+ </TableHeader>
94
+ <TableBody>
95
+ {LISTINGS.slice(0, 4).map((l) => (
96
+ <TableRow key={l.id}>{cols.map((c) => <TableCell key={c.id} className={wrap ? "whitespace-normal" : undefined}>{CELL[c.id](l)}</TableCell>)}</TableRow>
97
+ ))}
98
+ </TableBody>
99
+ </Table>
100
+ <p className="tabular border-t border-line px-3 py-2 text-sm text-ink-2">Showing 1–4 of {LISTINGS.length} listings · {pageSize} per page</p>
101
+ </div>
102
+ </div>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ### sheet
108
+
109
+ ```tsx
110
+ import { ColumnsMenu, type ColumnsMenuItem } from "@twoux/ui";
111
+ import { useState } from "react";
112
+
113
+ export const meta = { title: "Show, hide and reorder columns", description: "Changes apply at once. Drag a handle, use Move up and Move down, or focus a handle, press Space, move with the arrow keys and press Space to drop.", order: 1 };
114
+
115
+ export default function Example() {
116
+ const [columns, setColumns] = useState<ColumnsMenuItem[]>([
117
+ { id: "name", label: "Name", type: "text", locked: true },
118
+ { id: "title", label: "Job title", type: "text" },
119
+ { id: "company", label: "Company", type: "text" },
120
+ { id: "size", label: "Company size", type: "number" },
121
+ { id: "added", label: "Added", type: "date", hidden: true },
122
+ { id: "fit", label: "Fit score", type: "formula" },
123
+ { id: "summary", label: "Summarise role", type: "ai" },
124
+ { id: "email", label: "Work email", type: "enrichment", provider: "hubspot" },
125
+ ]);
126
+ return (
127
+ <div className="grid justify-items-start gap-2">
128
+ <ColumnsMenu columns={columns} onChange={setColumns} align="start" />
129
+ <p className="text-sm text-ink-2">Shown, in order: {columns.filter((c) => !c.hidden).map((c) => c.label).join(", ")}</p>
130
+ </div>
131
+ );
132
+ }
133
+ ```
134
+
135
+ ## Related
136
+
137
+ - Sheet grid
138
+ - List table
139
+ - Saved views
140
+ - pagination-counts
@@ -0,0 +1,161 @@
1
+ # Combobox
2
+
3
+ Pick one or several values from a long list by typing a few letters, and add a missing one without leaving the form: a location, skills, an owner.
4
+
5
+ Kind: component · Group: Actions and inputs · Status: built, import from @twoux/ui
6
+
7
+ ## When to use
8
+
9
+ - One value from eight or more options, such as a location from 40 offices.
10
+ - Several values from eight or more options, such as skills from a long list (multiple).
11
+ - Options the server searches, such as people in a large company (filter={null} with onSearch).
12
+ - When a missing option can be created on the spot: Create "Leeds office".
13
+
14
+ ## When not to use
15
+
16
+ - Up to seven options. Show them all as Radio buttons (radio) or Checkboxes (checkbox).
17
+ - Free values with no list, such as email addresses. Use Tag input (tag-input).
18
+ - Narrowing a list or a table. Use Filter pills (filter-bar).
19
+ - Running commands or jumping anywhere. Use the Command palette (command-palette).
20
+
21
+ ## Structure
22
+
23
+ 1. Label
24
+ 2. Trigger: the value, or chosen values as tags then +N more
25
+ 3. Popover: search field
26
+ 4. Options, grouped (optional), with a check for chosen ones and secondary text
27
+ 5. Status: searching, no match, or failed with Try again
28
+ 6. Create "…" (optional)
29
+ 7. Footer for several: count, limit and Clear
30
+
31
+ ## Guidelines
32
+
33
+ - Matching ignores case and accents and looks at the label and the secondary text.
34
+ - With several values, chosen options are listed first when the list opens and keep their place while people tick, so rows don't jump.
35
+ - For server search, results replace the list in one go. "Searching…" shows only if the wait passes one second.
36
+ - If options fail to load, say so inside the list with Try again; the rest of the form keeps working.
37
+ - Offer Create only when nothing matches exactly. A name-only record is created and selected at once; anything more opens an overlay side panel and is selected when saved.
38
+ - Up to three chosen values show as tags in the trigger, then +N more. The footer states the count and any limit.
39
+ - A required combobox shows its error under the field on submit; the submit button is never disabled.
40
+
41
+ ## Content
42
+
43
+ - The placeholder names the action and the thing: Choose a location. The search field says Search locations.
44
+ - No match names the text: No locations match "lee".
45
+ - Create repeats the text in quotes: Create "Leeds office".
46
+
47
+ ## Accessibility
48
+
49
+ - The search field is a combobox controlling a listbox; options are named and the highlighted one is announced with aria-activedescendant.
50
+ - The trigger is named by the label and the value: "Location, Leeds" or "Skills, 3 selected".
51
+ - Result counts and the searching state are announced politely.
52
+ - Each tag's remove button is named "Remove Python" and has a 24px target.
53
+
54
+ ## Keyboard
55
+
56
+ | Keys | Action |
57
+ |---|---|
58
+ | Enter, Space or ↓ (trigger) | Open the list |
59
+ | A letter (trigger) | Open the list with that letter in the search |
60
+ | ↑ ↓ | Move through options |
61
+ | Enter | Choose the option; with several values it ticks and the list stays open |
62
+ | Esc | Close without changing the value; focus returns to the trigger |
63
+ | Backspace (trigger) | Move to the last chosen tag; Backspace again removes it |
64
+
65
+ ## Examples
66
+
67
+ ### location
68
+
69
+ ```tsx
70
+ import { Combobox, type ComboboxOption } from "@twoux/ui";
71
+ import { useState } from "react";
72
+
73
+ export const meta = { title: "One value, with Create", description: "Type a few letters. When an office isn't listed, Create adds it and selects it in one step.", order: 1 };
74
+
75
+ const START: ComboboxOption[] = [
76
+ ["Aberdeen", "Scotland"], ["Belfast", "Northern Ireland"], ["Birmingham", "England"], ["Bristol", "England"], ["Cardiff", "Wales"], ["Edinburgh", "Scotland"],
77
+ ["Glasgow", "Scotland"], ["Leeds", "England"], ["Liverpool", "England"], ["London", "England"], ["Manchester", "England"], ["Newcastle", "England"], ["Swansea", "Wales"],
78
+ ].map(([label, description]) => ({ value: label.toLowerCase(), label, description, group: description }));
79
+
80
+ export default function Example() {
81
+ const [options, setOptions] = useState(START);
82
+ const [value, setValue] = useState<string | null>("leeds");
83
+ return (
84
+ <div className="w-72">
85
+ <Combobox
86
+ label="Location"
87
+ noun={{ one: "office", many: "offices" }}
88
+ options={options}
89
+ value={value}
90
+ onValueChange={setValue}
91
+ onCreate={(text) => {
92
+ const made = { value: text.toLowerCase(), label: text, description: "New office" };
93
+ setOptions((o) => [...o, made]);
94
+ return made;
95
+ }}
96
+ required
97
+ />
98
+ </div>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ### server-search
104
+
105
+ ```tsx
106
+ import { Combobox, type ComboboxOption, matchOption } from "@twoux/ui";
107
+ import { useCallback, useState } from "react";
108
+
109
+ export const meta = { title: "Server search and errors", description: "The app filters as people type. Nothing flickers for quick answers; a failure shows inside the list with Try again. Type \"fail\" to see it.", order: 3 };
110
+
111
+ const PEOPLE: ComboboxOption[] = ["Amara Okafor", "Ben Hughes", "Chloé Martin", "Dev Patel", "Eilidh Ross", "Femi Adeyemi", "Grace Lin", "Harry Evans", "Isla Byrne", "Jonas Weber"].map((n, i) => ({ value: `u${i}`, label: n, description: ["Talent", "Engineering", "Design", "Finance"][i % 4] }));
112
+
113
+ export default function Example() {
114
+ const [options, setOptions] = useState<ComboboxOption[]>(PEOPLE);
115
+ const [loading, setLoading] = useState(false);
116
+ const [loadError, setLoadError] = useState<string>();
117
+ const [value, setValue] = useState<string | null>(null);
118
+ const search = useCallback((text: string) => {
119
+ setLoading(true);
120
+ setLoadError(undefined);
121
+ setTimeout(() => {
122
+ setLoading(false);
123
+ if (text.trim().toLowerCase() === "fail") return setLoadError("People didn't load. Check your connection.");
124
+ setOptions(PEOPLE.filter((p) => matchOption(p, text)));
125
+ }, 1400);
126
+ }, []);
127
+ return (
128
+ <div className="w-72">
129
+ <Combobox label="Hiring manager" noun={{ one: "person", many: "people" }} options={options} value={value} onValueChange={setValue} filter={null} onSearch={search} loading={loading} loadError={loadError} onRetry={() => search("")} />
130
+ </div>
131
+ );
132
+ }
133
+ ```
134
+
135
+ ### skills
136
+
137
+ ```tsx
138
+ import { Combobox } from "@twoux/ui";
139
+ import { useState } from "react";
140
+
141
+ export const meta = { title: "Several values", description: "Ticking keeps the list open. Up to three skills show in the field, then +N more; the footer counts them against the limit.", order: 2 };
142
+
143
+ const SKILLS = ["Airflow", "AWS", "Azure", "dbt", "Docker", "Excel", "Figma", "GCP", "Go", "Java", "Kotlin", "Kubernetes", "Looker", "Power BI", "Python", "R", "React", "Rust", "Scala", "Snowflake", "Spark", "SQL", "Tableau", "Terraform", "TypeScript"].map((s) => ({ value: s, label: s }));
144
+
145
+ export default function Example() {
146
+ const [value, setValue] = useState<string[]>(["Python", "SQL", "dbt", "Airflow"]);
147
+ return (
148
+ <div className="w-80">
149
+ <Combobox multiple label="Skills" noun={{ one: "skill", many: "skills" }} options={SKILLS} value={value} onValueChange={setValue} max={8} hint="Shown on the listing and used to match candidates." />
150
+ </div>
151
+ );
152
+ }
153
+ ```
154
+
155
+ ## Related
156
+
157
+ - Checkbox
158
+ - Radio
159
+ - Tag input
160
+ - Choosing a selection control
161
+ - Creating a record: dialog, single page or steps