@dreamtree-org/twreact-ui 1.3.0 → 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 (184) hide show
  1. package/README.md +179 -10
  2. package/ai-skills/dreamtree-ui.md +71 -12
  3. package/dist/ai-schema.json +452 -17
  4. package/dist/components/core/Accordion.d.ts.map +1 -1
  5. package/dist/components/core/Button.d.ts.map +1 -1
  6. package/dist/components/core/Checkbox.d.ts.map +1 -1
  7. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  8. package/dist/components/core/DatePicker.d.ts.map +1 -1
  9. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  10. package/dist/components/core/Form.d.ts.map +1 -1
  11. package/dist/components/core/Input.d.ts.map +1 -1
  12. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  13. package/dist/components/core/Popover.d.ts +4 -0
  14. package/dist/components/core/Popover.d.ts.map +1 -0
  15. package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
  16. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  17. package/dist/components/core/Radio.d.ts.map +1 -1
  18. package/dist/components/core/Rate.d.ts.map +1 -1
  19. package/dist/components/core/Select.d.ts.map +1 -1
  20. package/dist/components/core/Skeleton.d.ts.map +1 -1
  21. package/dist/components/core/Slider.d.ts +4 -0
  22. package/dist/components/core/Slider.d.ts.map +1 -0
  23. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  24. package/dist/components/core/Switch.d.ts.map +1 -1
  25. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  26. package/dist/components/core/Table/ColumnMenu.d.ts +2 -1
  27. package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
  28. package/dist/components/core/Table/MobileCard.d.ts +2 -2
  29. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  30. package/dist/components/core/Table/Table.d.ts.map +1 -1
  31. package/dist/components/core/Table/TableRow.d.ts +4 -2
  32. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  33. package/dist/components/core/Tabs.d.ts.map +1 -1
  34. package/dist/components/core/Textarea.d.ts +4 -0
  35. package/dist/components/core/Textarea.d.ts.map +1 -0
  36. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  37. package/dist/components/core/Tooltip.d.ts.map +1 -1
  38. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  39. package/dist/components/feedback/Drawer.d.ts +4 -0
  40. package/dist/components/feedback/Drawer.d.ts.map +1 -0
  41. package/dist/components/feedback/Toast.d.ts +14 -5
  42. package/dist/components/feedback/Toast.d.ts.map +1 -1
  43. package/dist/components/feedback/Tour.d.ts +4 -0
  44. package/dist/components/feedback/Tour.d.ts.map +1 -0
  45. package/dist/components/feedback/TourSpotlight.d.ts +9 -0
  46. package/dist/components/feedback/TourSpotlight.d.ts.map +1 -0
  47. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  48. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  49. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  50. package/dist/components/navigation/Sidebar.d.ts +8 -2
  51. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  52. package/dist/components/utility/Badge.d.ts.map +1 -1
  53. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  54. package/dist/components/utility/Sortable.d.ts +4 -0
  55. package/dist/components/utility/Sortable.d.ts.map +1 -0
  56. package/dist/esm/_vendor/object-assign/index.js +99 -0
  57. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.development.js +190 -0
  58. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.production.min.js +26 -0
  59. package/dist/esm/_vendor/prop-types/_vendor/react-is/index.js +19 -0
  60. package/dist/esm/_vendor/prop-types/checkPropTypes.js +115 -0
  61. package/dist/esm/_vendor/prop-types/factoryWithThrowingShims.js +74 -0
  62. package/dist/esm/_vendor/prop-types/factoryWithTypeCheckers.js +625 -0
  63. package/dist/esm/_vendor/prop-types/index.js +30 -0
  64. package/dist/esm/_vendor/prop-types/lib/ReactPropTypesSecret.js +21 -0
  65. package/dist/esm/_vendor/prop-types/lib/has.js +11 -0
  66. package/dist/esm/_virtual/_commonjsHelpers.js +5 -0
  67. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
  68. package/dist/esm/_virtual/index.js +3 -0
  69. package/dist/esm/_virtual/index2.js +3 -0
  70. package/dist/esm/_virtual/react-is.development.js +3 -0
  71. package/dist/esm/_virtual/react-is.production.min.js +3 -0
  72. package/dist/esm/components/blocks/AppShell.js +205 -0
  73. package/dist/esm/components/blocks/ConfirmDialog.js +57 -0
  74. package/dist/esm/components/blocks/EmptyState.js +44 -0
  75. package/dist/esm/components/blocks/ErrorState.js +53 -0
  76. package/dist/esm/components/blocks/LoadingState.js +134 -0
  77. package/dist/esm/components/blocks/PageHeader.js +51 -0
  78. package/dist/esm/components/blocks/StatCard.js +79 -0
  79. package/dist/esm/components/blocks/StatusPill.js +55 -0
  80. package/dist/esm/components/core/Accordion.js +260 -0
  81. package/dist/esm/components/core/Button.js +59 -0
  82. package/dist/esm/components/core/Checkbox.js +188 -0
  83. package/dist/esm/components/core/ColorPicker.js +348 -0
  84. package/dist/esm/components/core/DatePicker.js +436 -0
  85. package/dist/esm/components/core/DateRangePicker.js +494 -0
  86. package/dist/esm/components/core/Form.js +522 -0
  87. package/dist/esm/components/core/Input.js +179 -0
  88. package/dist/esm/components/core/Loader.js +141 -0
  89. package/dist/esm/components/core/LocationPicker.js +161 -0
  90. package/dist/esm/components/core/Popover.js +171 -0
  91. package/dist/esm/components/core/PriceRangePicker.js +376 -0
  92. package/dist/esm/components/core/ProgressBar.js +152 -0
  93. package/dist/esm/components/core/Radio.js +156 -0
  94. package/dist/esm/components/core/Rate.js +197 -0
  95. package/dist/esm/components/core/RoundedTag.js +118 -0
  96. package/dist/esm/components/core/Select.js +857 -0
  97. package/dist/esm/components/core/Skeleton.js +100 -0
  98. package/dist/esm/components/core/Slider.js +134 -0
  99. package/dist/esm/components/core/SpeechToText.js +226 -0
  100. package/dist/esm/components/core/Switch.js +207 -0
  101. package/dist/esm/components/core/Table/ActionMenu.js +251 -0
  102. package/dist/esm/components/core/Table/ColumnMenu.js +55 -0
  103. package/dist/esm/components/core/Table/FieldInput.js +157 -0
  104. package/dist/esm/components/core/Table/MobileCard.js +187 -0
  105. package/dist/esm/components/core/Table/MobileFilters.js +72 -0
  106. package/dist/esm/components/core/Table/Pagination.js +157 -0
  107. package/dist/esm/components/core/Table/Table.js +1533 -0
  108. package/dist/esm/components/core/Table/TableRow.js +254 -0
  109. package/dist/esm/components/core/Table/constants.js +35 -0
  110. package/dist/esm/components/core/Table/helpers.js +124 -0
  111. package/dist/esm/components/core/Table/useClickOutside.js +26 -0
  112. package/dist/esm/components/core/Tabs.js +289 -0
  113. package/dist/esm/components/core/TextToSpeech.js +52 -0
  114. package/dist/esm/components/core/Textarea.js +125 -0
  115. package/dist/esm/components/core/ThreeDotPopover.js +160 -0
  116. package/dist/esm/components/core/Tooltip.js +372 -0
  117. package/dist/esm/components/feedback/Alert.js +96 -0
  118. package/dist/esm/components/feedback/Dialog.js +200 -0
  119. package/dist/esm/components/feedback/Drawer.js +171 -0
  120. package/dist/esm/components/feedback/Toast.js +362 -0
  121. package/dist/esm/components/feedback/Tour.js +214 -0
  122. package/dist/esm/components/feedback/TourSpotlight.js +71 -0
  123. package/dist/esm/components/navigation/Breadcrumbs.js +75 -0
  124. package/dist/esm/components/navigation/Dropdown.js +53 -0
  125. package/dist/esm/components/navigation/FootNav.js +51 -0
  126. package/dist/esm/components/navigation/Navbar.js +437 -0
  127. package/dist/esm/components/navigation/Sidebar.js +567 -0
  128. package/dist/esm/components/utility/Avatar.js +89 -0
  129. package/dist/esm/components/utility/Badge.js +47 -0
  130. package/dist/esm/components/utility/Card.js +85 -0
  131. package/dist/esm/components/utility/Carousel.js +97 -0
  132. package/dist/esm/components/utility/Condition.js +9 -0
  133. package/dist/esm/components/utility/FileUpload.js +177 -0
  134. package/dist/esm/components/utility/Pagination.js +111 -0
  135. package/dist/esm/components/utility/Sortable.js +190 -0
  136. package/dist/esm/components/utility/Stepper.js +101 -0
  137. package/dist/esm/hooks/useApi.js +169 -0
  138. package/dist/esm/hooks/useControllableState.js +32 -0
  139. package/dist/esm/hooks/useFocusTrap.js +85 -0
  140. package/dist/esm/hooks/useLatestRef.js +20 -0
  141. package/dist/esm/hooks/useMixins.js +100 -0
  142. package/dist/esm/hooks/useScrollLock.js +26 -0
  143. package/dist/esm/hooks/useSliderValues.js +135 -0
  144. package/dist/esm/hooks/useSortable.js +188 -0
  145. package/dist/esm/hooks/useTheme.js +53 -0
  146. package/dist/esm/hooks/useTourTarget.js +92 -0
  147. package/dist/esm/index.js +65 -0
  148. package/dist/esm/store/index.js +26 -0
  149. package/dist/esm/store/store.js +107 -0
  150. package/dist/esm/store/storeSlice.js +45 -0
  151. package/dist/esm/utils/cn.js +11 -0
  152. package/dist/esm/utils/emitter.js +169 -0
  153. package/dist/esm/utils/helpers.js +161 -0
  154. package/dist/esm/utils/position.js +134 -0
  155. package/dist/hooks/useApi.d.ts +2 -1
  156. package/dist/hooks/useApi.d.ts.map +1 -1
  157. package/dist/hooks/useControllableState.d.ts +11 -0
  158. package/dist/hooks/useControllableState.d.ts.map +1 -0
  159. package/dist/hooks/useFocusTrap.d.ts +15 -0
  160. package/dist/hooks/useFocusTrap.d.ts.map +1 -0
  161. package/dist/hooks/useLatestRef.d.ts +8 -0
  162. package/dist/hooks/useLatestRef.d.ts.map +1 -0
  163. package/dist/hooks/useMixins.d.ts.map +1 -1
  164. package/dist/hooks/useScrollLock.d.ts +2 -0
  165. package/dist/hooks/useScrollLock.d.ts.map +1 -0
  166. package/dist/hooks/useSliderValues.d.ts +43 -0
  167. package/dist/hooks/useSliderValues.d.ts.map +1 -0
  168. package/dist/hooks/useSortable.d.ts +32 -0
  169. package/dist/hooks/useSortable.d.ts.map +1 -0
  170. package/dist/hooks/useTheme.d.ts.map +1 -1
  171. package/dist/hooks/useTourTarget.d.ts +15 -0
  172. package/dist/hooks/useTourTarget.d.ts.map +1 -0
  173. package/dist/index.css +1 -1
  174. package/dist/index.d.ts +8 -2
  175. package/dist/index.esm.css +1 -1
  176. package/dist/index.esm.js +4800 -2212
  177. package/dist/index.js +4805 -2210
  178. package/dist/types/index.d.ts +193 -5
  179. package/dist/types/index.d.ts.map +1 -1
  180. package/dist/utils/position.d.ts +30 -0
  181. package/dist/utils/position.d.ts.map +1 -0
  182. package/doc/README.md +7 -1
  183. package/mcp/catalog.snapshot.json +911 -212
  184. package/package.json +14 -2
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,6 +680,35 @@ 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
+
570
712
  ### Page blocks
571
713
 
572
714
  Page-level patterns extracted from the Dreamtree products. Build pages from these before composing layouts by hand.
@@ -590,6 +732,29 @@ import { PageHeader, StatCard, StatusPill, EmptyState, Button } from '@dreamtree
590
732
 
591
733
  Component types now carry their props (`<Button variant="bogus" />` is a type error).
592
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
+
593
758
  ## Theming
594
759
 
595
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.
@@ -626,6 +791,10 @@ This adds semantic utilities next to the palettes: `bg-surface`, `bg-background`
626
791
 
627
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`).
628
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
+
629
798
  ## Dark Mode
630
799
 
631
800
  Enable dark mode with the built-in theme provider:
@@ -63,6 +63,14 @@ module.exports = {
63
63
  };
64
64
  ```
65
65
 
66
+ Import from the package root only (`import { Button } from '@dreamtree-org/twreact-ui'`).
67
+ The root import tree-shakes, so do not suggest deep paths into `dist/` or `src/` for components.
68
+
69
+ **Next.js App Router:** components ship with `'use client'`, so a Server Component can import
70
+ them directly. Do not generate a `"use client"` wrapper file that re-exports the library.
71
+ Compound members (`Card.Header`, `Tabs.List`, `Dialog.Footer`) and function props (`onClick`)
72
+ still require a `'use client'` file. `cn`, `Helpers` and `Emitter` also work on the server.
73
+
66
74
  ## Live prop contracts via MCP (prefer this over guessing)
