@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.
- package/LICENSE.md +61 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/NOTICE.md +64 -0
- package/README.md +211 -2
- package/SHADCN-DIFF.md +228 -0
- package/agent/AGENTS.md +358 -0
- package/agent/docs-md/accessibility.md +49 -0
- package/agent/docs-md/action-catalogue.md +81 -0
- package/agent/docs-md/actions.md +74 -0
- package/agent/docs-md/agent-activity-log.md +70 -0
- package/agent/docs-md/agent-permissions.md +82 -0
- package/agent/docs-md/agent-run.md +90 -0
- package/agent/docs-md/agents-on-behalf.md +74 -0
- package/agent/docs-md/ai-column.md +85 -0
- package/agent/docs-md/ai-origin-label.md +70 -0
- package/agent/docs-md/announcing-features.md +73 -0
- package/agent/docs-md/answer-actions.md +81 -0
- package/agent/docs-md/app-shell.md +114 -0
- package/agent/docs-md/artifact-card.md +89 -0
- package/agent/docs-md/ask-on-screen.md +72 -0
- package/agent/docs-md/assistant-page.md +100 -0
- package/agent/docs-md/assistant-panel.md +142 -0
- package/agent/docs-md/avatar.md +82 -0
- package/agent/docs-md/banner.md +106 -0
- package/agent/docs-md/board.md +163 -0
- package/agent/docs-md/breadcrumb.md +89 -0
- package/agent/docs-md/breakdown-table.md +97 -0
- package/agent/docs-md/bulk-action-bar.md +112 -0
- package/agent/docs-md/button.md +293 -0
- package/agent/docs-md/card.md +115 -0
- package/agent/docs-md/change-list.md +99 -0
- package/agent/docs-md/change-request.md +166 -0
- package/agent/docs-md/chart-area.md +87 -0
- package/agent/docs-md/chart-bar.md +100 -0
- package/agent/docs-md/chart-box-plot.md +88 -0
- package/agent/docs-md/chart-bullet.md +89 -0
- package/agent/docs-md/chart-candlestick.md +112 -0
- package/agent/docs-md/chart-card-line.md +120 -0
- package/agent/docs-md/chart-donut.md +90 -0
- package/agent/docs-md/chart-drill-down.md +72 -0
- package/agent/docs-md/chart-funnel.md +71 -0
- package/agent/docs-md/chart-heatmap.md +76 -0
- package/agent/docs-md/chart-histogram.md +81 -0
- package/agent/docs-md/chart-map.md +165 -0
- package/agent/docs-md/chart-mixed.md +88 -0
- package/agent/docs-md/chart-price.md +92 -0
- package/agent/docs-md/chart-radial-bars.md +109 -0
- package/agent/docs-md/chart-radial-lines.md +111 -0
- package/agent/docs-md/chart-sankey.md +104 -0
- package/agent/docs-md/chart-scatter.md +87 -0
- package/agent/docs-md/chart-share-bar.md +89 -0
- package/agent/docs-md/chart-treemap.md +98 -0
- package/agent/docs-md/chart-waterfall.md +93 -0
- package/agent/docs-md/checkbox.md +131 -0
- package/agent/docs-md/choosing-selection-control.md +71 -0
- package/agent/docs-md/citations-sources.md +79 -0
- package/agent/docs-md/citing-sources.md +63 -0
- package/agent/docs-md/clarify-before-acting.md +69 -0
- package/agent/docs-md/clarifying-question.md +92 -0
- package/agent/docs-md/code-block.md +174 -0
- package/agent/docs-md/colours.md +87 -0
- package/agent/docs-md/columns-menu.md +140 -0
- package/agent/docs-md/combobox.md +161 -0
- package/agent/docs-md/command-palette.md +126 -0
- package/agent/docs-md/confidence-note.md +87 -0
- package/agent/docs-md/configurable-dashboard.md +76 -0
- package/agent/docs-md/confirmation-dialog.md +93 -0
- package/agent/docs-md/connected-tools.md +80 -0
- package/agent/docs-md/connector-picker.md +127 -0
- package/agent/docs-md/consistency.md +53 -0
- package/agent/docs-md/content-density.md +61 -0
- package/agent/docs-md/conversation-history.md +78 -0
- package/agent/docs-md/create-record.md +80 -0
- package/agent/docs-md/csv-import.md +88 -0
- package/agent/docs-md/dashboard-items.md +78 -0
- package/agent/docs-md/data-export.md +61 -0
- package/agent/docs-md/data-grid.md +137 -0
- package/agent/docs-md/data-vis-colours.md +76 -0
- package/agent/docs-md/data-visualisation.md +76 -0
- package/agent/docs-md/date-picker.md +178 -0
- package/agent/docs-md/delete-record.md +75 -0
- package/agent/docs-md/dense-table.md +495 -0
- package/agent/docs-md/design-tokens.md +53 -0
- package/agent/docs-md/disabled-read-only.md +75 -0
- package/agent/docs-md/drag-and-drop.md +74 -0
- package/agent/docs-md/dropdown-menu.md +137 -0
- package/agent/docs-md/edit-record.md +73 -0
- package/agent/docs-md/empty-loading-error.md +78 -0
- package/agent/docs-md/empty-state.md +128 -0
- package/agent/docs-md/error-messages.md +75 -0
- package/agent/docs-md/error-state.md +123 -0
- package/agent/docs-md/export-menu.md +86 -0
- package/agent/docs-md/feedback-prompt.md +88 -0
- package/agent/docs-md/field-mapping.md +78 -0
- package/agent/docs-md/file-drop.md +110 -0
- package/agent/docs-md/filter-bar.md +138 -0
- package/agent/docs-md/filter-panel.md +103 -0
- package/agent/docs-md/filtering-search.md +79 -0
- package/agent/docs-md/follow-ups-starters.md +79 -0
- package/agent/docs-md/form-validation.md +95 -0
- package/agent/docs-md/generated-columns.md +65 -0
- package/agent/docs-md/generated-drafts-files.md +71 -0
- package/agent/docs-md/grouped-list.md +89 -0
- package/agent/docs-md/guide-cue.md +170 -0
- package/agent/docs-md/help-panel.md +103 -0
- package/agent/docs-md/help-system.md +74 -0
- package/agent/docs-md/iconography.md +49 -0
- package/agent/docs-md/inbox.md +108 -0
- package/agent/docs-md/inline-editing-validation.md +80 -0
- package/agent/docs-md/inline-input-in-answer.md +70 -0
- package/agent/docs-md/items-palette.md +116 -0
- package/agent/docs-md/keyboard-model.md +47 -0
- package/agent/docs-md/keyboard-navigation.md +68 -0
- package/agent/docs-md/keyboard-shortcut-hint.md +87 -0
- package/agent/docs-md/keyboard-shortcut-sheet.md +90 -0
- package/agent/docs-md/labelling-ai-output.md +66 -0
- package/agent/docs-md/layout.md +47 -0
- package/agent/docs-md/list-table.md +254 -0
- package/agent/docs-md/loading-skeleton.md +91 -0
- package/agent/docs-md/managing-agents.md +71 -0
- package/agent/docs-md/message-composer.md +105 -0
- package/agent/docs-md/migrating.md +49 -0
- package/agent/docs-md/motion.md +93 -0
- package/agent/docs-md/navigation.md +76 -0
- package/agent/docs-md/notification-channels.md +65 -0
- package/agent/docs-md/notifications-decision.md +100 -0
- package/agent/docs-md/onboarding-step.md +114 -0
- package/agent/docs-md/order-book.md +108 -0
- package/agent/docs-md/page-header.md +96 -0
- package/agent/docs-md/pagination.md +88 -0
- package/agent/docs-md/pane-layout.md +105 -0
- package/agent/docs-md/period-comparison.md +66 -0
- package/agent/docs-md/plan-card.md +120 -0
- package/agent/docs-md/popover-rules.md +88 -0
- package/agent/docs-md/prompt-editor.md +90 -0
- package/agent/docs-md/propose-apply.md +65 -0
- package/agent/docs-md/provider-logo.md +97 -0
- package/agent/docs-md/radio.md +83 -0
- package/agent/docs-md/ranked-list.md +102 -0
- package/agent/docs-md/reasoning-progress.md +72 -0
- package/agent/docs-md/record-detail.md +71 -0
- package/agent/docs-md/regenerating-answers.md +70 -0
- package/agent/docs-md/run-status-bar.md +94 -0
- package/agent/docs-md/saved-tasks-schedules.md +63 -0
- package/agent/docs-md/saved-views.md +149 -0
- package/agent/docs-md/saving-views.md +68 -0
- package/agent/docs-md/scheduler.md +258 -0
- package/agent/docs-md/secondary-panels.md +72 -0
- package/agent/docs-md/segmented-control.md +88 -0
- package/agent/docs-md/selection-bulk-actions.md +70 -0
- package/agent/docs-md/separator.md +75 -0
- package/agent/docs-md/setup-checklist.md +92 -0
- package/agent/docs-md/shape-elevation.md +55 -0
- package/agent/docs-md/sheet-grid.md +211 -0
- package/agent/docs-md/side-panel.md +134 -0
- package/agent/docs-md/sign-in.md +114 -0
- package/agent/docs-md/spacing.md +55 -0
- package/agent/docs-md/split-button.md +100 -0
- package/agent/docs-md/stat-compare.md +82 -0
- package/agent/docs-md/static-dashboard.md +75 -0
- package/agent/docs-md/status-badge.md +140 -0
- package/agent/docs-md/step-card.md +75 -0
- package/agent/docs-md/stepper.md +92 -0
- package/agent/docs-md/suggested-changes.md +91 -0
- package/agent/docs-md/switch.md +78 -0
- package/agent/docs-md/table-settings.md +81 -0
- package/agent/docs-md/tabs.md +85 -0
- package/agent/docs-md/tag-input.md +108 -0
- package/agent/docs-md/tag.md +79 -0
- package/agent/docs-md/task-builder.md +87 -0
- package/agent/docs-md/team-instructions.md +72 -0
- package/agent/docs-md/templates-gallery.md +85 -0
- package/agent/docs-md/text-input.md +160 -0
- package/agent/docs-md/thinking-steps.md +89 -0
- package/agent/docs-md/timeline.md +189 -0
- package/agent/docs-md/timestamp.md +84 -0
- package/agent/docs-md/timestamps.md +70 -0
- package/agent/docs-md/toast.md +117 -0
- package/agent/docs-md/tooltip.md +90 -0
- package/agent/docs-md/typography.md +62 -0
- package/agent/docs-md/undo-destructive.md +66 -0
- package/agent/docs-md/unsaved-changes.md +71 -0
- package/agent/docs-md/usage-meter.md +80 -0
- package/agent/docs-md/user-feedback.md +71 -0
- package/agent/docs-md/voice-and-tone.md +60 -0
- package/agent/docs-md/why-choice.md +93 -0
- package/agent/docs-md/workspace-onboarding.md +74 -0
- package/agent/docs-md/writing.md +48 -0
- package/agent/llms-full.txt +18369 -0
- package/agent/llms.txt +198 -0
- package/agent/skills/twoux-audit/SKILL.md +49 -0
- package/agent/skills/twoux-build/SKILL.md +39 -0
- package/agent/skills/twoux-design/SKILL.md +54 -0
- package/agent/skills/twoux-write/SKILL.md +32 -0
- package/bin/check.mjs +123 -0
- package/bin/docs.mjs +24 -0
- package/bin/guard.mjs +27 -0
- package/bin/init.mjs +113 -0
- package/bin/knowledge.mjs +87 -0
- package/bin/mcp.mjs +116 -0
- package/bin/theme.mjs +168 -0
- package/bin/twoux.mjs +25 -0
- package/package.json +80 -3
- package/registry/jobs.json +1028 -0
- package/registry/palette.json +770 -0
- package/registry/rules.json +913 -0
- package/src/components/agent-review.tsx +364 -0
- package/src/components/ai-basics.tsx +212 -0
- package/src/components/ai-work.tsx +304 -0
- package/src/components/ai.tsx +227 -0
- package/src/components/alert-dialog.tsx +203 -0
- package/src/components/alert.tsx +86 -0
- package/src/components/avatar.tsx +114 -0
- package/src/components/badge.tsx +59 -0
- package/src/components/board.tsx +397 -0
- package/src/components/breadcrumb.tsx +114 -0
- package/src/components/button-group.tsx +90 -0
- package/src/components/button.tsx +109 -0
- package/src/components/calendar.tsx +247 -0
- package/src/components/card.tsx +102 -0
- package/src/components/chart-map.tsx +336 -0
- package/src/components/charts-more.tsx +1331 -0
- package/src/components/charts-radial.tsx +328 -0
- package/src/components/charts.tsx +390 -0
- package/src/components/checkbox.tsx +40 -0
- package/src/components/code.tsx +139 -0
- package/src/components/collapsible.tsx +37 -0
- package/src/components/combobox.tsx +339 -0
- package/src/components/command-extras.tsx +11 -0
- package/src/components/command.tsx +196 -0
- package/src/components/data-grid.tsx +213 -0
- package/src/components/data-table.tsx +334 -0
- package/src/components/data-views.tsx +740 -0
- package/src/components/date-picker.tsx +686 -0
- package/src/components/dense-table.tsx +207 -0
- package/src/components/dialog.tsx +162 -0
- package/src/components/dropdown-menu.tsx +264 -0
- package/src/components/empty.tsx +114 -0
- package/src/components/feedback-prompt.tsx +80 -0
- package/src/components/field.tsx +254 -0
- package/src/components/fields.tsx +112 -0
- package/src/components/flow.tsx +840 -0
- package/src/components/guide-cue.tsx +273 -0
- package/src/components/inbox.tsx +113 -0
- package/src/components/input-group.tsx +176 -0
- package/src/components/input.tsx +26 -0
- package/src/components/kbd.tsx +34 -0
- package/src/components/label.tsx +27 -0
- package/src/components/labels.tsx +176 -0
- package/src/components/motion.tsx +136 -0
- package/src/components/onboarding.tsx +454 -0
- package/src/components/order-book.tsx +185 -0
- package/src/components/pane-layout.tsx +417 -0
- package/src/components/popover.tsx +96 -0
- package/src/components/provider-logos.data.ts +49 -0
- package/src/components/radio-group.tsx +52 -0
- package/src/components/scheduler.tsx +997 -0
- package/src/components/segmented-control.tsx +28 -0
- package/src/components/select.tsx +195 -0
- package/src/components/separator.tsx +31 -0
- package/src/components/sheet.tsx +148 -0
- package/src/components/shell.tsx +426 -0
- package/src/components/shortcut-sheet.tsx +83 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/sonner.tsx +107 -0
- package/src/components/spinner.tsx +13 -0
- package/src/components/split-button.tsx +58 -0
- package/src/components/stepper.tsx +78 -0
- package/src/components/switch.tsx +43 -0
- package/src/components/table.tsx +126 -0
- package/src/components/tabs.tsx +118 -0
- package/src/components/tag-input.tsx +253 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/theme.tsx +80 -0
- package/src/components/timeline.tsx +408 -0
- package/src/components/toggle-group.tsx +87 -0
- package/src/components/toggle.tsx +54 -0
- package/src/components/tooltip.tsx +65 -0
- package/src/components/workspace-actions.tsx +294 -0
- package/src/components/workspace-import.tsx +465 -0
- package/src/components/workspace.tsx +1884 -0
- package/src/index.ts +80 -0
- package/src/lib/active-indicator.tsx +53 -0
- package/src/lib/chart-kit.tsx +72 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/shortcuts.tsx +61 -0
- package/src/lib/workspace.tsx +10 -0
- package/src/shadcn/alert-dialog.tsx +196 -0
- package/src/shadcn/alert.tsx +65 -0
- package/src/shadcn/avatar.tsx +108 -0
- package/src/shadcn/badge.tsx +47 -0
- package/src/shadcn/breadcrumb.tsx +108 -0
- package/src/shadcn/button-group.tsx +83 -0
- package/src/shadcn/button.tsx +63 -0
- package/src/shadcn/calendar.tsx +220 -0
- package/src/shadcn/card.tsx +91 -0
- package/src/shadcn/checkbox.tsx +31 -0
- package/src/shadcn/collapsible.tsx +33 -0
- package/src/shadcn/command.tsx +184 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/dropdown-menu.tsx +256 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +248 -0
- package/src/shadcn/input-group.tsx +170 -0
- package/src/shadcn/input.tsx +20 -0
- package/src/shadcn/kbd.tsx +28 -0
- package/src/shadcn/label.tsx +23 -0
- package/src/shadcn/popover.tsx +88 -0
- package/src/shadcn/radio-group.tsx +44 -0
- package/src/shadcn/select.tsx +189 -0
- package/src/shadcn/separator.tsx +27 -0
- package/src/shadcn/sheet.tsx +142 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/sonner.tsx +40 -0
- package/src/shadcn/switch.tsx +34 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +90 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +83 -0
- package/src/shadcn/toggle.tsx +46 -0
- package/src/shadcn/tooltip.tsx +56 -0
- package/src/styles/palette.css +127 -0
- package/src/styles/theme.css +35 -0
- package/src/styles/twoux.css +236 -0
- package/theme.json +14 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# List table
|
|
2
|
+
|
|
3
|
+
A read-mostly table for scanning and triaging records such as listings or candidates, with a sticky header, sortable columns, J and K row focus and checkbox selection.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Data · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- People need to find records that match criteria, compare them, and act on one or several. These are the core table tasks.
|
|
10
|
+
- Every record has the same attributes and the set will keep growing, such as all live listings or an applicant inbox.
|
|
11
|
+
- People will scan, sort and analyse large volumes of rows rather than read each one.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- To edit cells, enrich with AI or total columns. Use the TwoUX Data grid, because a table is not an interactive widget and its cells are not focusable.
|
|
16
|
+
- For a short list of items under shared headings, such as candidates by stage. Use the TwoUX Grouped list.
|
|
17
|
+
- For page layout. Tables are only for structured data.
|
|
18
|
+
- For one figure that matters. Use the TwoUX Stat card with comparison instead of a one-row table.
|
|
19
|
+
|
|
20
|
+
## Structure
|
|
21
|
+
|
|
22
|
+
1. Table header (title, item count, actions)
|
|
23
|
+
2. Filter bar
|
|
24
|
+
3. Sticky column header row
|
|
25
|
+
4. Selection checkbox column
|
|
26
|
+
5. Identifying column (Role)
|
|
27
|
+
6. Data columns
|
|
28
|
+
7. Row focus indicator
|
|
29
|
+
8. Footer (count, selection count, pagination)
|
|
30
|
+
|
|
31
|
+
## Guidelines
|
|
32
|
+
|
|
33
|
+
- Keep the column header row visible on scroll when a table has more than 30 items per page, more than five columns or sortable columns.
|
|
34
|
+
- Put a human-readable identifier (the Role) first and order the remaining columns by importance.
|
|
35
|
+
- Only the sorted column shows its arrow; the others reveal it on hover. Expose the current sort with aria-sort on that header only.
|
|
36
|
+
- J and K (or the arrow keys) move row focus, X toggles selection, Enter opens the row in the side panel, Esc clears. Show each shortcut on its button so experts learn it.
|
|
37
|
+
- The header checkbox selects only the rows on the current page, and the footer reads "2 of 24 selected".
|
|
38
|
+
- Open a row in a non-modal side panel so the table stays visible; modals hide neighbouring rows.
|
|
39
|
+
- Never add cell editing or a totals row to a list table; that is the Data grid's job.
|
|
40
|
+
- For spreadsheet-like data people scan many rows of, such as an inventory, a ledger, an import or a mapping, use size="dense" (28px rows) and the Dense table parts. Keep the default 36px rows for everything else. Dense is a variant for that data, never a setting people switch.
|
|
41
|
+
|
|
42
|
+
## Content
|
|
43
|
+
|
|
44
|
+
- Left-align text and right-align numbers with a consistent number of decimals, in tabular figures.
|
|
45
|
+
- Write column labels as short, plain nouns.
|
|
46
|
+
- Show the total next to the title, and the selected count before the total when rows are selected. Hide the count while loading.
|
|
47
|
+
- Show a dash for an empty value and give it hidden text for screen readers, never a blank cell.
|
|
48
|
+
- Status is a bordered pill with a word, never colour alone.
|
|
49
|
+
|
|
50
|
+
## Accessibility
|
|
51
|
+
|
|
52
|
+
- 1.3.1 Info and Relationships: mark up header cells with scope="col" (and scope="row" for the Role cell) so each value is tied to its column.
|
|
53
|
+
- APG table pattern: use table semantics with an accessible name from a caption or aria-label, and aria-sort on the sorted header only.
|
|
54
|
+
- 2.1.4 Character Key Shortcuts: J, K, X, E and / are single-key shortcuts, so TwoUX lets people turn them off in settings.
|
|
55
|
+
- 2.4.7 Focus Visible: the focused row shows a visible indicator, and highlighted rows are never the only signal of selection or focus.
|
|
56
|
+
- 2.4.11 Focus Not Obscured: the sticky header must not fully cover the focused row; use scroll padding.
|
|
57
|
+
- Give repeated row actions unique names, for example "Open Junior Product Designer" rather than "Open".
|
|
58
|
+
|
|
59
|
+
## Keyboard
|
|
60
|
+
|
|
61
|
+
| Keys | Action |
|
|
62
|
+
|---|---|
|
|
63
|
+
| J / ↓ | Move row focus down |
|
|
64
|
+
| K / ↑ | Move row focus up |
|
|
65
|
+
| ⇧J / ⇧K | Extend selection down or up |
|
|
66
|
+
| X | Select or deselect the focused row |
|
|
67
|
+
| Enter | Open the focused row in the side panel |
|
|
68
|
+
| Esc | Close the side panel, or clear the selection |
|
|
69
|
+
| / | Focus search |
|
|
70
|
+
| F | Open the filter field list |
|
|
71
|
+
| ⇧P | Switch the side panel between push and overlay |
|
|
72
|
+
|
|
73
|
+
## Examples
|
|
74
|
+
|
|
75
|
+
### listings
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import {
|
|
79
|
+
AlertDialog,
|
|
80
|
+
AlertDialogAction,
|
|
81
|
+
AlertDialogCancel,
|
|
82
|
+
AlertDialogContent,
|
|
83
|
+
AlertDialogDescription,
|
|
84
|
+
AlertDialogFooter,
|
|
85
|
+
AlertDialogHeader,
|
|
86
|
+
AlertDialogTitle,
|
|
87
|
+
Badge,
|
|
88
|
+
BulkActionBar,
|
|
89
|
+
Button,
|
|
90
|
+
Checkbox,
|
|
91
|
+
DropdownMenu,
|
|
92
|
+
DropdownMenuContent,
|
|
93
|
+
DropdownMenuItem,
|
|
94
|
+
DropdownMenuSeparator,
|
|
95
|
+
DropdownMenuTrigger,
|
|
96
|
+
Table,
|
|
97
|
+
TableBody,
|
|
98
|
+
TableCell,
|
|
99
|
+
TableHead,
|
|
100
|
+
TableHeader,
|
|
101
|
+
TablePagination,
|
|
102
|
+
TableRow,
|
|
103
|
+
TableSortHead,
|
|
104
|
+
toast,
|
|
105
|
+
useRowKeyboard,
|
|
106
|
+
} from "@twoux/ui";
|
|
107
|
+
import { type ColumnDef, type RowSelectionState, type SortingState, flexRender, getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
|
|
108
|
+
import { CalendarPlus, CircleX, Copy, Ellipsis, Pencil, Trash2 } from "lucide-react";
|
|
109
|
+
import { useMemo, useState } from "react";
|
|
110
|
+
import { LISTINGS, type Listing, STATUS_VARIANT, formatDate } from "./data";
|
|
111
|
+
|
|
112
|
+
export const meta = { title: "Listings", description: "Click a header to sort; only the sorted column shows its arrow. Select rows to bring up the bulk bar. Focus the table and use J/K to move, X to select, Esc to clear.", order: 1 };
|
|
113
|
+
|
|
114
|
+
export default function Example() {
|
|
115
|
+
const [sorting, setSorting] = useState<SortingState>([{ id: "closes", desc: false }]);
|
|
116
|
+
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
|
117
|
+
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 8 });
|
|
118
|
+
const [confirm, setConfirm] = useState(false);
|
|
119
|
+
|
|
120
|
+
const columns = useMemo<ColumnDef<Listing>[]>(
|
|
121
|
+
() => [
|
|
122
|
+
{
|
|
123
|
+
id: "select",
|
|
124
|
+
header: ({ table }) => (
|
|
125
|
+
<Checkbox
|
|
126
|
+
aria-label="Select all on this page"
|
|
127
|
+
checked={table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && "indeterminate")}
|
|
128
|
+
onCheckedChange={(v) => table.toggleAllPageRowsSelected(!!v)}
|
|
129
|
+
/>
|
|
130
|
+
),
|
|
131
|
+
cell: ({ row }) => <Checkbox aria-label={`Select ${row.original.role}`} checked={row.getIsSelected()} onCheckedChange={(v) => row.toggleSelected(!!v)} />,
|
|
132
|
+
enableSorting: false,
|
|
133
|
+
},
|
|
134
|
+
{ accessorKey: "role", header: "Role", cell: ({ row }) => <span className="font-medium text-ink">{row.original.role}</span> },
|
|
135
|
+
{ accessorKey: "location", header: "Location", cell: ({ getValue }) => <span className="text-ink-2">{getValue<string>()}</span> },
|
|
136
|
+
{ accessorKey: "status", header: "Status", cell: ({ row }) => <Badge variant={STATUS_VARIANT[row.original.status]} dot>{row.original.status}</Badge> },
|
|
137
|
+
{ accessorKey: "applicants", header: "Applicants", meta: { align: "right" }, cell: ({ getValue }) => <span className="tabular block text-right">{getValue<number>()}</span> },
|
|
138
|
+
{ accessorKey: "closes", header: "Closes", cell: ({ getValue }) => <span className="tabular text-ink-2">{formatDate(getValue<string>())}</span> },
|
|
139
|
+
{ id: "actions", header: () => <span className="sr-only">Actions</span>, enableSorting: false, cell: ({ row, table }) => <RowMenu listing={row.original} disabled={table.getSelectedRowModel().rows.length > 0} /> },
|
|
140
|
+
],
|
|
141
|
+
[],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const table = useReactTable({
|
|
145
|
+
data: LISTINGS,
|
|
146
|
+
columns,
|
|
147
|
+
state: { sorting, rowSelection, pagination },
|
|
148
|
+
onSortingChange: setSorting,
|
|
149
|
+
onRowSelectionChange: setRowSelection,
|
|
150
|
+
onPaginationChange: (u) => {
|
|
151
|
+
setPagination(u);
|
|
152
|
+
setRowSelection({}); // changing page clears the selection
|
|
153
|
+
},
|
|
154
|
+
getRowId: (r) => r.id,
|
|
155
|
+
getCoreRowModel: getCoreRowModel(),
|
|
156
|
+
getSortedRowModel: getSortedRowModel(),
|
|
157
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const rows = table.getRowModel().rows;
|
|
161
|
+
const selected = table.getSelectedRowModel().rows.length;
|
|
162
|
+
const clear = () => setRowSelection({});
|
|
163
|
+
const keys = useRowKeyboard({ count: rows.length, onToggle: (i, v) => rows[i]?.toggleSelected(v), onOpen: (i) => toast(`Opened ${rows[i].original.role}`), onClear: clear, selected });
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className="w-full overflow-hidden rounded-sm border border-line bg-surface">
|
|
167
|
+
<BulkActionBar
|
|
168
|
+
count={selected}
|
|
169
|
+
noun={selected === 1 ? "listing" : "listings"}
|
|
170
|
+
onClear={clear}
|
|
171
|
+
actions={[
|
|
172
|
+
{ label: "Extend 30 days", icon: <CalendarPlus />, shortcut: "T", onSelect: () => { toast.success(`Extended ${selected} listing${selected === 1 ? "" : "s"} by 30 days`, { action: { label: "Undo", onClick: () => {} } }); clear(); } },
|
|
173
|
+
{ label: "Duplicate", icon: <Copy />, shortcut: "D", onSelect: () => { toast.success(`Duplicated ${selected} listing${selected === 1 ? "" : "s"}`); clear(); } },
|
|
174
|
+
{ label: "Close listings", icon: <CircleX />, destructive: true, onSelect: () => setConfirm(true) },
|
|
175
|
+
]}
|
|
176
|
+
/>
|
|
177
|
+
<Table aria-label="Listings">
|
|
178
|
+
<TableHeader>
|
|
179
|
+
{table.getHeaderGroups().map((hg) => (
|
|
180
|
+
<TableRow key={hg.id} className="hover:bg-transparent">
|
|
181
|
+
{hg.headers.map((h) => {
|
|
182
|
+
const right = (h.column.columnDef.meta as { align?: string } | undefined)?.align === "right";
|
|
183
|
+
if (!h.column.getCanSort()) return <TableHead key={h.id} className={h.id === "select" ? "w-9" : "w-10"}>{flexRender(h.column.columnDef.header, h.getContext())}</TableHead>;
|
|
184
|
+
return (
|
|
185
|
+
<TableSortHead key={h.id} sorted={h.column.getIsSorted()} onSort={() => h.column.toggleSorting(h.column.getIsSorted() === "asc")} align={right ? "right" : "left"}>
|
|
186
|
+
{flexRender(h.column.columnDef.header, h.getContext())}
|
|
187
|
+
</TableSortHead>
|
|
188
|
+
);
|
|
189
|
+
})}
|
|
190
|
+
</TableRow>
|
|
191
|
+
))}
|
|
192
|
+
</TableHeader>
|
|
193
|
+
<TableBody {...keys.bodyProps} aria-label="Listings, use J and K to move, X to select">
|
|
194
|
+
{rows.map((row, i) => (
|
|
195
|
+
<TableRow key={row.id} data-state={row.getIsSelected() ? "selected" : undefined} {...keys.rowProps(i)}>
|
|
196
|
+
{row.getVisibleCells().map((cell) => (
|
|
197
|
+
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
|
|
198
|
+
))}
|
|
199
|
+
</TableRow>
|
|
200
|
+
))}
|
|
201
|
+
</TableBody>
|
|
202
|
+
</Table>
|
|
203
|
+
<TablePagination
|
|
204
|
+
page={pagination.pageIndex}
|
|
205
|
+
pageCount={table.getPageCount()}
|
|
206
|
+
pageSize={pagination.pageSize}
|
|
207
|
+
total={LISTINGS.length}
|
|
208
|
+
selected={selected}
|
|
209
|
+
noun="listings"
|
|
210
|
+
onPageChange={(p) => table.setPageIndex(p)}
|
|
211
|
+
/>
|
|
212
|
+
<AlertDialog open={confirm} onOpenChange={setConfirm}>
|
|
213
|
+
<AlertDialogContent>
|
|
214
|
+
<AlertDialogHeader>
|
|
215
|
+
<AlertDialogTitle>Close {selected} listings?</AlertDialogTitle>
|
|
216
|
+
<AlertDialogDescription>They come off every job board today. Applicants and notes are kept.</AlertDialogDescription>
|
|
217
|
+
</AlertDialogHeader>
|
|
218
|
+
<AlertDialogFooter>
|
|
219
|
+
<AlertDialogCancel>Keep open</AlertDialogCancel>
|
|
220
|
+
<AlertDialogAction variant="destructive" onClick={() => { toast.success(`${selected} listing${selected === 1 ? "" : "s"} closed`); clear(); }}>Close listings</AlertDialogAction>
|
|
221
|
+
</AlertDialogFooter>
|
|
222
|
+
</AlertDialogContent>
|
|
223
|
+
</AlertDialog>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function RowMenu({ listing, disabled }: { listing: Listing; disabled: boolean }) {
|
|
229
|
+
return (
|
|
230
|
+
<DropdownMenu>
|
|
231
|
+
<DropdownMenuTrigger asChild>
|
|
232
|
+
<Button variant="ghost" size="icon-sm" aria-label={`Actions for ${listing.role}`} disabled={disabled}><Ellipsis /></Button>
|
|
233
|
+
</DropdownMenuTrigger>
|
|
234
|
+
<DropdownMenuContent align="end">
|
|
235
|
+
<DropdownMenuItem><Pencil />Edit</DropdownMenuItem>
|
|
236
|
+
<DropdownMenuItem><Copy />Duplicate</DropdownMenuItem>
|
|
237
|
+
<DropdownMenuSeparator />
|
|
238
|
+
<DropdownMenuItem variant="destructive"><Trash2 />Close listing</DropdownMenuItem>
|
|
239
|
+
</DropdownMenuContent>
|
|
240
|
+
</DropdownMenu>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Related
|
|
246
|
+
|
|
247
|
+
- Data grid
|
|
248
|
+
- Filter pills and filter bar
|
|
249
|
+
- Bulk action bar
|
|
250
|
+
- Pagination and counts
|
|
251
|
+
- Columns menu
|
|
252
|
+
- Saved views
|
|
253
|
+
- Grouped list
|
|
254
|
+
- Dense table
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Loading skeleton
|
|
2
|
+
|
|
3
|
+
Grey placeholder rows that keep the table or panel's shape while data loads.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: Feedback and overlays · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- First load of a table, list or panel whose layout is known.
|
|
10
|
+
- Loads that take roughly 1 to 10 seconds.
|
|
11
|
+
- Several parts of a view load at once and each can fill in as it arrives.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- Loads under 1 second. Show nothing and render the data.
|
|
16
|
+
- A single action in progress, such as Apply or Run. Use the button's inline spinner.
|
|
17
|
+
- Work over 10 seconds or with known progress, such as an agent run. Use a progress indicator with a count ("2 / 5").
|
|
18
|
+
- Popovers, menus, toasts or dialogs, whose content should already be loaded.
|
|
19
|
+
|
|
20
|
+
## Structure
|
|
21
|
+
|
|
22
|
+
1. Region header kept ("Loading listings…")
|
|
23
|
+
2. Skeleton rows at the real row height (36px)
|
|
24
|
+
3. Bars per column: checkbox, title, secondary, right-aligned figure
|
|
25
|
+
4. Shimmer animation (off with reduced motion)
|
|
26
|
+
|
|
27
|
+
## Guidelines
|
|
28
|
+
|
|
29
|
+
- Match the real layout: same row height, same column positions, varied bar widths so it reads as text, numbers on the right.
|
|
30
|
+
- Keep the header, filters and page actions live; only the data region shows the skeleton.
|
|
31
|
+
- Replace the skeleton as soon as data arrives, section by section where possible.
|
|
32
|
+
- Never combine a skeleton with a spinner in the same region.
|
|
33
|
+
- Shimmer runs at 1.4 s per cycle and stops when prefers-reduced-motion is set.
|
|
34
|
+
- If loading fails, swap to the Error state in the same space.
|
|
35
|
+
|
|
36
|
+
## Content
|
|
37
|
+
|
|
38
|
+
- The region header says what is loading, short, without articles: "Loading listings…".
|
|
39
|
+
- No other text inside the skeleton; it holds space, it does not explain.
|
|
40
|
+
- Never rely on a bare "Loading…" message alone for longer waits; the skeleton shows where the data will appear, and long runs show a count.
|
|
41
|
+
|
|
42
|
+
## Accessibility
|
|
43
|
+
|
|
44
|
+
- Mark the region aria-busy while loading and give it a label ("Loading listings"); skeleton bars are hidden from assistive technology.
|
|
45
|
+
- Do not announce every load; announce only the result, such as the row count, through a polite live region (4.1.3).
|
|
46
|
+
- Shimmer shown next to usable content must stop within 5 seconds or be pausable; TwoUX's reduced-motion setting stops it (2.2.2, 2.3.3).
|
|
47
|
+
|
|
48
|
+
## Keyboard
|
|
49
|
+
|
|
50
|
+
| Keys | Action |
|
|
51
|
+
|---|---|
|
|
52
|
+
| None | No keyboard interaction; filters and page actions stay usable while it loads |
|
|
53
|
+
|
|
54
|
+
## Examples
|
|
55
|
+
|
|
56
|
+
### table
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { Skeleton } from "@twoux/ui";
|
|
60
|
+
|
|
61
|
+
export const meta = { title: "Table rows", description: "Rows keep the real 36px height and column positions. Bars vary in width so they read as text; the figure sits on the right. The header stays live.", order: 1 };
|
|
62
|
+
|
|
63
|
+
const WIDTHS = ["w-40", "w-52", "w-36", "w-48", "w-44"];
|
|
64
|
+
|
|
65
|
+
export default function Example() {
|
|
66
|
+
return (
|
|
67
|
+
<div className="w-full max-w-xl overflow-hidden rounded-sm border border-line bg-surface" aria-busy="true" aria-label="Loading listings">
|
|
68
|
+
<div className="grid h-9 grid-cols-[16px_1fr_96px_64px] items-center gap-3 border-b border-line bg-surface-2 px-3 text-sm font-medium text-ink-2">
|
|
69
|
+
<span />
|
|
70
|
+
<span>Listing</span>
|
|
71
|
+
<span>Location</span>
|
|
72
|
+
<span className="text-right">Applicants</span>
|
|
73
|
+
</div>
|
|
74
|
+
{WIDTHS.map((w, i) => (
|
|
75
|
+
<div key={w + i} className="grid h-9 grid-cols-[16px_1fr_96px_64px] items-center gap-3 border-b border-line px-3 last:border-b-0">
|
|
76
|
+
<Skeleton className="size-3.5" />
|
|
77
|
+
<Skeleton className={`h-2.5 ${w}`} />
|
|
78
|
+
<Skeleton className="h-2.5 w-16" />
|
|
79
|
+
<Skeleton className="ml-auto h-2.5 w-6" />
|
|
80
|
+
</div>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Related
|
|
88
|
+
|
|
89
|
+
- Empty state
|
|
90
|
+
- Error state (failed to load)
|
|
91
|
+
- Side panel (drawer)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Managing agents
|
|
2
|
+
|
|
3
|
+
One Agents page lists every agent with its source, what it does, whether it is on and where it may act. People turn agents on and off with Undo, edit scope in the side panel, and agents are never switched off or deleted without someone choosing to.
|
|
4
|
+
|
|
5
|
+
Kind: pattern · Group: Patterns: AI
|
|
6
|
+
|
|
7
|
+
**Problem:** As a team adds agents (a listings checker, a scheduler, a third-party sourcing tool), nobody can see in one place which are on, who added them, what they can reach and which listings they touch. Trust drops when an agent acts somewhere people didn't expect.
|
|
8
|
+
|
|
9
|
+
**Solution:** An Agents page with a list table: name and avatar, source, short description, capabilities as tags, scope, status as a word badge and an On switch. Selecting a row opens the agent in the side panel with its permissions, scope and activity log. Add agent sits in the page header.
|
|
10
|
+
|
|
11
|
+
**Built from:** Page header, List table, Filter pills and filter bar, Switch, Status badge, Tag, Side panel (drawer), Agent permissions, Connected tools, Toast, Inbox (triage)
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
1. List agents in a table with name, source (TwoUX, your team, or the third party that provides it), what it does, scope, owner and status.
|
|
16
|
+
2. Turn an agent on or off with its switch. The change applies at once with a toast and Undo; runs in progress finish or stop as the person chooses.
|
|
17
|
+
3. Open a row to see and edit its permissions per action, its scope (which teams, listings or locations) and its activity log.
|
|
18
|
+
4. Add agent opens the templates gallery or connected tools; a new agent starts Off with changes set to Ask first.
|
|
19
|
+
5. If an agent can't run (a connection expired), mark it "Needs attention" and send its owner an Inbox item. Don't switch it off.
|
|
20
|
+
|
|
21
|
+
## When to use
|
|
22
|
+
|
|
23
|
+
- When a workspace has more than one agent, or any agent from a third party.
|
|
24
|
+
- To answer "what can act on our listings, and who said it could".
|
|
25
|
+
- When an admin reviews access, for example before a new team joins the workspace.
|
|
26
|
+
|
|
27
|
+
## When not to use
|
|
28
|
+
|
|
29
|
+
- To set what one agent may do in a single run. Use the permission card in the run (Ask first, Allow).
|
|
30
|
+
- For outside tools the assistant can read from. Use Connected tools.
|
|
31
|
+
- For saved tasks a person runs themselves. Those live in Saved tasks.
|
|
32
|
+
|
|
33
|
+
## Guidelines
|
|
34
|
+
|
|
35
|
+
- Show the source next to the name so people can judge trust before turning an agent on.
|
|
36
|
+
- Permissions and scope are visible on the page and editable in place, never only inside a setup flow.
|
|
37
|
+
- Never switch off or delete an agent automatically. Failures are reported, not acted on.
|
|
38
|
+
- Actions that decide on candidates (reject, advance, score for shortlisting) can't be set to Allow for any agent; they stay with a person.
|
|
39
|
+
- Deleting an agent removes its schedule but keeps its activity log. It is irreversible, so it uses the confirmation dialog.
|
|
40
|
+
|
|
41
|
+
## Content
|
|
42
|
+
|
|
43
|
+
- Page title "Agents" with one line: "Agents that can read or change your hiring data, and where."
|
|
44
|
+
- Status words: On, Off, Needs attention. Capability tags are verbs: "Reads listings", "Suggests closing dates".
|
|
45
|
+
- Button: "Add agent".
|
|
46
|
+
|
|
47
|
+
## Accessibility
|
|
48
|
+
|
|
49
|
+
- Each switch is named with the agent: "Turn on Listings checker".
|
|
50
|
+
- Status is a word in a badge, so it reads without colour.
|
|
51
|
+
- The switch change and its Undo are announced as a status message.
|
|
52
|
+
|
|
53
|
+
## Do
|
|
54
|
+
|
|
55
|
+
- Do show source, scope and status at a glance.
|
|
56
|
+
- Do start new agents Off with changes on Ask first.
|
|
57
|
+
- Do let people edit scope from the row's side panel.
|
|
58
|
+
|
|
59
|
+
## Don't
|
|
60
|
+
|
|
61
|
+
- Don't auto-disable or delete an agent.
|
|
62
|
+
- Don't let any agent be allowed to decide on candidates.
|
|
63
|
+
- Don't hide permissions inside the setup flow.
|
|
64
|
+
|
|
65
|
+
## Related
|
|
66
|
+
|
|
67
|
+
- Agents acting on your behalf
|
|
68
|
+
- Agent activity log
|
|
69
|
+
- Saved tasks and schedules
|
|
70
|
+
- Agent permissions
|
|
71
|
+
- Connected tools
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Message composer
|
|
2
|
+
|
|
3
|
+
The message box with context chips, a + menu, @ mentions, / saved tasks, typeahead completions and the Quick or Thorough answer mode.
|
|
4
|
+
|
|
5
|
+
Kind: component · Group: AI and agents · Status: built, import from @twoux/ui
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- Any free question or instruction to the assistant, in the panel or on the full page.
|
|
10
|
+
- When people need to add or narrow context: @ adds a record as a chip, the + menu attaches files and tools.
|
|
11
|
+
- When the trade-off between speed and depth matters: Quick reads what is on screen, Thorough also reads candidates and past listings and says what it costs.
|
|
12
|
+
|
|
13
|
+
## When not to use
|
|
14
|
+
|
|
15
|
+
- A task that repeats with the same inputs. Use the Task builder (task-builder) with slots.
|
|
16
|
+
- A prompt that runs on every row. Use the Prompt editor (prompt-editor), which lists inputs and outputs with types.
|
|
17
|
+
- Searching records by name. Use the table filters (filters), not the assistant.
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
1. Context chips (selection, filters, mentions)
|
|
22
|
+
2. Text area
|
|
23
|
+
3. Typeahead list
|
|
24
|
+
4. + menu (Mention a listing, Run a saved task, Attach a file, toggles, Writing style submenu, Connected tools submenu)
|
|
25
|
+
5. Answer mode button (Quick or Thorough)
|
|
26
|
+
6. Send or Stop button
|
|
27
|
+
|
|
28
|
+
## Guidelines
|
|
29
|
+
|
|
30
|
+
- The box stays clean. Everything that changes context or output lives in the + menu, grouped: inserts, toggles, writing style, connected tools.
|
|
31
|
+
- @ and / work without opening the menu. @ suggests listings and adds the chosen one as a removable chip; / suggests saved tasks.
|
|
32
|
+
- Typeahead suggests whole questions from your own records and saved tasks. What you typed stays regular and the completion is bold. Enter still sends what you typed unless you moved into the list.
|
|
33
|
+
- Answer mode is a stable contract: Quick or Thorough, each with one line on what it does and what it costs. The model name is shown but set by an admin.
|
|
34
|
+
- Allow attachments at any point, including follow-ups, and cite files when an answer relies on them.
|
|
35
|
+
- While running, Send becomes Stop in the same place. People can still draft the next message.
|
|
36
|
+
- One symbol per menu: / for saved tasks and actions, @ for records and files. Each also has a button in the composer bar with the same symbol.
|
|
37
|
+
- Only one menu is open at a time; group items under labels such as "Saved tasks" and "Actions".
|
|
38
|
+
- A hint under the composer says what the symbols do: "/ for tasks, @ to add a record".
|
|
39
|
+
- The Quick or Thorough setting stays visible as a chip and carries over to the next message until changed.
|
|
40
|
+
|
|
41
|
+
## Content
|
|
42
|
+
|
|
43
|
+
- Placeholder says what to do: "Ask, or type @ for a listing and / for a saved task".
|
|
44
|
+
- Menu items are verbs with the shortcut shown: "Mention a listing @", "Run a saved task /".
|
|
45
|
+
- Mode descriptions state the trade-off in plain words: "Answers from what's on screen, in seconds" and "Also reads candidates and past listings. About 5 credits."
|
|
46
|
+
- Never name controls with branded or vague words such as "Magic" or "Boost".
|
|
47
|
+
|
|
48
|
+
## Accessibility
|
|
49
|
+
|
|
50
|
+
- The text area is an editable combobox: aria-autocomplete="list", aria-controls pointing at the suggestions listbox, aria-expanded reflecting whether it is shown, and aria-activedescendant on the highlighted option.
|
|
51
|
+
- Down Arrow moves into suggestions, Enter or Tab accepts the highlighted one, Esc closes the list and keeps focus in the box.
|
|
52
|
+
- The + and mode buttons are menu buttons (aria-haspopup="menu", aria-expanded). Toggles are menuitemcheckbox and writing styles are menuitemradio with aria-checked.
|
|
53
|
+
- The text area has a programmatic label ("Message") even though the label is visually hidden (SC 3.3.2, 4.1.2).
|
|
54
|
+
- @ and / only trigger while the text area has focus, so they are not global character key shortcuts (SC 2.1.4).
|
|
55
|
+
- Each context chip's remove button names what it removes, for example "Remove 2 listings from context".
|
|
56
|
+
- Typing / or @ opens a combobox list; arrows move, Enter inserts, Esc closes and leaves the typed symbol.
|
|
57
|
+
|
|
58
|
+
## Keyboard
|
|
59
|
+
|
|
60
|
+
| Keys | Action |
|
|
61
|
+
|---|---|
|
|
62
|
+
| Enter | Send, or accept the highlighted suggestion |
|
|
63
|
+
| Shift+Enter | New line |
|
|
64
|
+
| Down / Up arrow | Move through suggestions |
|
|
65
|
+
| Tab | Accept the highlighted suggestion |
|
|
66
|
+
| Esc | Close suggestions or the open menu |
|
|
67
|
+
| @ | Suggest listings to add as context |
|
|
68
|
+
| / | Suggest saved tasks |
|
|
69
|
+
| Right arrow / Left arrow | Open or close a submenu in the + menu |
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
### composer
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, MessageComposer, toast } from "@twoux/ui";
|
|
77
|
+
import { useState } from "react";
|
|
78
|
+
|
|
79
|
+
export const meta = { title: "Ask with context", description: "The selection and filters arrive as chips; removing one narrows what the assistant reads. Type @ for a listing or / for a saved task. Enter sends, Shift+Enter adds a line.", order: 1 };
|
|
80
|
+
|
|
81
|
+
export default function Example() {
|
|
82
|
+
const [chips, setChips] = useState([{ id: "sel", label: "3 selected listings" }, { id: "f", label: "Team is Engineering" }]);
|
|
83
|
+
return (
|
|
84
|
+
<div className="w-full max-w-xl">
|
|
85
|
+
<MessageComposer
|
|
86
|
+
noun={{ one: "listing", many: "listings" }}
|
|
87
|
+
chips={chips}
|
|
88
|
+
onRemoveChip={(id) => setChips((c) => c.filter((x) => x.id !== id))}
|
|
89
|
+
mentions={[{ id: "1", label: "FrontendEngineer" }, { id: "2", label: "DataAnalyst" }, { id: "3", label: "QAEngineer" }]}
|
|
90
|
+
tasks={["Weekly-pipeline-summary", "Draft-rejection-reasons"]}
|
|
91
|
+
onSend={(t, m) => toast(`Sent (${m}): ${t}`)}
|
|
92
|
+
plusMenu={<><DropdownMenuLabel>Insert</DropdownMenuLabel><DropdownMenuItem>Mention a listing</DropdownMenuItem><DropdownMenuItem>Run a saved task</DropdownMenuItem><DropdownMenuItem>Attach a file</DropdownMenuItem><DropdownMenuSeparator /><DropdownMenuLabel>Connected tools</DropdownMenuLabel><DropdownMenuItem>Calendar</DropdownMenuItem></>}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Related
|
|
100
|
+
|
|
101
|
+
- Assistant side panel
|
|
102
|
+
- Assistant page
|
|
103
|
+
- Task builder
|
|
104
|
+
- Connected tools
|
|
105
|
+
- Team instructions and files
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Migrating an existing app
|
|
2
|
+
|
|
3
|
+
Move an existing product onto TwoUX one screen at a time, keeping its brand and the way it already works. Primitives come over quietly behind the app's own names; the change people notice comes from rebuilding composites and page flows on TwoUX patterns.
|
|
4
|
+
|
|
5
|
+
Kind: foundation · Group: Adopting TwoUX
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
1. Check the platform. TwoUX components need React 19 and Tailwind CSS 4; upgrade those first, on their own branch, and run the app's tests before any TwoUX change. twoux init warns when a version is too old.
|
|
10
|
+
2. Install @twoux/ui and run npx twoux init. It links AGENTS.md and CLAUDE.md, adds the guard hook, the five skills and twoux.theme.json, and never overwrites existing files.
|
|
11
|
+
3. Set the theme before touching a component: the product's primary colour, corner radii and font in twoux.theme.json, then npx twoux theme. The generator refuses colours that fail WCAG contrast.
|
|
12
|
+
4. Decide who owns each shared token name. Apps built on shadcn define many of the same CSS variables as TwoUX (primary, border, muted, radius, font). Whichever stylesheet loads last repaints the other's components, so pick one owner per name and delete the other definition.
|
|
13
|
+
5. Keep raw Tailwind colours building while you migrate. TwoUX switches off Tailwind's default palette; if the app still uses classes such as text-slate-500, add those colour families back in an @theme block after TwoUX's stylesheet, and remove them once the codemod in step 6 has run.
|
|
14
|
+
6. Swap primitives behind the app's own names. Write one thin adapter per component (same name and props, TwoUX inside) so importing files don't change; map variants and sizes to TwoUX's. Expect tests that assert class names to fail, and fix the tests, not the adapters.
|
|
15
|
+
7. Codemod raw colour classes to roles (text-slate-500 to text-ink-3, border-slate-200 to border-line, bg-primary-50 to bg-selected). A person reviews the mappings that change meaning, such as light grey text that would fail contrast.
|
|
16
|
+
8. Move one screen at a time. Map each composite to the TwoUX job and pattern (filters to the filter bar, record detail to the side panel, delete to Undo, errors in place, the assistant's output to the origin label), rebuild it with /twoux-build and /twoux-design, and keep the product's layout where it already works.
|
|
17
|
+
9. Lock it in. Run npx twoux check in CI on every migrated folder, so new drift fails the build while the rest of the app is still moving.
|
|
18
|
+
|
|
19
|
+
## Principles
|
|
20
|
+
|
|
21
|
+
- Replace gradually. Each screen moves on its own and ships when it is done; the old and new run side by side until the last screen moves.
|
|
22
|
+
- Keep the brand and the workflow. TwoUX takes the product's colour, corners and font through the theme file, and the flows people already know stay where they are unless a TwoUX pattern fixes a real problem in them.
|
|
23
|
+
- Start where people see the difference. Swapping buttons and inputs changes little in an app that is already tidy; the rough edges live in composites (filter chips, review cards, dialogs, the sidebar) and in how pages combine them.
|
|
24
|
+
- Propose, then apply. Every step is shown as a list of changes the team approves before anything is rewritten.
|
|
25
|
+
|
|
26
|
+
## Guidelines
|
|
27
|
+
|
|
28
|
+
- Ask /twoux-audit for the migration plan: it measures the app (versions, shared token names, raw colour classes, primitives that match TwoUX's names, composites that map to TwoUX patterns) and proposes the steps above in order, applying only what the team approves.
|
|
29
|
+
- Before moving a screen, decide which of its differences from TwoUX are product decisions to keep and which are drift to remove. Write the kept ones down; they are candidates for a TwoUX proposal, not exceptions to repeat.
|
|
30
|
+
- Keep each migration change small enough to review: one adapter, one codemod pass or one screen per pull request.
|
|
31
|
+
|
|
32
|
+
## Do
|
|
33
|
+
|
|
34
|
+
- Theme TwoUX with the product's brand colour and font, then swap primitives behind adapters so nothing else changes in that pull request.
|
|
35
|
+
- Rebuild the product's own review card as a TwoUX list row with a side panel, because that is the screen people use all day.
|
|
36
|
+
|
|
37
|
+
## Don't
|
|
38
|
+
|
|
39
|
+
- Don't rebuild the app in TwoUX's demo look; people should still recognise their product.
|
|
40
|
+
- Don't load both stylesheets and leave shared token names to load order.
|
|
41
|
+
- Don't stop after the primitives and call the migration done; the visible gains come from the composites.
|
|
42
|
+
|
|
43
|
+
## Related
|
|
44
|
+
|
|
45
|
+
- Consistency
|
|
46
|
+
- Design tokens
|
|
47
|
+
- Colours
|
|
48
|
+
- Writing
|
|
49
|
+
- Voice and tone
|