@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,207 @@
1
+ /**
2
+ * TwoUX compositions for the dense Table (size="dense", 28px rows) used for spreadsheet-like data: inventories,
3
+ * ledgers, imports and mapping. Built on the generated shadcn Table, Button, Input, DropdownMenu and Tooltip.
4
+ * TableGroupRow: a full-width section row with its label, an optional info tooltip and count, optionally collapsible.
5
+ * TableRowNumber: the row number with a ⋮ menu of row actions, named "Actions for <row>".
6
+ * TableFilterRow: one compact search field under each filterable header, named "Filter <column>".
7
+ * TableCellAction: an in-cell button revealed on row hover and keyboard focus, always shown on touch. Its action is
8
+ * always in the row menu too, so it is never the only way.
9
+ * TableNotApplicable and TableLinkedItems: "n/a" read as "not applicable", and two items joined by an arrow.
10
+ */
11
+ import { ArrowRight, ChevronRight, EllipsisVertical, Info, Search } from "lucide-react"
12
+ import * as React from "react"
13
+ import { cn } from "../lib/cn"
14
+ import { Button } from "./button"
15
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "./dropdown-menu"
16
+ import { TableCell, TableRow } from "./table"
17
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"
18
+
19
+ /* ---------------------------------------------------------------- group row */
20
+
21
+ /**
22
+ * Put each group in its own TableBody with this row first. With onExpandedChange the label becomes a button with
23
+ * aria-expanded; leave the group's rows out while it is collapsed.
24
+ */
25
+ export function TableGroupRow({
26
+ label,
27
+ info,
28
+ count,
29
+ colSpan,
30
+ expanded,
31
+ onExpandedChange,
32
+ controls,
33
+ children,
34
+ className,
35
+ }: {
36
+ label: string
37
+ /** What belongs in the group, in a tooltip on the info button. */
38
+ info?: React.ReactNode
39
+ count?: number
40
+ /** The number of columns in the table. */
41
+ colSpan: number
42
+ expanded?: boolean
43
+ onExpandedChange?: (expanded: boolean) => void
44
+ /** id of the TableBody the row opens and closes. */
45
+ controls?: string
46
+ /** Extra content on the right of the row, such as a group total. */
47
+ children?: React.ReactNode
48
+ className?: string
49
+ }) {
50
+ const collapsible = !!onExpandedChange
51
+ const open = expanded ?? true
52
+ const name = (
53
+ <>
54
+ {collapsible && <ChevronRight aria-hidden="true" className={cn("size-3.5 shrink-0 text-ink-3 transition-transform duration-(--duration-fast) ease-(--ease-out)", open && "rotate-90")} />}
55
+ <span className="truncate font-medium text-ink">{label}</span>
56
+ {count !== undefined && <span className="tabular shrink-0 text-sm font-normal text-ink-3">{count.toLocaleString("en-GB")}<span className="sr-only"> rows</span></span>}
57
+ </>
58
+ )
59
+ return (
60
+ <TableRow data-slot="table-group-row" className={cn("bg-surface-2 hover:bg-surface-2", className)}>
61
+ <th scope="rowgroup" colSpan={colSpan} className="h-9 p-0 text-left align-middle text-base font-normal group-data-[size=dense]/table:h-7">
62
+ <div className="sticky left-0 flex w-fit max-w-full items-center gap-control px-3 group-data-[size=dense]/table:px-2">
63
+ {collapsible ? (
64
+ <button
65
+ type="button"
66
+ aria-expanded={open}
67
+ aria-controls={controls}
68
+ onClick={() => onExpandedChange?.(!open)}
69
+ className="-ml-1 flex min-w-0 items-center gap-control rounded-xs px-1 outline-none hover:bg-hover focus-visible:ring-2 focus-visible:ring-ring/50"
70
+ >
71
+ {name}
72
+ </button>
73
+ ) : name}
74
+ {info && (
75
+ <Tooltip>
76
+ <TooltipTrigger asChild>
77
+ <button type="button" aria-label={`About ${label}`} className="relative grid size-5 shrink-0 place-items-center rounded-xs text-ink-3 outline-none after:absolute after:-inset-0.5 hover:bg-hover hover:text-ink focus-visible:ring-2 focus-visible:ring-ring/50">
78
+ <Info className="size-3.5" aria-hidden="true" />
79
+ </button>
80
+ </TooltipTrigger>
81
+ <TooltipContent className="max-w-64">{info}</TooltipContent>
82
+ </Tooltip>
83
+ )}
84
+ {children}
85
+ </div>
86
+ </th>
87
+ </TableRow>
88
+ )
89
+ }
90
+
91
+ /* ---------------------------------------------------------------- row number + row actions */
92
+
93
+ /** The first cell of a row: its number, then ⋮ with the row's actions (DropdownMenuItem children). */
94
+ export function TableRowNumber({
95
+ n,
96
+ label,
97
+ children,
98
+ className,
99
+ ...props
100
+ }: Omit<React.ComponentProps<typeof TableCell>, "children"> & {
101
+ n: number
102
+ /** Names the row in the menu button: "Actions for <label>". */
103
+ label: string
104
+ /** The row's actions. Include every in-cell action here too. */
105
+ children: React.ReactNode
106
+ }) {
107
+ return (
108
+ <TableCell data-slot="table-row-number" className={cn("w-16 pr-1 text-sm text-ink-3 group-data-[size=dense]/table:pr-0.5", className)} {...props}>
109
+ <span className="flex items-center justify-between gap-1">
110
+ <span className="tabular">{n.toLocaleString("en-GB")}</span>
111
+ <DropdownMenu>
112
+ <DropdownMenuTrigger asChild>
113
+ <Button variant="ghost" size="icon-xs" aria-label={`Actions for ${label}`} className="size-5 text-ink-3 hover:text-ink data-[state=open]:bg-hover data-[state=open]:text-ink">
114
+ <EllipsisVertical />
115
+ </Button>
116
+ </DropdownMenuTrigger>
117
+ <DropdownMenuContent align="start" className="w-52">{children}</DropdownMenuContent>
118
+ </DropdownMenu>
119
+ </span>
120
+ </TableCell>
121
+ )
122
+ }
123
+
124
+ /* ---------------------------------------------------------------- filter row */
125
+
126
+ export type TableFilterColumn = { id: string; label: string; align?: "left" | "right" } | null
127
+
128
+ /** Goes in TableHeader under the header row. Pass null for a column with no filter (selection, row number, actions). */
129
+ export function TableFilterRow({
130
+ columns,
131
+ values,
132
+ onChange,
133
+ className,
134
+ }: {
135
+ columns: TableFilterColumn[]
136
+ values: Record<string, string>
137
+ onChange: (columnId: string, value: string) => void
138
+ className?: string
139
+ }) {
140
+ return (
141
+ <TableRow data-slot="table-filter-row" className={cn("bg-surface hover:bg-surface", className)}>
142
+ {columns.map((c, i) => (
143
+ <TableCell key={c?.id ?? `none-${i}`} className="px-1 group-data-[size=dense]/table:px-1">
144
+ {c && (
145
+ <span className="relative block">
146
+ <Search aria-hidden="true" className="pointer-events-none absolute top-1/2 left-1.5 size-3 -translate-y-1/2 text-ink-3" />
147
+ <input
148
+ type="search"
149
+ value={values[c.id] ?? ""}
150
+ onChange={(e) => onChange(c.id, e.target.value)}
151
+ onKeyDown={(e) => { if (e.key === "Escape" && values[c.id]) { e.preventDefault(); e.stopPropagation(); onChange(c.id, "") } }}
152
+ placeholder="Search"
153
+ aria-label={`Filter ${c.label}`}
154
+ className={cn(
155
+ "h-7 w-full min-w-0 rounded-xs border border-input bg-background pr-2 pl-5.5 text-sm text-ink outline-none placeholder:text-ink-3 hover:border-input-hover focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/50 group-data-[size=dense]/table:h-5.5 [&::-webkit-search-cancel-button]:hidden",
156
+ c.align === "right" && "text-right",
157
+ values[c.id] && "border-primary"
158
+ )}
159
+ />
160
+ </span>
161
+ )}
162
+ </TableCell>
163
+ ))}
164
+ </TableRow>
165
+ )
166
+ }
167
+
168
+ /* ---------------------------------------------------------------- in-cell action */
169
+
170
+ /**
171
+ * A quiet button inside a cell, such as Open or Share, revealed while the row is hovered or has keyboard focus and
172
+ * always shown on touch screens. aria-label names the row ("Open Magnet preparation"). Put the same action in the
173
+ * row's ⋮ menu: an in-cell button is a shortcut, never the only way.
174
+ */
175
+ export function TableCellAction({ className, ...props }: React.ComponentProps<typeof Button> & { "aria-label": string }) {
176
+ return (
177
+ <Button
178
+ data-slot="table-cell-action"
179
+ variant="outline"
180
+ size="xs"
181
+ className={cn(
182
+ "h-5 shrink-0 px-control opacity-0 transition-opacity duration-(--duration-fast) ease-(--ease-out) focus-visible:opacity-100 pointer-coarse:opacity-100 in-[tr:hover]:opacity-100 in-[tr:focus-within]:opacity-100 in-data-[focused=true]:opacity-100",
183
+ className
184
+ )}
185
+ {...props}
186
+ />
187
+ )
188
+ }
189
+
190
+ /* ---------------------------------------------------------------- small cell contents */
191
+
192
+ /** "n/a" for a value that doesn't apply to this row, read as "not applicable". Not the same as empty (a dash). */
193
+ export function TableNotApplicable({ className }: { className?: string }) {
194
+ return <span aria-label="not applicable" className={cn("text-sm text-ink-3", className)}>n/a</span>
195
+ }
196
+
197
+ /** Two linked items, such as a process and what it makes: "Magnet preparation → NdFeB magnet powder". */
198
+ export function TableLinkedItems({ from, to, className }: { from: React.ReactNode; to: React.ReactNode; className?: string }) {
199
+ return (
200
+ <span className={cn("flex min-w-0 items-center gap-control", className)}>
201
+ <span className="min-w-0 truncate">{from}</span>
202
+ <ArrowRight className="size-3 shrink-0 text-ink-3" aria-hidden="true" />
203
+ <span className="sr-only">to</span>
204
+ <span className="min-w-0 truncate text-ink-2">{to}</span>
205
+ </span>
206
+ )
207
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Generated from shadcn/ui new-york-v4 "dialog" by scripts/tailor-shadcn.mjs. Do not edit by hand.
3
+ * TwoUX changes: shared changes only (imports, role tokens for light and dark, type scale).
4
+ */
5
+ "use client"
6
+
7
+ import * as React from "react"
8
+ import { cn } from "../lib/cn"
9
+ import { XIcon } from "lucide-react"
10
+ import { Dialog as DialogPrimitive } from "radix-ui"
11
+
12
+ import { Button } from "./button"
13
+
14
+ function Dialog({
15
+ ...props
16
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
17
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
18
+ }
19
+
20
+ function DialogTrigger({
21
+ ...props
22
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
23
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
24
+ }
25
+
26
+ function DialogPortal({
27
+ ...props
28
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
29
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
30
+ }
31
+
32
+ function DialogClose({
33
+ ...props
34
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
35
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
36
+ }
37
+
38
+ function DialogOverlay({
39
+ className,
40
+ ...props
41
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
42
+ return (
43
+ <DialogPrimitive.Overlay
44
+ data-slot="dialog-overlay"
45
+ className={cn(
46
+ "fixed inset-0 z-50 bg-scrim data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ function DialogContent({
55
+ className,
56
+ children,
57
+ showCloseButton = true,
58
+ ...props
59
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
60
+ showCloseButton?: boolean
61
+ }) {
62
+ return (
63
+ <DialogPortal data-slot="dialog-portal">
64
+ <DialogOverlay />
65
+ <DialogPrimitive.Content
66
+ data-slot="dialog-content"
67
+ className={cn(
68
+ "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-md border border-line bg-background p-5 shadow-pop duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
69
+ className
70
+ )}
71
+ {...props}
72
+ >
73
+ {children}
74
+ {showCloseButton && (
75
+ <DialogPrimitive.Close
76
+ data-slot="dialog-close"
77
+ className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
78
+ >
79
+ <XIcon />
80
+ <span className="sr-only">Close</span>
81
+ </DialogPrimitive.Close>
82
+ )}
83
+ </DialogPrimitive.Content>
84
+ </DialogPortal>
85
+ )
86
+ }
87
+
88
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
89
+ return (
90
+ <div
91
+ data-slot="dialog-header"
92
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
93
+ {...props}
94
+ />
95
+ )
96
+ }
97
+
98
+ function DialogFooter({
99
+ className,
100
+ showCloseButton = false,
101
+ children,
102
+ ...props
103
+ }: React.ComponentProps<"div"> & {
104
+ showCloseButton?: boolean
105
+ }) {
106
+ return (
107
+ <div
108
+ data-slot="dialog-footer"
109
+ className={cn(
110
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
111
+ className
112
+ )}
113
+ {...props}
114
+ >
115
+ {children}
116
+ {showCloseButton && (
117
+ <DialogPrimitive.Close asChild>
118
+ <Button variant="outline">Close</Button>
119
+ </DialogPrimitive.Close>
120
+ )}
121
+ </div>
122
+ )
123
+ }
124
+
125
+ function DialogTitle({
126
+ className,
127
+ ...props
128
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
129
+ return (
130
+ <DialogPrimitive.Title
131
+ data-slot="dialog-title"
132
+ className={cn("text-lg leading-none font-semibold", className)}
133
+ {...props}
134
+ />
135
+ )
136
+ }
137
+
138
+ function DialogDescription({
139
+ className,
140
+ ...props
141
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
142
+ return (
143
+ <DialogPrimitive.Description
144
+ data-slot="dialog-description"
145
+ className={cn("text-base text-muted-foreground", className)}
146
+ {...props}
147
+ />
148
+ )
149
+ }
150
+
151
+ export {
152
+ Dialog,
153
+ DialogClose,
154
+ DialogContent,
155
+ DialogDescription,
156
+ DialogFooter,
157
+ DialogHeader,
158
+ DialogOverlay,
159
+ DialogPortal,
160
+ DialogTitle,
161
+ DialogTrigger,
162
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Generated from shadcn/ui new-york-v4 "dropdown-menu" by scripts/tailor-shadcn.mjs. Do not edit by hand.
3
+ * TwoUX changes:
4
+ * - Shortcut slot holds keys; no letter-spacing so Kbd groups read cleanly
5
+ * - Icons stay smaller than the 13px label and muted
6
+ * - Check and radio marks sit at the end of the item, so every label in a menu starts at the same edge
7
+ * - Group headings are smaller and muted, so they read as headings, not as items people can choose
8
+ */
9
+ "use client"
10
+
11
+ import * as React from "react"
12
+ import { cn } from "../lib/cn"
13
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
14
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
15
+
16
+ function DropdownMenu({
17
+ ...props
18
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
19
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
20
+ }
21
+
22
+ function DropdownMenuPortal({
23
+ ...props
24
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
25
+ return (
26
+ <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
27
+ )
28
+ }
29
+
30
+ function DropdownMenuTrigger({
31
+ ...props
32
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
33
+ return (
34
+ <DropdownMenuPrimitive.Trigger
35
+ data-slot="dropdown-menu-trigger"
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function DropdownMenuContent({
42
+ className,
43
+ sideOffset = 4,
44
+ ...props
45
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
46
+ return (
47
+ <DropdownMenuPrimitive.Portal>
48
+ <DropdownMenuPrimitive.Content
49
+ data-slot="dropdown-menu-content"
50
+ sideOffset={sideOffset}
51
+ className={cn(
52
+ "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
53
+ className
54
+ )}
55
+ {...props}
56
+ />
57
+ </DropdownMenuPrimitive.Portal>
58
+ )
59
+ }
60
+
61
+ function DropdownMenuGroup({
62
+ ...props
63
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
64
+ return (
65
+ <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
66
+ )
67
+ }
68
+
69
+ function DropdownMenuItem({
70
+ className,
71
+ inset,
72
+ variant = "default",
73
+ ...props
74
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
75
+ inset?: boolean
76
+ variant?: "default" | "destructive"
77
+ }) {
78
+ return (
79
+ <DropdownMenuPrimitive.Item
80
+ data-slot="dropdown-menu-item"
81
+ data-inset={inset}
82
+ data-variant={variant}
83
+ className={cn(
84
+ "relative flex cursor-default items-center gap-2 rounded-xs px-2 py-1.5 text-base outline-hidden select-none transition-colors duration-(--duration-fast) ease-(--ease-out) focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
85
+ className
86
+ )}
87
+ {...props}
88
+ />
89
+ )
90
+ }
91
+
92
+ function DropdownMenuCheckboxItem({
93
+ className,
94
+ children,
95
+ checked,
96
+ ...props
97
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
98
+ return (
99
+ <DropdownMenuPrimitive.CheckboxItem
100
+ data-slot="dropdown-menu-checkbox-item"
101
+ className={cn(
102
+ "relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-8 pl-2 text-base outline-hidden select-none transition-colors duration-(--duration-fast) ease-(--ease-out) focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-muted-foreground",
103
+ className
104
+ )}
105
+ checked={checked}
106
+ {...props}
107
+ >
108
+ <span className="pointer-events-none absolute right-2 flex size-3.5 items-center justify-center">
109
+ <DropdownMenuPrimitive.ItemIndicator>
110
+ <CheckIcon className="size-4" />
111
+ </DropdownMenuPrimitive.ItemIndicator>
112
+ </span>
113
+ {children}
114
+ </DropdownMenuPrimitive.CheckboxItem>
115
+ )
116
+ }
117
+
118
+ function DropdownMenuRadioGroup({
119
+ ...props
120
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
121
+ return (
122
+ <DropdownMenuPrimitive.RadioGroup
123
+ data-slot="dropdown-menu-radio-group"
124
+ {...props}
125
+ />
126
+ )
127
+ }
128
+
129
+ function DropdownMenuRadioItem({
130
+ className,
131
+ children,
132
+ ...props
133
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
134
+ return (
135
+ <DropdownMenuPrimitive.RadioItem
136
+ data-slot="dropdown-menu-radio-item"
137
+ className={cn(
138
+ "relative flex cursor-default items-center gap-2 rounded-xs py-1.5 pr-8 pl-2 text-base outline-hidden select-none transition-colors duration-(--duration-fast) ease-(--ease-out) focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-muted-foreground",
139
+ className
140
+ )}
141
+ {...props}
142
+ >
143
+ <span className="pointer-events-none absolute right-2 flex size-3.5 items-center justify-center">
144
+ <DropdownMenuPrimitive.ItemIndicator>
145
+ <CircleIcon className="size-2 fill-current" />
146
+ </DropdownMenuPrimitive.ItemIndicator>
147
+ </span>
148
+ {children}
149
+ </DropdownMenuPrimitive.RadioItem>
150
+ )
151
+ }
152
+
153
+ function DropdownMenuLabel({
154
+ className,
155
+ inset,
156
+ ...props
157
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
158
+ inset?: boolean
159
+ }) {
160
+ return (
161
+ <DropdownMenuPrimitive.Label
162
+ data-slot="dropdown-menu-label"
163
+ data-inset={inset}
164
+ className={cn(
165
+ "px-2 pt-2 pb-1 text-sm font-medium text-ink-3 data-[inset]:pl-8",
166
+ className
167
+ )}
168
+ {...props}
169
+ />
170
+ )
171
+ }
172
+
173
+ function DropdownMenuSeparator({
174
+ className,
175
+ ...props
176
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
177
+ return (
178
+ <DropdownMenuPrimitive.Separator
179
+ data-slot="dropdown-menu-separator"
180
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
181
+ {...props}
182
+ />
183
+ )
184
+ }
185
+
186
+ function DropdownMenuShortcut({
187
+ className,
188
+ ...props
189
+ }: React.ComponentProps<"span">) {
190
+ return (
191
+ <span
192
+ data-slot="dropdown-menu-shortcut"
193
+ className={cn(
194
+ "ml-auto flex items-center gap-0.5 text-sm text-muted-foreground",
195
+ className
196
+ )}
197
+ {...props}
198
+ />
199
+ )
200
+ }
201
+
202
+ function DropdownMenuSub({
203
+ ...props
204
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
205
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
206
+ }
207
+
208
+ function DropdownMenuSubTrigger({
209
+ className,
210
+ inset,
211
+ children,
212
+ ...props
213
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
214
+ inset?: boolean
215
+ }) {
216
+ return (
217
+ <DropdownMenuPrimitive.SubTrigger
218
+ data-slot="dropdown-menu-sub-trigger"
219
+ data-inset={inset}
220
+ className={cn(
221
+ "flex cursor-default items-center gap-2 rounded-xs px-2 py-1.5 text-base outline-hidden select-none transition-colors duration-(--duration-fast) ease-(--ease-out) focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground",
222
+ className
223
+ )}
224
+ {...props}
225
+ >
226
+ {children}
227
+ <ChevronRightIcon className="ml-auto size-4" />
228
+ </DropdownMenuPrimitive.SubTrigger>
229
+ )
230
+ }
231
+
232
+ function DropdownMenuSubContent({
233
+ className,
234
+ ...props
235
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
236
+ return (
237
+ <DropdownMenuPrimitive.SubContent
238
+ data-slot="dropdown-menu-sub-content"
239
+ className={cn(
240
+ "z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
241
+ className
242
+ )}
243
+ {...props}
244
+ />
245
+ )
246
+ }
247
+
248
+ export {
249
+ DropdownMenu,
250
+ DropdownMenuPortal,
251
+ DropdownMenuTrigger,
252
+ DropdownMenuContent,
253
+ DropdownMenuGroup,
254
+ DropdownMenuLabel,
255
+ DropdownMenuItem,
256
+ DropdownMenuCheckboxItem,
257
+ DropdownMenuRadioGroup,
258
+ DropdownMenuRadioItem,
259
+ DropdownMenuSeparator,
260
+ DropdownMenuShortcut,
261
+ DropdownMenuSub,
262
+ DropdownMenuSubTrigger,
263
+ DropdownMenuSubContent,
264
+ }