67
75
 
68
76
  An **MCP server bundled in this package** serves the authoritative prop
@@ -99,10 +107,10 @@ The library exports four kinds of things from `@dreamtree-org/twreact-ui`:
99
107
 
100
108
  | Group | Components |
101
109
  | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
102
- | **Core** | `Input`, `Button`, `Select`, `Table`, `Form`, `Accordion`, `Checkbox`, `ColorPicker`, `DatePicker`, `DateRangePicker`, `Loader`, `LocationPicker`, `PriceRangePicker`, `ProgressBar`, `Radio`, `Rate`, `RoundedTag`, `Skeleton`, `Switch`, `Tabs`, `ThreeDotPopover`, `Tooltip`, `SpeechToText`, `TextToSpeech` |
110
+ | **Core** | `Input`, `Button`, `Select`, `Table`, `Form`, `Accordion`, `Checkbox`, `ColorPicker`, `DatePicker`, `DateRangePicker`, `Loader`, `LocationPicker`, `PriceRangePicker`, `ProgressBar`, `Radio`, `Rate`, `RoundedTag`, `Skeleton`, `Switch`, `Tabs`, `ThreeDotPopover`, `Tooltip`, `SpeechToText`, `TextToSpeech`, `Textarea`, `Slider`, `Popover` |
103
111
  | **Navigation** | `Sidebar`, `Navbar`, `FootNav`, `Breadcrumbs` |
