@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.
Files changed (215) hide show
  1. package/DESIGN.md +118 -0
  2. package/README.md +231 -44
  3. package/ai-skills/dreamtree-ui.md +98 -12
  4. package/dist/ai-schema.json +452 -17
  5. package/dist/components/blocks/AppShell.d.ts +4 -0
  6. package/dist/components/blocks/AppShell.d.ts.map +1 -0
  7. package/dist/components/blocks/ConfirmDialog.d.ts +4 -0
  8. package/dist/components/blocks/ConfirmDialog.d.ts.map +1 -0
  9. package/dist/components/blocks/EmptyState.d.ts +4 -0
  10. package/dist/components/blocks/EmptyState.d.ts.map +1 -0
  11. package/dist/components/blocks/ErrorState.d.ts +4 -0
  12. package/dist/components/blocks/ErrorState.d.ts.map +1 -0
  13. package/dist/components/blocks/LoadingState.d.ts +9 -0
  14. package/dist/components/blocks/LoadingState.d.ts.map +1 -0
  15. package/dist/components/blocks/PageHeader.d.ts +4 -0
  16. package/dist/components/blocks/PageHeader.d.ts.map +1 -0
  17. package/dist/components/blocks/StatCard.d.ts +4 -0
  18. package/dist/components/blocks/StatCard.d.ts.map +1 -0
  19. package/dist/components/blocks/StatusPill.d.ts +5 -0
  20. package/dist/components/blocks/StatusPill.d.ts.map +1 -0
  21. package/dist/components/core/Accordion.d.ts.map +1 -1
  22. package/dist/components/core/Button.d.ts.map +1 -1
  23. package/dist/components/core/Checkbox.d.ts.map +1 -1
  24. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  25. package/dist/components/core/DatePicker.d.ts.map +1 -1
  26. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  27. package/dist/components/core/Form.d.ts.map +1 -1
  28. package/dist/components/core/Input.d.ts.map +1 -1
  29. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  30. package/dist/components/core/Popover.d.ts +4 -0
  31. package/dist/components/core/Popover.d.ts.map +1 -0
  32. package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
  33. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  34. package/dist/components/core/Radio.d.ts.map +1 -1
  35. package/dist/components/core/Rate.d.ts.map +1 -1
  36. package/dist/components/core/Select.d.ts.map +1 -1
  37. package/dist/components/core/Skeleton.d.ts.map +1 -1
  38. package/dist/components/core/Slider.d.ts +4 -0
  39. package/dist/components/core/Slider.d.ts.map +1 -0
  40. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  41. package/dist/components/core/Switch.d.ts.map +1 -1
  42. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  43. package/dist/components/core/Table/ColumnMenu.d.ts +2 -1
  44. package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
  45. package/dist/components/core/Table/MobileCard.d.ts +2 -2
  46. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  47. package/dist/components/core/Table/Table.d.ts.map +1 -1
  48. package/dist/components/core/Table/TableRow.d.ts +4 -2
  49. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  50. package/dist/components/core/Tabs.d.ts.map +1 -1
  51. package/dist/components/core/Textarea.d.ts +4 -0
  52. package/dist/components/core/Textarea.d.ts.map +1 -0
  53. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  54. package/dist/components/core/Tooltip.d.ts.map +1 -1
  55. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  56. package/dist/components/feedback/Drawer.d.ts +4 -0
  57. package/dist/components/feedback/Drawer.d.ts.map +1 -0
  58. package/dist/components/feedback/Toast.d.ts +14 -5
  59. package/dist/components/feedback/Toast.d.ts.map +1 -1
  60. package/dist/components/feedback/Tour.d.ts +4 -0
  61. package/dist/components/feedback/Tour.d.ts.map +1 -0
  62. package/dist/components/feedback/TourSpotlight.d.ts +9 -0
  63. package/dist/components/feedback/TourSpotlight.d.ts.map +1 -0
  64. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  65. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  66. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  67. package/dist/components/navigation/Sidebar.d.ts +8 -2
  68. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  69. package/dist/components/utility/Badge.d.ts.map +1 -1
  70. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  71. package/dist/components/utility/Sortable.d.ts +4 -0
  72. package/dist/components/utility/Sortable.d.ts.map +1 -0
  73. package/dist/esm/_vendor/object-assign/index.js +99 -0
  74. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.development.js +190 -0
  75. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.production.min.js +26 -0
  76. package/dist/esm/_vendor/prop-types/_vendor/react-is/index.js +19 -0
  77. package/dist/esm/_vendor/prop-types/checkPropTypes.js +115 -0
  78. package/dist/esm/_vendor/prop-types/factoryWithThrowingShims.js +74 -0
  79. package/dist/esm/_vendor/prop-types/factoryWithTypeCheckers.js +625 -0
  80. package/dist/esm/_vendor/prop-types/index.js +30 -0
  81. package/dist/esm/_vendor/prop-types/lib/ReactPropTypesSecret.js +21 -0
  82. package/dist/esm/_vendor/prop-types/lib/has.js +11 -0
  83. package/dist/esm/_virtual/_commonjsHelpers.js +5 -0
  84. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
  85. package/dist/esm/_virtual/index.js +3 -0
  86. package/dist/esm/_virtual/index2.js +3 -0
  87. package/dist/esm/_virtual/react-is.development.js +3 -0
  88. package/dist/esm/_virtual/react-is.production.min.js +3 -0
  89. package/dist/esm/components/blocks/AppShell.js +205 -0
  90. package/dist/esm/components/blocks/ConfirmDialog.js +57 -0
  91. package/dist/esm/components/blocks/EmptyState.js +44 -0
  92. package/dist/esm/components/blocks/ErrorState.js +53 -0
  93. package/dist/esm/components/blocks/LoadingState.js +134 -0
  94. package/dist/esm/components/blocks/PageHeader.js +51 -0
  95. package/dist/esm/components/blocks/StatCard.js +79 -0
  96. package/dist/esm/components/blocks/StatusPill.js +55 -0
  97. package/dist/esm/components/core/Accordion.js +260 -0
  98. package/dist/esm/components/core/Button.js +59 -0
  99. package/dist/esm/components/core/Checkbox.js +188 -0
  100. package/dist/esm/components/core/ColorPicker.js +348 -0
  101. package/dist/esm/components/core/DatePicker.js +436 -0
  102. package/dist/esm/components/core/DateRangePicker.js +494 -0
  103. package/dist/esm/components/core/Form.js +522 -0
  104. package/dist/esm/components/core/Input.js +179 -0
  105. package/dist/esm/components/core/Loader.js +141 -0
  106. package/dist/esm/components/core/LocationPicker.js +161 -0
  107. package/dist/esm/components/core/Popover.js +171 -0
  108. package/dist/esm/components/core/PriceRangePicker.js +376 -0
  109. package/dist/esm/components/core/ProgressBar.js +152 -0
  110. package/dist/esm/components/core/Radio.js +156 -0
  111. package/dist/esm/components/core/Rate.js +197 -0
  112. package/dist/esm/components/core/RoundedTag.js +118 -0
  113. package/dist/esm/components/core/Select.js +857 -0
  114. package/dist/esm/components/core/Skeleton.js +100 -0
  115. package/dist/esm/components/core/Slider.js +134 -0
  116. package/dist/esm/components/core/SpeechToText.js +226 -0
  117. package/dist/esm/components/core/Switch.js +207 -0
  118. package/dist/esm/components/core/Table/ActionMenu.js +251 -0
  119. package/dist/esm/components/core/Table/ColumnMenu.js +55 -0
  120. package/dist/esm/components/core/Table/FieldInput.js +157 -0
  121. package/dist/esm/components/core/Table/MobileCard.js +187 -0
  122. package/dist/esm/components/core/Table/MobileFilters.js +72 -0
  123. package/dist/esm/components/core/Table/Pagination.js +157 -0
  124. package/dist/esm/components/core/Table/Table.js +1533 -0
  125. package/dist/esm/components/core/Table/TableRow.js +254 -0
  126. package/dist/esm/components/core/Table/constants.js +35 -0
  127. package/dist/esm/components/core/Table/helpers.js +124 -0
  128. package/dist/esm/components/core/Table/useClickOutside.js +26 -0
  129. package/dist/esm/components/core/Tabs.js +289 -0
  130. package/dist/esm/components/core/TextToSpeech.js +52 -0
  131. package/dist/esm/components/core/Textarea.js +125 -0
  132. package/dist/esm/components/core/ThreeDotPopover.js +160 -0
  133. package/dist/esm/components/core/Tooltip.js +372 -0
  134. package/dist/esm/components/feedback/Alert.js +96 -0
  135. package/dist/esm/components/feedback/Dialog.js +200 -0
  136. package/dist/esm/components/feedback/Drawer.js +171 -0
  137. package/dist/esm/components/feedback/Toast.js +362 -0
  138. package/dist/esm/components/feedback/Tour.js +214 -0
  139. package/dist/esm/components/feedback/TourSpotlight.js +71 -0
  140. package/dist/esm/components/navigation/Breadcrumbs.js +75 -0
  141. package/dist/esm/components/navigation/Dropdown.js +53 -0
  142. package/dist/esm/components/navigation/FootNav.js +51 -0
  143. package/dist/esm/components/navigation/Navbar.js +437 -0
  144. package/dist/esm/components/navigation/Sidebar.js +567 -0
  145. package/dist/esm/components/utility/Avatar.js +89 -0
  146. package/dist/esm/components/utility/Badge.js +47 -0
  147. package/dist/esm/components/utility/Card.js +85 -0
  148. package/dist/esm/components/utility/Carousel.js +97 -0
  149. package/dist/esm/components/utility/Condition.js +9 -0
  150. package/dist/esm/components/utility/FileUpload.js +177 -0
  151. package/dist/esm/components/utility/Pagination.js +111 -0
  152. package/dist/esm/components/utility/Sortable.js +190 -0
  153. package/dist/esm/components/utility/Stepper.js +101 -0
  154. package/dist/esm/hooks/useApi.js +169 -0
  155. package/dist/esm/hooks/useControllableState.js +32 -0
  156. package/dist/esm/hooks/useFocusTrap.js +85 -0
  157. package/dist/esm/hooks/useLatestRef.js +20 -0
  158. package/dist/esm/hooks/useMixins.js +100 -0
  159. package/dist/esm/hooks/useScrollLock.js +26 -0
  160. package/dist/esm/hooks/useSliderValues.js +135 -0
  161. package/dist/esm/hooks/useSortable.js +188 -0
  162. package/dist/esm/hooks/useTheme.js +53 -0
  163. package/dist/esm/hooks/useTourTarget.js +92 -0
  164. package/dist/esm/index.js +65 -0
  165. package/dist/esm/store/index.js +26 -0
  166. package/dist/esm/store/store.js +107 -0
  167. package/dist/esm/store/storeSlice.js +45 -0
  168. package/dist/esm/utils/cn.js +11 -0
  169. package/dist/esm/utils/emitter.js +169 -0
  170. package/dist/esm/utils/helpers.js +161 -0
  171. package/dist/esm/utils/position.js +134 -0
  172. package/dist/hooks/useApi.d.ts +2 -1
  173. package/dist/hooks/useApi.d.ts.map +1 -1
  174. package/dist/hooks/useControllableState.d.ts +11 -0
  175. package/dist/hooks/useControllableState.d.ts.map +1 -0
  176. package/dist/hooks/useFocusTrap.d.ts +15 -0
  177. package/dist/hooks/useFocusTrap.d.ts.map +1 -0
  178. package/dist/hooks/useLatestRef.d.ts +8 -0
  179. package/dist/hooks/useLatestRef.d.ts.map +1 -0
  180. package/dist/hooks/useMixins.d.ts.map +1 -1
  181. package/dist/hooks/useScrollLock.d.ts +2 -0
  182. package/dist/hooks/useScrollLock.d.ts.map +1 -0
  183. package/dist/hooks/useSliderValues.d.ts +43 -0
  184. package/dist/hooks/useSliderValues.d.ts.map +1 -0
  185. package/dist/hooks/useSortable.d.ts +32 -0
  186. package/dist/hooks/useSortable.d.ts.map +1 -0
  187. package/dist/hooks/useTheme.d.ts.map +1 -1
  188. package/dist/hooks/useTourTarget.d.ts +15 -0
  189. package/dist/hooks/useTourTarget.d.ts.map +1 -0
  190. package/dist/index.css +1 -1
  191. package/dist/index.d.ts +55 -39
  192. package/dist/index.esm.css +1 -1
  193. package/dist/index.esm.js +7937 -4659
  194. package/dist/index.js +7950 -4657
  195. package/dist/tokens/brands/default.css +1 -0
  196. package/dist/tokens/brands/default.json +5 -0
  197. package/dist/tokens/brands/practicepro.css +17 -0
  198. package/dist/tokens/brands/practicepro.json +27 -0
  199. package/dist/tokens/tailwind3-preset.cjs +214 -0
  200. package/dist/tokens/theme.css +193 -0
  201. package/dist/tokens/tokens.json +373 -0
  202. package/dist/tokens/vars.css +98 -0
  203. package/dist/types/blocks.d.ts +109 -0
  204. package/dist/types/blocks.d.ts.map +1 -0
  205. package/dist/types/index.d.ts +194 -5
  206. package/dist/types/index.d.ts.map +1 -1
  207. package/dist/utils/position.d.ts +30 -0
  208. package/dist/utils/position.d.ts.map +1 -0
  209. package/doc/README.md +26 -1
  210. package/mcp/catalog.snapshot.json +1643 -549
  211. package/package.json +24 -6
  212. package/tokens/brands/default.json +5 -0
  213. package/tokens/brands/practicepro.json +27 -0
  214. package/tokens/lib.cjs +128 -0
  215. 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
- Dreamtree UI supports custom theming through Tailwind CSS configuration:
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
- theme: {
578
- extend: {
579
- colors: {
580
- primary: {
581
- 50: '#eff6ff',
582
- 100: '#dbeafe',
583
- 500: '#3b82f6',
584
- 600: '#2563eb',
585
- 700: '#1d4ed8',
586
- },
587
- success: {
588
- 50: '#f0fdf4',
589
- 100: '#dcfce7',
590
- 500: '#22c55e',
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: