@dreamtree-org/twreact-ui 1.2.1 → 1.3.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/DESIGN.md +118 -0
- package/README.md +231 -44
- package/ai-skills/dreamtree-ui.md +98 -12
- package/dist/ai-schema.json +452 -17
- package/dist/components/blocks/AppShell.d.ts +4 -0
- package/dist/components/blocks/AppShell.d.ts.map +1 -0
- package/dist/components/blocks/ConfirmDialog.d.ts +4 -0
- package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -0
- package/dist/components/blocks/EmptyState.d.ts +4 -0
- package/dist/components/blocks/EmptyState.d.ts.map +1 -0
- package/dist/components/blocks/ErrorState.d.ts +4 -0
- package/dist/components/blocks/ErrorState.d.ts.map +1 -0
- package/dist/components/blocks/LoadingState.d.ts +9 -0
- package/dist/components/blocks/LoadingState.d.ts.map +1 -0
- package/dist/components/blocks/PageHeader.d.ts +4 -0
- package/dist/components/blocks/PageHeader.d.ts.map +1 -0
- package/dist/components/blocks/StatCard.d.ts +4 -0
- package/dist/components/blocks/StatCard.d.ts.map +1 -0
- package/dist/components/blocks/StatusPill.d.ts +5 -0
- package/dist/components/blocks/StatusPill.d.ts.map +1 -0
- package/dist/components/core/Accordion.d.ts.map +1 -1
- package/dist/components/core/Button.d.ts.map +1 -1
- package/dist/components/core/Checkbox.d.ts.map +1 -1
- package/dist/components/core/ColorPicker.d.ts.map +1 -1
- package/dist/components/core/DatePicker.d.ts.map +1 -1
- package/dist/components/core/DateRangePicker.d.ts.map +1 -1
- package/dist/components/core/Form.d.ts.map +1 -1
- package/dist/components/core/Input.d.ts.map +1 -1
- package/dist/components/core/LocationPicker.d.ts.map +1 -1
- package/dist/components/core/Popover.d.ts +4 -0
- package/dist/components/core/Popover.d.ts.map +1 -0
- package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
- package/dist/components/core/ProgressBar.d.ts.map +1 -1
- package/dist/components/core/Radio.d.ts.map +1 -1
- package/dist/components/core/Rate.d.ts.map +1 -1
- package/dist/components/core/Select.d.ts.map +1 -1
- package/dist/components/core/Skeleton.d.ts.map +1 -1
- package/dist/components/core/Slider.d.ts +4 -0
- package/dist/components/core/Slider.d.ts.map +1 -0
- package/dist/components/core/SpeechToText.d.ts.map +1 -1
- package/dist/components/core/Switch.d.ts.map +1 -1
- package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
- package/dist/components/core/Table/ColumnMenu.d.ts +2 -1
- package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
- package/dist/components/core/Table/MobileCard.d.ts +2 -2
- package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
- package/dist/components/core/Table/Table.d.ts.map +1 -1
- package/dist/components/core/Table/TableRow.d.ts +4 -2
- package/dist/components/core/Table/TableRow.d.ts.map +1 -1
- package/dist/components/core/Tabs.d.ts.map +1 -1
- package/dist/components/core/Textarea.d.ts +4 -0
- package/dist/components/core/Textarea.d.ts.map +1 -0
- package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
- package/dist/components/core/Tooltip.d.ts.map +1 -1
- package/dist/components/feedback/Dialog.d.ts.map +1 -1
- package/dist/components/feedback/Drawer.d.ts +4 -0
- package/dist/components/feedback/Drawer.d.ts.map +1 -0
- package/dist/components/feedback/Toast.d.ts +14 -5
- package/dist/components/feedback/Toast.d.ts.map +1 -1
- package/dist/components/feedback/Tour.d.ts +4 -0
- package/dist/components/feedback/Tour.d.ts.map +1 -0
- package/dist/components/feedback/TourSpotlight.d.ts +9 -0
- package/dist/components/feedback/TourSpotlight.d.ts.map +1 -0
- package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/navigation/FootNav.d.ts.map +1 -1
- package/dist/components/navigation/Navbar.d.ts.map +1 -1
- package/dist/components/navigation/Sidebar.d.ts +8 -2
- package/dist/components/navigation/Sidebar.d.ts.map +1 -1
- package/dist/components/utility/Badge.d.ts.map +1 -1
- package/dist/components/utility/FileUpload.d.ts.map +1 -1
- package/dist/components/utility/Sortable.d.ts +4 -0
- package/dist/components/utility/Sortable.d.ts.map +1 -0
- package/dist/esm/_vendor/object-assign/index.js +99 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.development.js +190 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.production.min.js +26 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/index.js +19 -0
- package/dist/esm/_vendor/prop-types/checkPropTypes.js +115 -0
- package/dist/esm/_vendor/prop-types/factoryWithThrowingShims.js +74 -0
- package/dist/esm/_vendor/prop-types/factoryWithTypeCheckers.js +625 -0
- package/dist/esm/_vendor/prop-types/index.js +30 -0
- package/dist/esm/_vendor/prop-types/lib/ReactPropTypesSecret.js +21 -0
- package/dist/esm/_vendor/prop-types/lib/has.js +11 -0
- package/dist/esm/_virtual/_commonjsHelpers.js +5 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
- package/dist/esm/_virtual/index.js +3 -0
- package/dist/esm/_virtual/index2.js +3 -0
- package/dist/esm/_virtual/react-is.development.js +3 -0
- package/dist/esm/_virtual/react-is.production.min.js +3 -0
- package/dist/esm/components/blocks/AppShell.js +205 -0
- package/dist/esm/components/blocks/ConfirmDialog.js +57 -0
- package/dist/esm/components/blocks/EmptyState.js +44 -0
- package/dist/esm/components/blocks/ErrorState.js +53 -0
- package/dist/esm/components/blocks/LoadingState.js +134 -0
- package/dist/esm/components/blocks/PageHeader.js +51 -0
- package/dist/esm/components/blocks/StatCard.js +79 -0
- package/dist/esm/components/blocks/StatusPill.js +55 -0
- package/dist/esm/components/core/Accordion.js +260 -0
- package/dist/esm/components/core/Button.js +59 -0
- package/dist/esm/components/core/Checkbox.js +188 -0
- package/dist/esm/components/core/ColorPicker.js +348 -0
- package/dist/esm/components/core/DatePicker.js +436 -0
- package/dist/esm/components/core/DateRangePicker.js +494 -0
- package/dist/esm/components/core/Form.js +522 -0
- package/dist/esm/components/core/Input.js +179 -0
- package/dist/esm/components/core/Loader.js +141 -0
- package/dist/esm/components/core/LocationPicker.js +161 -0
- package/dist/esm/components/core/Popover.js +171 -0
- package/dist/esm/components/core/PriceRangePicker.js +376 -0
- package/dist/esm/components/core/ProgressBar.js +152 -0
- package/dist/esm/components/core/Radio.js +156 -0
- package/dist/esm/components/core/Rate.js +197 -0
- package/dist/esm/components/core/RoundedTag.js +118 -0
- package/dist/esm/components/core/Select.js +857 -0
- package/dist/esm/components/core/Skeleton.js +100 -0
- package/dist/esm/components/core/Slider.js +134 -0
- package/dist/esm/components/core/SpeechToText.js +226 -0
- package/dist/esm/components/core/Switch.js +207 -0
- package/dist/esm/components/core/Table/ActionMenu.js +251 -0
- package/dist/esm/components/core/Table/ColumnMenu.js +55 -0
- package/dist/esm/components/core/Table/FieldInput.js +157 -0
- package/dist/esm/components/core/Table/MobileCard.js +187 -0
- package/dist/esm/components/core/Table/MobileFilters.js +72 -0
- package/dist/esm/components/core/Table/Pagination.js +157 -0
- package/dist/esm/components/core/Table/Table.js +1533 -0
- package/dist/esm/components/core/Table/TableRow.js +254 -0
- package/dist/esm/components/core/Table/constants.js +35 -0
- package/dist/esm/components/core/Table/helpers.js +124 -0
- package/dist/esm/components/core/Table/useClickOutside.js +26 -0
- package/dist/esm/components/core/Tabs.js +289 -0
- package/dist/esm/components/core/TextToSpeech.js +52 -0
- package/dist/esm/components/core/Textarea.js +125 -0
- package/dist/esm/components/core/ThreeDotPopover.js +160 -0
- package/dist/esm/components/core/Tooltip.js +372 -0
- package/dist/esm/components/feedback/Alert.js +96 -0
- package/dist/esm/components/feedback/Dialog.js +200 -0
- package/dist/esm/components/feedback/Drawer.js +171 -0
- package/dist/esm/components/feedback/Toast.js +362 -0
- package/dist/esm/components/feedback/Tour.js +214 -0
- package/dist/esm/components/feedback/TourSpotlight.js +71 -0
- package/dist/esm/components/navigation/Breadcrumbs.js +75 -0
- package/dist/esm/components/navigation/Dropdown.js +53 -0
- package/dist/esm/components/navigation/FootNav.js +51 -0
- package/dist/esm/components/navigation/Navbar.js +437 -0
- package/dist/esm/components/navigation/Sidebar.js +567 -0
- package/dist/esm/components/utility/Avatar.js +89 -0
- package/dist/esm/components/utility/Badge.js +47 -0
- package/dist/esm/components/utility/Card.js +85 -0
- package/dist/esm/components/utility/Carousel.js +97 -0
- package/dist/esm/components/utility/Condition.js +9 -0
- package/dist/esm/components/utility/FileUpload.js +177 -0
- package/dist/esm/components/utility/Pagination.js +111 -0
- package/dist/esm/components/utility/Sortable.js +190 -0
- package/dist/esm/components/utility/Stepper.js +101 -0
- package/dist/esm/hooks/useApi.js +169 -0
- package/dist/esm/hooks/useControllableState.js +32 -0
- package/dist/esm/hooks/useFocusTrap.js +85 -0
- package/dist/esm/hooks/useLatestRef.js +20 -0
- package/dist/esm/hooks/useMixins.js +100 -0
- package/dist/esm/hooks/useScrollLock.js +26 -0
- package/dist/esm/hooks/useSliderValues.js +135 -0
- package/dist/esm/hooks/useSortable.js +188 -0
- package/dist/esm/hooks/useTheme.js +53 -0
- package/dist/esm/hooks/useTourTarget.js +92 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/store/index.js +26 -0
- package/dist/esm/store/store.js +107 -0
- package/dist/esm/store/storeSlice.js +45 -0
- package/dist/esm/utils/cn.js +11 -0
- package/dist/esm/utils/emitter.js +169 -0
- package/dist/esm/utils/helpers.js +161 -0
- package/dist/esm/utils/position.js +134 -0
- package/dist/hooks/useApi.d.ts +2 -1
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useControllableState.d.ts +11 -0
- package/dist/hooks/useControllableState.d.ts.map +1 -0
- package/dist/hooks/useFocusTrap.d.ts +15 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -0
- package/dist/hooks/useLatestRef.d.ts +8 -0
- package/dist/hooks/useLatestRef.d.ts.map +1 -0
- package/dist/hooks/useMixins.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.d.ts +2 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -0
- package/dist/hooks/useSliderValues.d.ts +43 -0
- package/dist/hooks/useSliderValues.d.ts.map +1 -0
- package/dist/hooks/useSortable.d.ts +32 -0
- package/dist/hooks/useSortable.d.ts.map +1 -0
- package/dist/hooks/useTheme.d.ts.map +1 -1
- package/dist/hooks/useTourTarget.d.ts +15 -0
- package/dist/hooks/useTourTarget.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +55 -39
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +7937 -4659
- package/dist/index.js +7950 -4657
- package/dist/tokens/brands/default.css +1 -0
- package/dist/tokens/brands/default.json +5 -0
- package/dist/tokens/brands/practicepro.css +17 -0
- package/dist/tokens/brands/practicepro.json +27 -0
- package/dist/tokens/tailwind3-preset.cjs +214 -0
- package/dist/tokens/theme.css +193 -0
- package/dist/tokens/tokens.json +373 -0
- package/dist/tokens/vars.css +98 -0
- package/dist/types/blocks.d.ts +109 -0
- package/dist/types/blocks.d.ts.map +1 -0
- package/dist/types/index.d.ts +194 -5
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/position.d.ts +30 -0
- package/dist/utils/position.d.ts.map +1 -0
- package/doc/README.md +26 -1
- package/mcp/catalog.snapshot.json +1643 -549
- package/package.json +24 -6
- package/tokens/brands/default.json +5 -0
- package/tokens/brands/practicepro.json +27 -0
- package/tokens/lib.cjs +128 -0
- package/tokens/tokens.json +133 -0
package/DESIGN.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Dreamtree
|
|
3
|
+
description: Shared design rules for every Dreamtree product UI, generated from twreact-ui tokens/tokens.json.
|
|
4
|
+
source: "@dreamtree-org/twreact-ui/tokens/tokens.json"
|
|
5
|
+
colors:
|
|
6
|
+
background: "#f8fafc"
|
|
7
|
+
surface: "#ffffff"
|
|
8
|
+
foreground: "#0f172a"
|
|
9
|
+
muted: "#475569"
|
|
10
|
+
line: "#e2e8f0"
|
|
11
|
+
shell: "#0f172a"
|
|
12
|
+
shell-foreground: "#cbd5e1"
|
|
13
|
+
shell-active: "#1e293b"
|
|
14
|
+
primary: "#2563eb"
|
|
15
|
+
secondary: "#475569"
|
|
16
|
+
success: "#16a34a"
|
|
17
|
+
warning: "#d97706"
|
|
18
|
+
error: "#dc2626"
|
|
19
|
+
info: "#0284c7"
|
|
20
|
+
colorsDark:
|
|
21
|
+
background: "#0b1120"
|
|
22
|
+
surface: "#111827"
|
|
23
|
+
foreground: "#e5e7eb"
|
|
24
|
+
muted: "#94a3b8"
|
|
25
|
+
line: "#1f2937"
|
|
26
|
+
shell: "#020617"
|
|
27
|
+
shell-foreground: "#cbd5e1"
|
|
28
|
+
shell-active: "#1e293b"
|
|
29
|
+
typography:
|
|
30
|
+
fontFamily: "Inter"
|
|
31
|
+
pageTitle: { size: 24px, lineHeight: 32px, weight: 600 }
|
|
32
|
+
sectionTitle: { size: 16px, lineHeight: 24px, weight: 600 }
|
|
33
|
+
body: { size: 14px, lineHeight: 20px, weight: 400 }
|
|
34
|
+
caption: { size: 12px, lineHeight: 16px, weight: 400 }
|
|
35
|
+
micro: { size: 11px, lineHeight: 16px, weight: 500 }
|
|
36
|
+
kpi: { size: 30px, lineHeight: 36px, weight: 600 }
|
|
37
|
+
rounded:
|
|
38
|
+
control: 8px
|
|
39
|
+
card: 12px
|
|
40
|
+
pill: 9999px
|
|
41
|
+
spacing:
|
|
42
|
+
grid: 4px
|
|
43
|
+
steps: [4, 8, 12, 16, 20, 24, 32]
|
|
44
|
+
brands: [default, practicepro]
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
<!-- Generated by twreact-ui scripts/build-tokens.mjs from tokens/tokens.json. Edit the tokens or this generator, not this file. -->
|
|
48
|
+
|
|
49
|
+
# Dreamtree design rules
|
|
50
|
+
|
|
51
|
+
Every Dreamtree product screen, and every prototype generated by Dreamtree Studio, follows these rules. They are enforced by `@dreamtree-org/config` design-lint (`dreamtree({ design: true })`) and by Studio's `validate_screen`.
|
|
52
|
+
|
|
53
|
+
## 1. Components come from twreact-ui
|
|
54
|
+
|
|
55
|
+
- Build screens from `@dreamtree-org/twreact-ui` components and blocks. Do not hand-roll a Button, Dialog, Table, Badge, Card, Toast, Tabs or Pagination.
|
|
56
|
+
- Page structure comes from blocks: `AppShell`, `PageHeader`, `StatCard`, `StatusPill`, `EmptyState`, `ErrorState`, `LoadingState`, `ConfirmDialog`.
|
|
57
|
+
- Icons come from `lucide-react` only. Icon-only buttons need an `aria-label`.
|
|
58
|
+
- If a pattern is missing, compose it from existing components and flag it as a candidate block. Do not copy one from another product.
|
|
59
|
+
|
|
60
|
+
## 2. Colour comes from tokens, never raw values
|
|
61
|
+
|
|
62
|
+
- **Never** write hex, `rgb()` or `hsl()` in `className` or `style`, and never use Tailwind's raw palette (`bg-blue-500`, `text-slate-600`).
|
|
63
|
+
- Use the semantic scales: `primary` (the one accent), `secondary` (neutral), `success`, `warning`, `error`, `info`, and `gray` for borders and quiet fills. Each has steps 50–950.
|
|
64
|
+
- Use roles for surfaces and text:
|
|
65
|
+
|
|
66
|
+
| Role | Utility | Light | Dark | Use |
|
|
67
|
+
| --- | --- | --- | --- | --- |
|
|
68
|
+
| `background` | `bg-background` / `text-background` | #f8fafc | #0b1120 | Page background. |
|
|
69
|
+
| `surface` | `bg-surface` / `text-surface` | #ffffff | #111827 | Cards, dialogs, menus. |
|
|
70
|
+
| `foreground` | `bg-foreground` / `text-foreground` | #0f172a | #e5e7eb | Body text and titles. |
|
|
71
|
+
| `muted` | `bg-muted` / `text-muted` | #475569 | #94a3b8 | Subtitles, labels, captions (7.5:1 on white). |
|
|
72
|
+
| `line` | `bg-line` / `text-line` | #e2e8f0 | #1f2937 | Card and divider borders. |
|
|
73
|
+
| `shell` | `bg-shell` / `text-shell` | #0f172a | #020617 | App shell sidebar background. |
|
|
74
|
+
| `shell-foreground` | `bg-shell-foreground` / `text-shell-foreground` | #cbd5e1 | #cbd5e1 | Sidebar text. |
|
|
75
|
+
| `shell-active` | `bg-shell-active` / `text-shell-active` | #1e293b | #1e293b | Active sidebar item fill. |
|
|
76
|
+
|
|
77
|
+
- **One accent per screen.** `primary` marks the primary action, the active nav item, the current step and progress. Red (`error`) is only for errors, overdue items and destructive actions.
|
|
78
|
+
- Badge and status fills use the 100 step with 800 text, which keeps contrast at or above 4.5:1.
|
|
79
|
+
- Products change their look with a **brand file** (`tokens/brands/<product>.json`), which overrides `--dt-*` variables. Brands available: `default`, `practicepro`.
|
|
80
|
+
|
|
81
|
+
## 3. Type, space and shape
|
|
82
|
+
|
|
83
|
+
- Font: Inter. Numbers in tables and KPIs use `tabular-nums`.
|
|
84
|
+
- Scale: page title `text-2xl font-semibold`, section title `text-base font-semibold`, body `text-sm`, caption `text-xs text-muted`, KPI `text-3xl font-semibold`. For tiny labels use `text-micro`, never `text-[10px]` or `text-[11px]`.
|
|
85
|
+
- **No arbitrary values** (`w-[312px]`, `text-[13px]`, `p-[7px]`). Use the Tailwind scale on a 4 px grid: steps 1, 2, 3, 4, 5, 6, 8.
|
|
86
|
+
- Radius: `rounded-control` (8 px) for buttons, inputs and menus; `rounded-card` (12 px) for cards and dialogs; `rounded-full` for pills and badges.
|
|
87
|
+
- Sentence case everywhere ("Add obligation", not "Add Obligation").
|
|
88
|
+
|
|
89
|
+
## 4. Page anatomy
|
|
90
|
+
|
|
91
|
+
- One `h1` per page, inside `PageHeader` (title, optional subtitle, actions). **One primary action per page**, top right; it becomes full-width below `sm`.
|
|
92
|
+
- All content sits in cards (`Card`), never loose on the page background. Use a grid with `gap-4 md:gap-6`; main plus side panel is `lg:grid-cols-[minmax(0,1fr)_360px]`. That column template is the one allowed arbitrary value.
|
|
93
|
+
- Creating a record opens a Dialog (short form) or goes to a `/new` page (a wizard).
|
|
94
|
+
|
|
95
|
+
## 5. Every data screen has its states
|
|
96
|
+
|
|
97
|
+
- **Loading:** `LoadingState` or `Skeleton` shaped like the final content, inside the card. No page-level spinners.
|
|
98
|
+
- **Empty:** `EmptyState` inside the card, with a title, one line saying what to do, and the primary action.
|
|
99
|
+
- **Error:** `ErrorState` inside the card, with neutral copy and a Retry button. Never show raw `error.message`.
|
|
100
|
+
- Statuses use `StatusPill`, a single map from domain state to tone and a human label. Never show a raw enum such as `in_review`.
|
|
101
|
+
|
|
102
|
+
## 6. Dialogs and feedback
|
|
103
|
+
|
|
104
|
+
- **Never** use `alert()`, `confirm()` or `prompt()`. Confirmations use `ConfirmDialog`: the title is the action as a question, the body is one line saying what happens, and the action button is named with the verb (`destructive` when the action can't be undone).
|
|
105
|
+
- Outcomes ("Saved", "Reminder drafted") use `Toast`. Errors that block the user stay inline in an `Alert`.
|
|
106
|
+
|
|
107
|
+
## 7. Responsive and accessible
|
|
108
|
+
|
|
109
|
+
- No horizontal page scroll at **390 px**. Below `lg` the shell becomes a top bar with a drawer. Wide tables scroll inside their card.
|
|
110
|
+
- Tap targets are at least 40 px. Every control has a visible label. Focus is visible (`focus:ring-2 focus:ring-primary-500 focus:ring-offset-2`).
|
|
111
|
+
- Text contrast is at least 4.5:1. Status is never shown by colour alone: badges carry words.
|
|
112
|
+
- Respect `prefers-reduced-motion`.
|
|
113
|
+
|
|
114
|
+
## 8. Data and content
|
|
115
|
+
|
|
116
|
+
- Prototypes use **mock data only**, never real client data.
|
|
117
|
+
- Indian products format money as ₹ with lakh/crore grouping (₹12,34,567; ₹4.8L), and dates as DD MMM YYYY (20 Oct 2026).
|
|
118
|
+
- Use realistic content, including awkward cases: long company names, ₹0, empty lists and very long lists.
|
package/README.md
CHANGED
|
@@ -64,6 +64,45 @@ npm install @dreamtree-org/twreact-ui
|
|
|
64
64
|
yarn add @dreamtree-org/twreact-ui
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
Import the stylesheet once (app entry or root layout):
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import '@dreamtree-org/twreact-ui/dist/index.css';
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Bundle size and tree-shaking
|
|
74
|
+
|
|
75
|
+
Import from the package root. Bundlers (Vite, webpack 5, esbuild, Rollup, Next.js) resolve it to a
|
|
76
|
+
per-module ES build (`dist/esm/`, via the `module` field and the `module` export condition), and the
|
|
77
|
+
package declares `"sideEffects": ["**/*.css"]`, so only the components you import end up in your
|
|
78
|
+
bundle. `import { Button } from '@dreamtree-org/twreact-ui'` costs about 8 KB gzip, down from about
|
|
79
|
+
100 KB. `require()`, Jest and plain Node keep getting the CommonJS build (`dist/index.js`).
|
|
80
|
+
|
|
81
|
+
There are no per-component subpaths such as `@dreamtree-org/twreact-ui/button`. The root import
|
|
82
|
+
already tree-shakes. Existing file paths (`dist/index.css`, `dist/tokens/*`, `tokens/*`,
|
|
83
|
+
`dist/index.esm.js`, `package.json`, …) still resolve through the `exports` map.
|
|
84
|
+
|
|
85
|
+
### Next.js App Router
|
|
86
|
+
|
|
87
|
+
Every module that uses React starts with `'use client'`, so Server Components can import
|
|
88
|
+
components directly. You no longer need a `"use client"` wrapper file that re-exports the library:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// app/page.tsx — a Server Component
|
|
92
|
+
import { Button, cn } from '@dreamtree-org/twreact-ui';
|
|
93
|
+
|
|
94
|
+
export default function Page() {
|
|
95
|
+
return <Button className={cn('mt-4')}>Save</Button>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`cn`, `Helpers` and `Emitter` carry no directive, so they also run on the server. Two React
|
|
100
|
+
Server Component rules still apply. You cannot pass functions (`onClick`, `render`, …) from a
|
|
101
|
+
Server Component to these Client Components. You also cannot dot into a client export, so
|
|
102
|
+
`<Card.Header>`, `<Tabs.List>` and `<Dialog.Footer>` must be used inside a `'use client'` file.
|
|
103
|
+
In Vite, Rollup can log `Module level directives cause errors when bundled, "use client" … was ignored`.
|
|
104
|
+
This message is harmless. To hide it, filter `MODULE_LEVEL_DIRECTIVE` in `build.rollupOptions.onwarn`.
|
|
105
|
+
|
|
67
106
|
## Quick Start
|
|
68
107
|
|
|
69
108
|
```jsx
|
|
@@ -147,7 +186,7 @@ Versatile button component with multiple variants and states.
|
|
|
147
186
|
|
|
148
187
|
**Props:**
|
|
149
188
|
- `variant`: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'success' | 'warning' (default: 'primary')
|
|
150
|
-
- `size`: 'sm' | 'md' | 'lg' (default: 'md')
|
|
189
|
+
- `size`: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' (default: 'md') — heights h-7 / h-8 / h-10 / h-12 / h-14 / h-16
|
|
151
190
|
- `disabled`: boolean
|
|
152
191
|
- `loading`: boolean
|
|
153
192
|
- `leftIcon`: ReactNode
|
|
@@ -172,6 +211,7 @@ Enhanced text input with validation states, icons, and clear functionality.
|
|
|
172
211
|
- `placeholder`: string
|
|
173
212
|
- `error`: string
|
|
174
213
|
- `success`: string
|
|
214
|
+
- `helperText`: ReactNode — hint below the input, linked via `aria-describedby`; replaced by `error` / `success` while shown
|
|
175
215
|
- `disabled`: boolean
|
|
176
216
|
- `required`: boolean
|
|
177
217
|
- `clearable`: boolean
|
|
@@ -197,17 +237,18 @@ Enhanced text input with validation states, icons, and clear functionality.
|
|
|
197
237
|
Advanced select component with search, multi-select, and grouping support.
|
|
198
238
|
|
|
199
239
|
**Props:**
|
|
200
|
-
- `options`: Array<{value: any, label: string, disabled?: boolean}>
|
|
240
|
+
- `options`: Array<{value: any, label: string, disabled?: boolean, group?: string}> (with `grouped`: also `[{ label, options }]` groups)
|
|
201
241
|
- `value`: any | Array<any>
|
|
202
242
|
- `onChange`: (value: any) => void
|
|
203
243
|
- `placeholder`: string (default: 'Select an option...')
|
|
204
|
-
- `label`: string
|
|
244
|
+
- `label`: string — associated with the trigger (accessible name = label + value)
|
|
205
245
|
- `error`: string
|
|
246
|
+
- `helperText`: ReactNode — hint below the control; replaced by `error` while shown
|
|
206
247
|
- `disabled`: boolean
|
|
207
248
|
- `required`: boolean
|
|
208
249
|
- `multiSelect`: boolean
|
|
209
250
|
- `searchable`: boolean
|
|
210
|
-
- `grouped`: boolean
|
|
251
|
+
- `grouped`: boolean — groups objects or flat options with a `group` key; malformed input is skipped (console warning), never thrown
|
|
211
252
|
- `creatable`: boolean
|
|
212
253
|
- `onCreateOption`: (value: string) => void
|
|
213
254
|
- `onSearch`: (term: string) => void
|
|
@@ -241,6 +282,8 @@ Declarative form builder with validation support.
|
|
|
241
282
|
- `resetText`: string (default: 'Reset')
|
|
242
283
|
- `showReset`: boolean (default: true)
|
|
243
284
|
|
|
285
|
+
Field types: `text`, `email`, `password`, `number`, `tel`, `url`, `select`, `date`, `textarea`, `checkbox`, `radio`, `file`, `switch`, `color` (mounts `ColorPicker`; accepts `swatches` / `showAlpha`), `custom`. Every field accepts `helperText` (hint under the field; the validation error replaces it while shown).
|
|
286
|
+
|
|
244
287
|
```jsx
|
|
245
288
|
const formFields = [
|
|
246
289
|
{
|
|
@@ -273,7 +316,8 @@ actions, and expandable row details.
|
|
|
273
316
|
- `data`: Array<object>
|
|
274
317
|
- `columns`: Array<Column>
|
|
275
318
|
- `sortable`: boolean (default: true)
|
|
276
|
-
- `filterable`: boolean
|
|
319
|
+
- `filterable`: boolean — per-column filter inputs; filter client-side data as you type (not in `serverSide` mode)
|
|
320
|
+
- `globalSearch`: boolean — search box; filters client-side data with or without `filterable` (skips `searchable: false` columns)
|
|
277
321
|
- `selectable`: boolean
|
|
278
322
|
- `pagination`: boolean
|
|
279
323
|
- `pageSize`: number (default: 25)
|
|
@@ -281,7 +325,7 @@ actions, and expandable row details.
|
|
|
281
325
|
- `onFilter`: (filters: object) => void
|
|
282
326
|
- `onSelectionChange`: (selectedRows: object[]) => void
|
|
283
327
|
- `onRowClick`: (row: object, index: number) => void
|
|
284
|
-
- `onPageChange`: (page: number) => void
|
|
328
|
+
- `onPageChange`: (page: number) => void — also called when the table clamps a page that no longer exists after the data shrinks
|
|
285
329
|
- `hasDetails`: boolean | ((row, index) => boolean)
|
|
286
330
|
- `DetailsComponent`: ReactComponent
|
|
287
331
|
- `withAction`: boolean (default: true)
|
|
@@ -295,6 +339,15 @@ actions, and expandable row details.
|
|
|
295
339
|
- `onSave`: ({ updatedRows, newRows, allData, tableData }) => void | Promise<allData | { data: allData } | void>
|
|
296
340
|
- `onEdit`: ({ rowKey, colKey, newValue, row, action, isNew }) => void
|
|
297
341
|
- `showSerial`: boolean (default: true)
|
|
342
|
+
- `showToolbar`: boolean (default: true) — `false` hides the toolbar (reload, page size, result count, pager header) and the column picker; interactive save controls still render
|
|
343
|
+
- `showColumnPicker`: boolean (default: true) — hide just the column-picker button and its column
|
|
344
|
+
- `wrapCells`: boolean (default: false) — let every cell wrap; a column's own `wrap` wins
|
|
345
|
+
- `mobileMaxFields`: number — cap the fields shown on each mobile card (column order, after `hideOnMobile`)
|
|
346
|
+
- `serverSide` / `totalRecords`: in server-side mode the "start - end of total" counter reads `totalRecords`
|
|
347
|
+
|
|
348
|
+
Column extras: `searchable: false` keeps a column out of the global search
|
|
349
|
+
(ids, codes), `wrap: true` lets its text wrap instead of staying on one line, and
|
|
350
|
+
`hideOnMobile: true` leaves it off the mobile card view (desktop is unaffected).
|
|
298
351
|
|
|
299
352
|
An `Action` is `{ name, label, icon, render?, onClick?, hidden?, disabled?,
|
|
300
353
|
inline? }`. `label`, `icon`, `hidden` and `disabled` each accept a value **or** a
|
|
@@ -335,6 +388,7 @@ Calendar-based date selection with keyboard navigation.
|
|
|
335
388
|
- `placeholder`: string (default: 'Select date...')
|
|
336
389
|
- `label`: string
|
|
337
390
|
- `error`: string
|
|
391
|
+
- `helperText`: ReactNode — hint below the trigger; replaced by `error` while shown
|
|
338
392
|
- `disabled`: boolean
|
|
339
393
|
- `required`: boolean
|
|
340
394
|
- `minDate`: Date | string
|
|
@@ -367,22 +421,31 @@ Collapsible sidebar navigation with nested items.
|
|
|
367
421
|
- `logo`: ReactNode
|
|
368
422
|
- `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>}
|
|
369
423
|
- `onUserClick`: (user: object) => void
|
|
424
|
+
- `linkAs`: ElementType (default: `"a"`) — e.g. Next.js `Link`
|
|
425
|
+
- `expandedIds` / `defaultExpandedIds`: string[] — open groups (controlled / uncontrolled); `onExpandedChange`: (ids: string[]) => void
|
|
426
|
+
- `footer`: ReactNode | ({ collapsed, isMobile, closeMobile }) => ReactNode — pinned region above the user block
|
|
427
|
+
- `stickyOffset`: number | string — height of a top bar above the sidebar (sticks below it, fits the viewport); `containerClassName` for the outer sticky wrapper
|
|
428
|
+
- `isMobileOpen` / `setIsMobileOpen`: mobile drawer — inert while closed; focus-trapped, Escape-to-close and focus-restoring while open
|
|
429
|
+
|
|
430
|
+
Every item renders as an `<a>`. Give an item an `href` and it becomes a real link, so Ctrl/Cmd-click or middle-click opens it in a new tab. Parents with an `href` navigate and expand; their chevron toggles without navigating. The group holding an `active: true` item (any depth) opens on load. Items take `badge` (+ accessible `badgeLabel`) and top-level `group` (section heading, a divider when collapsed — same as `AppShell`). With `onUserClick` the user block is a real `<button>`.
|
|
370
431
|
|
|
371
432
|
```jsx
|
|
372
433
|
const sidebarItems = [
|
|
373
434
|
{
|
|
374
435
|
id: 'dashboard',
|
|
375
436
|
label: 'Dashboard',
|
|
437
|
+
href: '/dashboard',
|
|
376
438
|
icon: <Home className="h-4 w-4" />,
|
|
377
439
|
active: true
|
|
378
440
|
},
|
|
379
441
|
{
|
|
380
442
|
id: 'users',
|
|
381
443
|
label: 'Users',
|
|
444
|
+
href: '/users',
|
|
382
445
|
icon: <Users className="h-4 w-4" />,
|
|
383
446
|
children: [
|
|
384
|
-
{ id: 'all-users', label: 'All Users' },
|
|
385
|
-
{ id: 'new-user', label: 'Add New User' }
|
|
447
|
+
{ id: 'all-users', label: 'All Users', href: '/users' },
|
|
448
|
+
{ id: 'new-user', label: 'Add New User', href: '/users/new' }
|
|
386
449
|
]
|
|
387
450
|
}
|
|
388
451
|
];
|
|
@@ -398,9 +461,10 @@ const sidebarItems = [
|
|
|
398
461
|
Top navigation bar with user menu and notifications.
|
|
399
462
|
|
|
400
463
|
**Props:**
|
|
401
|
-
- `logo`: ReactNode
|
|
464
|
+
- `logo`: ReactNode (truncates on narrow screens)
|
|
465
|
+
- `center`: ReactNode — centre slot (e.g. a search field); shrinks first so the bar fits a 390px phone
|
|
402
466
|
- `items`: Array<NavItem>
|
|
403
|
-
- `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>}
|
|
467
|
+
- `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>} — without `avatar`, the menu shows `name` initials; below `sm` the logout icon moves into the user menu as "Sign out"
|
|
404
468
|
- `notifications`: Array<Notification>
|
|
405
469
|
- `searchable`: boolean
|
|
406
470
|
- `onSearch`: (term: string) => void
|
|
@@ -420,6 +484,38 @@ Top navigation bar with user menu and notifications.
|
|
|
420
484
|
/>
|
|
421
485
|
```
|
|
422
486
|
|
|
487
|
+
#### FootNav
|
|
488
|
+
Fixed bottom tab bar for phones (`md:hidden`). Uses the semantic `bg-surface` / `border-line` tokens, so it follows dark mode.
|
|
489
|
+
|
|
490
|
+
**Props:**
|
|
491
|
+
- `items`: Array<{ id, label, icon, onClick?, active?, badge? }>
|
|
492
|
+
- `className`: string
|
|
493
|
+
|
|
494
|
+
The `active` item gets a tinted primary background, `text-primary-700` and `aria-current="page"` (inactive tabs are neutral `text-muted`). Badges sit on their own item's icon. Items share the width and labels truncate, so five tabs fit a 375px screen.
|
|
495
|
+
|
|
496
|
+
```jsx
|
|
497
|
+
<FootNav
|
|
498
|
+
items={[
|
|
499
|
+
{ id: 'home', label: 'Home', icon: <Home />, active: true, onClick: () => navigate('/') },
|
|
500
|
+
{ id: 'inbox', label: 'Inbox', icon: <Inbox />, badge: 3, onClick: () => navigate('/inbox') },
|
|
501
|
+
]}
|
|
502
|
+
/>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### Breadcrumbs
|
|
506
|
+
Breadcrumb trail with an optional Home link.
|
|
507
|
+
|
|
508
|
+
**Props:**
|
|
509
|
+
- `items`: Array<{ id?, label, href?, icon?, onClick? }> — `id` is optional (used as the React key when present)
|
|
510
|
+
- `separator`: ReactNode
|
|
511
|
+
- `showHome`: boolean (default: true)
|
|
512
|
+
- `homeIcon`: ReactNode
|
|
513
|
+
- `onHomeClick`: () => void
|
|
514
|
+
|
|
515
|
+
```jsx
|
|
516
|
+
<Breadcrumbs items={[{ label: 'Projects', href: '/projects' }, { label: 'Alpha' }]} />
|
|
517
|
+
```
|
|
518
|
+
|
|
423
519
|
### Feedback Components
|
|
424
520
|
|
|
425
521
|
#### Alert
|
|
@@ -455,6 +551,23 @@ Notification messages with different types, actions, bottom progress bar, and po
|
|
|
455
551
|
- `onClose`: (id: string) => void
|
|
456
552
|
- `position`: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center'
|
|
457
553
|
|
|
554
|
+
**App-level provider (recommended).** Wrap the app once in `ToastProvider`; every `useToast()` below it shares one queue and the provider renders it — no per-screen `ToastContainer`:
|
|
555
|
+
|
|
556
|
+
```jsx
|
|
557
|
+
import { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';
|
|
558
|
+
|
|
559
|
+
<ToastProvider position="top-right">
|
|
560
|
+
<App />
|
|
561
|
+
</ToastProvider>;
|
|
562
|
+
|
|
563
|
+
function SaveButton() {
|
|
564
|
+
const { toast } = useToast();
|
|
565
|
+
return <button onClick={() => toast.success('Saved')}>Save</button>;
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Standalone (no provider).** Outside a provider each `useToast()` owns its own queue; render it with `ToastContainer`. Under a provider, a `ToastContainer` handed the provider's own `toasts` renders nothing, so it never double-renders while you migrate.
|
|
570
|
+
|
|
458
571
|
```jsx
|
|
459
572
|
import { useToast, ToastContainer } from '@dreamtree-org/twreact-ui';
|
|
460
573
|
|
|
@@ -567,47 +680,121 @@ const tabItems = [
|
|
|
567
680
|
<Tabs items={tabItems} variant="pills" />
|
|
568
681
|
```
|
|
569
682
|
|
|
683
|
+
### Overlays, inputs and lists
|
|
684
|
+
|
|
685
|
+
Interaction primitives that products used to compose by hand. All are `forwardRef`, accept `className` + `...rest`, are keyboard-operable with explicit ARIA, dark-mode aware and respect `prefers-reduced-motion`.
|
|
686
|
+
|
|
687
|
+
| Component | Group | Use for |
|
|
688
|
+
|---|---|---|
|
|
689
|
+
| [`Drawer`](doc/Drawer.md) | Feedback | Side/edge sheet (`placement` right/left/top/bottom, `size` sm–full) — record peeks and editors. Same `isOpen`/`onClose` contract as `Dialog`; focus trap, Escape/backdrop close, focus restore, scroll lock; `Drawer.Header/Body/Footer` |
|
|
690
|
+
| [`Popover`](doc/Popover.md) | Core | Anchored, click-triggered panel with interactive content (unlike `Tooltip`). Controlled/uncontrolled `open`, `placement` with flip, Escape/outside-click close, `aria-expanded`/`aria-controls`/`aria-haspopup="dialog"` |
|
|
691
|
+
| [`Textarea`](doc/Textarea.md) | Core | Multi-line input mirroring `Input`: `label`, `error`, `helperText`, `required`, `showCount`/`maxLength`, `autoResize`/`maxRows` |
|
|
692
|
+
| [`Slider`](doc/Slider.md) | Core | Single value or `[low, high]` range; `min`/`max`/`step`, arrows/PageUp/PageDown/Home/End, `role="slider"` with full `aria-value*` |
|
|
693
|
+
| [`Sortable`](doc/Sortable.md) | Utility | Drag-to-reorder list (pointer events, no extra dependency) + keyboard reordering (Space, arrows, Space/Escape) with live announcements; `items`, `onReorder`, `renderItem`, `getKey` |
|
|
694
|
+
| [`Tour`](doc/Tour.md) | Feedback | Step-based onboarding: spotlight on each `target` (selector or ref), Back/Next/Skip/Finish, `open`/`onClose`/`onFinish`, scrolls targets into view |
|
|
695
|
+
|
|
696
|
+
```jsx
|
|
697
|
+
import { Drawer, Popover, Textarea, Slider, Sortable, Tour, Button } from '@dreamtree-org/twreact-ui';
|
|
698
|
+
|
|
699
|
+
<Drawer isOpen={open} onClose={() => setOpen(false)} title="Edit task" footer={<Button onClick={save}>Save</Button>}>
|
|
700
|
+
<Textarea label="Description" autoResize showCount maxLength={500} />
|
|
701
|
+
</Drawer>
|
|
702
|
+
|
|
703
|
+
<Popover trigger={<Button variant="outline">Filters</Button>} title="Filters">
|
|
704
|
+
<Slider label="Budget" defaultValue={[2000, 8000]} min={0} max={10000} step={500} showValue />
|
|
705
|
+
</Popover>
|
|
706
|
+
|
|
707
|
+
<Sortable aria-label="Backlog" items={tasks} onReorder={setTasks} renderItem={(t) => t.title} />
|
|
708
|
+
|
|
709
|
+
<Tour open={tourOpen} steps={[{ title: 'Welcome' }, { target: '[data-tour="new-task"]', title: 'Capture work' }]} onClose={() => setTourOpen(false)} />
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
### Page blocks
|
|
713
|
+
|
|
714
|
+
Page-level patterns extracted from the Dreamtree products. Build pages from these before composing layouts by hand.
|
|
715
|
+
|
|
716
|
+
| Block | Use for |
|
|
717
|
+
|---|---|
|
|
718
|
+
| [`AppShell`](doc/AppShell.md) | Product shell: sidebar, top bar, mobile drawer (`nav`, `linkAs`) |
|
|
719
|
+
| [`PageHeader`](doc/PageHeader.md) | The page's `h1`, subtitle, one primary action, back link |
|
|
720
|
+
| [`StatCard`](doc/StatCard.md) | KPI tile with icon, tone and change |
|
|
721
|
+
| [`StatusPill`](doc/StatusPill.md) | Domain status → tone and human label (never a raw enum) |
|
|
722
|
+
| [`EmptyState`](doc/EmptyState.md) / [`ErrorState`](doc/ErrorState.md) / [`LoadingState`](doc/LoadingState.md) | The three states every data card needs |
|
|
723
|
+
| [`ConfirmDialog`](doc/ConfirmDialog.md) | Confirmation step — replaces `window.confirm()` |
|
|
724
|
+
|
|
725
|
+
```jsx
|
|
726
|
+
import { PageHeader, StatCard, StatusPill, EmptyState, Button } from '@dreamtree-org/twreact-ui';
|
|
727
|
+
|
|
728
|
+
<PageHeader title="Compliance calendar" actions={<Button>Add obligation</Button>} />
|
|
729
|
+
<StatCard label="Due this week" value={18} icon={<CalendarDays className="h-6 w-6" />} />
|
|
730
|
+
<StatusPill status="docs_missing" map={{ docs_missing: { label: 'Missing documents', tone: 'warning' } }} />
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
Component types now carry their props (`<Button variant="bogus" />` is a type error).
|
|
734
|
+
|
|
735
|
+
### Hooks
|
|
736
|
+
|
|
737
|
+
#### useApi
|
|
738
|
+
Thin axios wrapper. Returns `{ data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith }`.
|
|
739
|
+
|
|
740
|
+
- `fetchApi(key, data?, needLoading?)` calls `apiMap[key](data)` with **one** argument.
|
|
741
|
+
- `fetchApiWith(key, args, { needLoading }?)` spreads `args` into the map entry — use it for entries like `(id, body) => ({...})`.
|
|
742
|
+
|
|
743
|
+
```jsx
|
|
744
|
+
const { fetchApi, fetchApiWith } = useApi({
|
|
745
|
+
BASE_URL: 'https://api.example.com',
|
|
746
|
+
apiMap: {
|
|
747
|
+
getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),
|
|
748
|
+
updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
fetchApi('getUser', 7);
|
|
753
|
+
fetchApiWith('updateUser', [7, { name: 'Ada' }]);
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
See [doc/useApi.md](doc/useApi.md).
|
|
757
|
+
|
|
570
758
|
## Theming
|
|
571
759
|
|
|
572
|
-
|
|
760
|
+
Every colour in the compiled CSS is a CSS variable (`--dt-primary-600`, `--dt-surface`, …) generated from `tokens/tokens.json` (W3C Design Tokens format). The defaults equal the classic palette, so nothing changes until you override a variable.
|
|
761
|
+
|
|
762
|
+
**Re-theme with a brand file** (no Tailwind config needed, works with the precompiled CSS):
|
|
763
|
+
|
|
764
|
+
```css
|
|
765
|
+
@import "@dreamtree-org/twreact-ui/dist/index.css";
|
|
766
|
+
@import "@dreamtree-org/twreact-ui/dist/tokens/brands/practicepro.css"; /* or your own --dt-* overrides */
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**Use the same tokens in your own classes:**
|
|
573
770
|
|
|
574
771
|
```js
|
|
575
|
-
// tailwind.config.js
|
|
772
|
+
// Tailwind 3 — tailwind.config.js
|
|
576
773
|
module.exports = {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
600: '#16a34a',
|
|
592
|
-
},
|
|
593
|
-
error: {
|
|
594
|
-
50: '#fef2f2',
|
|
595
|
-
100: '#fee2e2',
|
|
596
|
-
500: '#ef4444',
|
|
597
|
-
600: '#dc2626',
|
|
598
|
-
},
|
|
599
|
-
warning: {
|
|
600
|
-
50: '#fffbeb',
|
|
601
|
-
100: '#fef3c7',
|
|
602
|
-
500: '#f59e0b',
|
|
603
|
-
600: '#d97706',
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
774
|
+
presets: [require('@dreamtree-org/twreact-ui/dist/tokens/tailwind3-preset.cjs')],
|
|
775
|
+
content: ['./src/**/*.{js,jsx,ts,tsx}', './node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}'],
|
|
776
|
+
};
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
```css
|
|
780
|
+
/* Tailwind 4 — globals.css */
|
|
781
|
+
@layer theme, base, twreact, components, utilities;
|
|
782
|
+
@import "@dreamtree-org/twreact-ui/dist/index.css" layer(twreact);
|
|
783
|
+
@import "tailwindcss";
|
|
784
|
+
@import "@dreamtree-org/twreact-ui/dist/tokens/theme.css";
|
|
785
|
+
/* Generate twreact-ui's classes in your utilities layer too. Without it, an app class such as `hidden`
|
|
786
|
+
lands in a higher layer than twreact's `lg:flex` and responsive components (AppShell) break. */
|
|
787
|
+
@source "../node_modules/@dreamtree-org/twreact-ui/dist/index.esm.js";
|
|
609
788
|
```
|
|
610
789
|
|
|
790
|
+
This adds semantic utilities next to the palettes: `bg-surface`, `bg-background`, `text-foreground`, `text-muted`, `border-line`, `bg-shell`, `text-micro`, `rounded-control` and `rounded-card`. Add a brand by creating `tokens/brands/<name>.json`; it overrides only the steps and roles you list.
|
|
791
|
+
|
|
792
|
+
**Design rules:** [`DESIGN.md`](DESIGN.md) is the rule set for product screens and AI agents: tokens only, blocks first, loading, empty and error states, 390 px, no native dialogs. It is generated from the tokens (`npm run build:tokens`).
|
|
793
|
+
|
|
794
|
+
**Colour props are overrides, not defaults.** Without colour props, `ProgressBar`, `Checkbox`, `Rate` and `Skeleton` use token classes, so a brand file recolours them. Their HEX props (`color`, `bgColor`, `borderColor`, `activeColor`, `inactiveColor`) still work but are deprecated.
|
|
795
|
+
|
|
796
|
+
**SSR:** `Tabs`, `Tooltip` and `Switch` generate ids with `React.useId()`, so server-rendered pages (Next.js) hydrate without mismatches.
|
|
797
|
+
|
|
611
798
|
## Dark Mode
|
|
612
799
|
|
|
613
800
|
Enable dark mode with the built-in theme provider:
|