104
- | **Feedback** | `Dialog`, `Toast` (+ `ToastContainer`, `useToast`), `Alert` |
105
- | **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel` |
112
+ | **Feedback** | `Dialog`, `Toast` (+ `ToastProvider`, `ToastContainer`, `useToast`), `Alert`, `Drawer` (side sheet; `isOpen`/`onClose` like Dialog), `Tour` (onboarding spotlight) |
113
+ | **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel`, `Sortable` (drag + keyboard reorder) |
106
114
  | **Blocks** | `AppShell`, `PageHeader`, `StatCard`, `StatusPill`, `EmptyState`, `ErrorState`, `LoadingState`, `ConfirmDialog` — page-level patterns; build pages from these before composing layouts by hand |
107
115
 
108
116
  ### Hooks
@@ -111,7 +119,7 @@ The library exports four kinds of things from `@dreamtree-org/twreact-ui`:
111
119
  | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
112
120
  | `useTheme` | Read/write the current theme (`light | dark`); pairs with `<ThemeProvider>`. Throws if used outside the provider. |
113
121
  | `ThemeProvider` | Provider that persists theme to `localStorage` (`dreamtree-theme`) and toggles `data-theme` on `<html>`. |
114
- | `useApi` | Thin axios wrapper. Returns `{data, error, loading, sendRequest}`. Accepts `BASE_URL`, `DEFAULT_HEADERS`, `apiMap`. |
122
+ | `useApi` | Thin axios wrapper. Returns `{data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith}`. Accepts `BASE_URL`, `DEFAULT_HEADERS`, `apiMap`. |
115
123
  | `useMixins` | Bridge between component state, Redux store, and an in-memory cache. Power-user hook — prefer local React state for new code. |
116
124
 
117
125
  ### Store
@@ -149,8 +157,9 @@ Components use a **shared variant/size/focus vocabulary**:
149
157
 
150
158
  - **Variants:** `primary | secondary | outline | ghost | destructive | success | warning`
151
159
  (component-specific extras like `Badge: info` and `Alert: neutral` are documented per-component).
152
- - **Sizes:** `xs | sm | md | lg | xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14`
153
- and padding `px-2 / px-3 / px-4 / px-6 / px-8` (a component MAY support a subset).
160
+ - **Sizes:** `xs | sm | md | lg | xl | 2xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14 / h-16`
161
+ and padding `px-2 / px-3 / px-4 / px-6 / px-8 / px-10` (`Button` implements all six;
162
+ other components MAY support a subset and fall back to `md`).
154
163
  - **Focus ring:** `focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500`
155
164
  (the color token swaps for destructive / etc.).
156
165
  - **Dark mode:** Tailwind `darkMode: 'class'`. Every color choice has a `dark:` counterpart.
@@ -162,12 +171,22 @@ When suggesting a component or new variant value, use this exact vocabulary.
162
171
  **Blocks** are page-level patterns extracted from the products, so screens stop re-inventing them:
163
172
 
164
173
  - `AppShell` — sidebar + top bar + mobile drawer; pass `nav` items and `linkAs={Link}` in Next.js.
174
+ - `Sidebar` — every item renders as an `<a>`; give items an `href` (and `linkAs={Link}` in Next.js) so Ctrl/Cmd-click opens a new tab. Parents with `href` navigate + expand; the chevron toggles. Mark the current page `active: true` and its group opens on load (control groups with `expandedIds`/`defaultExpandedIds`/`onExpandedChange`). Top-level `group: 'Name'` adds section headings; `badge` (+ `badgeLabel: '3 unread'`) shows counts; `footer` pins links above the user block; `stickyOffset={64}` when a top bar sits above it. The mobile drawer (`isMobileOpen`/`setIsMobileOpen`) is inert when shut and closes on Escape.
165
175
  - `PageHeader` — the page's only `h1`, subtitle, one primary action, optional `back` link.
166
176
  - `StatCard` — KPI tile (`label`, `value`, `icon`, `tone`, `change`).
167
177
  - `StatusPill` — map domain states to `{ label, tone }` once; unknown states are humanized, never raw.
168
178
  - `EmptyState`, `ErrorState`, `LoadingState` — every data card needs all three.
169
179
  - `ConfirmDialog` — use instead of `window.confirm()`; title is the question, button is the verb.
170
180
 
181
+ **Interaction primitives** — reach for these before hand-rolling the pattern:
182
+
183
+ - `Drawer` — side/edge sheet for record peeks and editors (`isOpen`, `onClose`, `title`, `placement`, `size`, `footer`); don't fake one with `Dialog size="lg"`.
184
+ - `Popover` — click-triggered panel with controls (`trigger={<Button>…</Button>}`, children or `({ close }) => …`); `Tooltip` is for read-only hover text only.
185
+ - `Textarea` — same API as `Input` plus `autoResize`/`maxRows`; never a raw `<textarea>`.
186
+ - `Slider` — `defaultValue={40}` for one thumb, `[low, high]` for a range; `onChangeEnd` to save after a drag.
187
+ - `Sortable` — `items` + `onReorder(newItems)` + `renderItem`; keyboard reordering and announcements are built in. Pass `handle` and spread `handleProps` on a grip for touch screens.
188
+ - `Tour` — onboarding coach marks: `steps=[{ target: '[data-tour="x"]', title, content, placement }]`, `open`, `onClose(reason)`; a missing target falls back to a centred card.
189
+
171
190
  **Tokens:** colours are CSS variables (`--dt-primary-600`, `--dt-surface`, …) generated from
172
191
  `tokens/tokens.json`. Use semantic classes (`bg-primary-600`, `bg-surface`, `text-foreground`,
173
192
  `text-muted`, `border-line`, `text-micro`, `rounded-card`) — never hex values or Tailwind's raw
@@ -216,17 +235,48 @@ import { Input } from '@dreamtree-org/twreact-ui';
216
235
  `Form` integrates with `react-hook-form` + `yup` (`@hookform/resolvers`).
217
236
  Validation rules live in consumer code.
218
237
 
238
+ **Helper text:** `Input`, `Select`, `DatePicker`, `DateRangePicker` and every
239
+ `Form` field take `helperText` (rendered below, linked via `aria-describedby`).
240
+ While `error` is set it **replaces** the helper text — don't render both. Don't
241
+ hand-roll a `<p>` hint under a field.
242
+
243
+ ```jsx
244
+ <Select label="Role" helperText="Controls access." options={roles} value={role} onChange={setRole} />
245
+ <Form fields={[
246
+ { type: 'email', name: 'email', label: 'Email', helperText: 'Receipts only.' },
247
+ { type: 'color', name: 'brand', label: 'Brand colour', swatches: ['#2563eb'] }, // ColorPicker
248
+ ]} />
249
+ ```
250
+
251
+ `Form` field types: `text | email | password | number | tel | url | select |
252
+ date | textarea | checkbox | radio | file | switch | color | custom`. Use
253
+ `type: 'color'` rather than a `custom` field wrapping `ColorPicker`.
254
+
255
+ **Select `grouped`** accepts `[{ label, options }]` groups **or** flat options
256
+ with a `group` key (`{ value, label, group: 'Code' }`) — no need to pre-group.
257
+
258
+ **Navbar** takes a `center` slot (search field / primary action) and shows
259
+ `user.name` initials when `user.avatar` is missing; it fits a 390px phone.
260
+
261
+ **Tooltip** around a component (e.g. `<Button>`) in a row/grid: use
262
+ `customTrigger={<Button>…</Button>}` so there is one tab stop, not two. A lone
263
+ native `<button>` / `<a href>` / `<input>` child is detected automatically.
264
+
219
265
  ### Toast
220
266
 
267
+ Wrap the app ONCE in `ToastProvider`; every `useToast()` below it shares one
268
+ queue and the provider renders it. (Without a provider each `useToast()` owns a
269
+ private queue that only shows where that same hook's `toasts` are passed to a
270
+ `<ToastContainer toasts={toasts} onRemove={removeToast} />`.)
271
+
221
272
  ```jsx
222
- import { ToastContainer, useToast } from '@dreamtree-org/twreact-ui';
273
+ import { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';
223
274
 
224
275
  function App() {
225
276
  return (
226
- <>
227
- <ToastContainer />
277
+ <ToastProvider position="top-right">
228
278
  <YourApp />
229
- </>
279
+ </ToastProvider>
230
280
  );
231
281
  }
232
282
 
@@ -256,14 +306,23 @@ toggles `data-theme` on `<html>`.
256
306
  ```jsx
257
307
  import { useApi } from '@dreamtree-org/twreact-ui';
258
308
 
259
- const { data, error, loading, sendRequest } = useApi({
309
+ const { data, error, loading, fetchData, fetchApi, fetchApiWith } = useApi({
260
310
  BASE_URL: 'https://api.example.com',
261
311
  DEFAULT_HEADERS: { Authorization: `Bearer ${token}` },
312
+ apiMap: {
313
+ getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),
314
+ updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),
315
+ },
262
316
  });
263
317
 
264
- useEffect(() => { sendRequest('GET', '/users'); }, []);
318
+ useEffect(() => { fetchData('/users', 'GET'); }, []);
319
+ fetchApi('getUser', 7); // apiMap.getUser(7) — ONE argument
320
+ fetchApiWith('updateUser', [7, { name: 'A' }]); // apiMap.updateUser(7, {...}) — spread
265
321
  ```
266
322
 
323
+ `fetchApi(key, data)` always passes the map entry exactly one argument; use
324
+ `fetchApiWith(key, [a, b])` for multi-argument entries.
325
+
267
326
  Treat `useApi` as a thin axios wrapper, not a caching/dedup layer. For
268
327
  cache, the consumer brings their own (React Query, SWR, etc.).
269
328