@dreamtree-org/twreact-ui 1.3.0 → 1.3.2

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 (234) hide show
  1. package/DESIGN.md +2 -2
  2. package/README.md +258 -10
  3. package/ai-skills/dreamtree-ui.md +111 -15
  4. package/charts/package.json +5 -0
  5. package/dist/_shared/cn-DPPQUJu1.js +138 -0
  6. package/dist/ai-schema.json +1252 -179
  7. package/dist/charts/index.d.ts +9 -0
  8. package/dist/charts/index.d.ts.map +1 -0
  9. package/dist/charts/index.js +769 -0
  10. package/dist/components/charts/AreaChart.d.ts +4 -0
  11. package/dist/components/charts/AreaChart.d.ts.map +1 -0
  12. package/dist/components/charts/BarChart.d.ts +4 -0
  13. package/dist/components/charts/BarChart.d.ts.map +1 -0
  14. package/dist/components/charts/CartesianChart.d.ts +5 -0
  15. package/dist/components/charts/CartesianChart.d.ts.map +1 -0
  16. package/dist/components/charts/ChartFrame.d.ts +18 -0
  17. package/dist/components/charts/ChartFrame.d.ts.map +1 -0
  18. package/dist/components/charts/DonutChart.d.ts +4 -0
  19. package/dist/components/charts/DonutChart.d.ts.map +1 -0
  20. package/dist/components/charts/LineChart.d.ts +4 -0
  21. package/dist/components/charts/LineChart.d.ts.map +1 -0
  22. package/dist/components/charts/Sparkline.d.ts +5 -0
  23. package/dist/components/charts/Sparkline.d.ts.map +1 -0
  24. package/dist/components/charts/chartTheme.d.ts +26 -0
  25. package/dist/components/charts/chartTheme.d.ts.map +1 -0
  26. package/dist/components/core/Accordion.d.ts.map +1 -1
  27. package/dist/components/core/Button.d.ts.map +1 -1
  28. package/dist/components/core/Checkbox.d.ts.map +1 -1
  29. package/dist/components/core/ColorPicker.d.ts.map +1 -1
  30. package/dist/components/core/DatePicker.d.ts.map +1 -1
  31. package/dist/components/core/DateRangePicker.d.ts.map +1 -1
  32. package/dist/components/core/Form.d.ts.map +1 -1
  33. package/dist/components/core/Input.d.ts.map +1 -1
  34. package/dist/components/core/LocationPicker.d.ts.map +1 -1
  35. package/dist/components/core/Popover.d.ts +4 -0
  36. package/dist/components/core/Popover.d.ts.map +1 -0
  37. package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
  38. package/dist/components/core/ProgressBar.d.ts.map +1 -1
  39. package/dist/components/core/Radio.d.ts.map +1 -1
  40. package/dist/components/core/Rate.d.ts.map +1 -1
  41. package/dist/components/core/Select.d.ts.map +1 -1
  42. package/dist/components/core/Skeleton.d.ts.map +1 -1
  43. package/dist/components/core/Slider.d.ts +4 -0
  44. package/dist/components/core/Slider.d.ts.map +1 -0
  45. package/dist/components/core/SpeechToText.d.ts.map +1 -1
  46. package/dist/components/core/Switch.d.ts.map +1 -1
  47. package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
  48. package/dist/components/core/Table/ColumnMenu.d.ts +2 -1
  49. package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
  50. package/dist/components/core/Table/MobileCard.d.ts +2 -2
  51. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  52. package/dist/components/core/Table/Table.d.ts.map +1 -1
  53. package/dist/components/core/Table/TableRow.d.ts +4 -2
  54. package/dist/components/core/Table/TableRow.d.ts.map +1 -1
  55. package/dist/components/core/Tabs.d.ts.map +1 -1
  56. package/dist/components/core/Textarea.d.ts +4 -0
  57. package/dist/components/core/Textarea.d.ts.map +1 -0
  58. package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
  59. package/dist/components/core/Tooltip.d.ts.map +1 -1
  60. package/dist/components/feedback/Dialog.d.ts.map +1 -1
  61. package/dist/components/feedback/Drawer.d.ts +4 -0
  62. package/dist/components/feedback/Drawer.d.ts.map +1 -0
  63. package/dist/components/feedback/Toast.d.ts +14 -5
  64. package/dist/components/feedback/Toast.d.ts.map +1 -1
  65. package/dist/components/feedback/Tour.d.ts +4 -0
  66. package/dist/components/feedback/Tour.d.ts.map +1 -0
  67. package/dist/components/feedback/TourSpotlight.d.ts +9 -0
  68. package/dist/components/feedback/TourSpotlight.d.ts.map +1 -0
  69. package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
  70. package/dist/components/navigation/FootNav.d.ts.map +1 -1
  71. package/dist/components/navigation/Navbar.d.ts.map +1 -1
  72. package/dist/components/navigation/Sidebar.d.ts +8 -2
  73. package/dist/components/navigation/Sidebar.d.ts.map +1 -1
  74. package/dist/components/rich-text/RichTextEditor.d.ts +4 -0
  75. package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -0
  76. package/dist/components/rich-text/RichTextToolbar.d.ts +219 -0
  77. package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -0
  78. package/dist/components/utility/Badge.d.ts.map +1 -1
  79. package/dist/components/utility/FileUpload.d.ts.map +1 -1
  80. package/dist/components/utility/Sortable.d.ts +4 -0
  81. package/dist/components/utility/Sortable.d.ts.map +1 -0
  82. package/dist/esm/_vendor/object-assign/index.js +99 -0
  83. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.development.js +190 -0
  84. package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.production.min.js +26 -0
  85. package/dist/esm/_vendor/prop-types/_vendor/react-is/index.js +19 -0
  86. package/dist/esm/_vendor/prop-types/checkPropTypes.js +115 -0
  87. package/dist/esm/_vendor/prop-types/factoryWithThrowingShims.js +74 -0
  88. package/dist/esm/_vendor/prop-types/factoryWithTypeCheckers.js +625 -0
  89. package/dist/esm/_vendor/prop-types/index.js +30 -0
  90. package/dist/esm/_vendor/prop-types/lib/ReactPropTypesSecret.js +21 -0
  91. package/dist/esm/_vendor/prop-types/lib/has.js +11 -0
  92. package/dist/esm/_virtual/_commonjsHelpers.js +5 -0
  93. package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
  94. package/dist/esm/_virtual/index.js +3 -0
  95. package/dist/esm/_virtual/index2.js +3 -0
  96. package/dist/esm/_virtual/react-is.development.js +3 -0
  97. package/dist/esm/_virtual/react-is.production.min.js +3 -0
  98. package/dist/esm/charts/index.js +5 -0
  99. package/dist/esm/components/blocks/AppShell.js +205 -0
  100. package/dist/esm/components/blocks/ConfirmDialog.js +57 -0
  101. package/dist/esm/components/blocks/EmptyState.js +44 -0
  102. package/dist/esm/components/blocks/ErrorState.js +53 -0
  103. package/dist/esm/components/blocks/LoadingState.js +134 -0
  104. package/dist/esm/components/blocks/PageHeader.js +51 -0
  105. package/dist/esm/components/blocks/StatCard.js +79 -0
  106. package/dist/esm/components/blocks/StatusPill.js +55 -0
  107. package/dist/esm/components/charts/AreaChart.js +16 -0
  108. package/dist/esm/components/charts/BarChart.js +16 -0
  109. package/dist/esm/components/charts/CartesianChart.js +250 -0
  110. package/dist/esm/components/charts/ChartFrame.js +170 -0
  111. package/dist/esm/components/charts/DonutChart.js +119 -0
  112. package/dist/esm/components/charts/LineChart.js +16 -0
  113. package/dist/esm/components/charts/Sparkline.js +110 -0
  114. package/dist/esm/components/charts/chartTheme.js +109 -0
  115. package/dist/esm/components/core/Accordion.js +260 -0
  116. package/dist/esm/components/core/Button.js +59 -0
  117. package/dist/esm/components/core/Checkbox.js +188 -0
  118. package/dist/esm/components/core/ColorPicker.js +348 -0
  119. package/dist/esm/components/core/DatePicker.js +436 -0
  120. package/dist/esm/components/core/DateRangePicker.js +494 -0
  121. package/dist/esm/components/core/Form.js +522 -0
  122. package/dist/esm/components/core/Input.js +179 -0
  123. package/dist/esm/components/core/Loader.js +141 -0
  124. package/dist/esm/components/core/LocationPicker.js +161 -0
  125. package/dist/esm/components/core/Popover.js +171 -0
  126. package/dist/esm/components/core/PriceRangePicker.js +376 -0
  127. package/dist/esm/components/core/ProgressBar.js +152 -0
  128. package/dist/esm/components/core/Radio.js +156 -0
  129. package/dist/esm/components/core/Rate.js +197 -0
  130. package/dist/esm/components/core/RoundedTag.js +118 -0
  131. package/dist/esm/components/core/Select.js +857 -0
  132. package/dist/esm/components/core/Skeleton.js +100 -0
  133. package/dist/esm/components/core/Slider.js +134 -0
  134. package/dist/esm/components/core/SpeechToText.js +226 -0
  135. package/dist/esm/components/core/Switch.js +207 -0
  136. package/dist/esm/components/core/Table/ActionMenu.js +251 -0
  137. package/dist/esm/components/core/Table/ColumnMenu.js +55 -0
  138. package/dist/esm/components/core/Table/FieldInput.js +157 -0
  139. package/dist/esm/components/core/Table/MobileCard.js +187 -0
  140. package/dist/esm/components/core/Table/MobileFilters.js +72 -0
  141. package/dist/esm/components/core/Table/Pagination.js +157 -0
  142. package/dist/esm/components/core/Table/Table.js +1533 -0
  143. package/dist/esm/components/core/Table/TableRow.js +254 -0
  144. package/dist/esm/components/core/Table/constants.js +35 -0
  145. package/dist/esm/components/core/Table/helpers.js +124 -0
  146. package/dist/esm/components/core/Table/useClickOutside.js +26 -0
  147. package/dist/esm/components/core/Tabs.js +289 -0
  148. package/dist/esm/components/core/TextToSpeech.js +52 -0
  149. package/dist/esm/components/core/Textarea.js +125 -0
  150. package/dist/esm/components/core/ThreeDotPopover.js +160 -0
  151. package/dist/esm/components/core/Tooltip.js +372 -0
  152. package/dist/esm/components/feedback/Alert.js +96 -0
  153. package/dist/esm/components/feedback/Dialog.js +200 -0
  154. package/dist/esm/components/feedback/Drawer.js +171 -0
  155. package/dist/esm/components/feedback/Toast.js +362 -0
  156. package/dist/esm/components/feedback/Tour.js +214 -0
  157. package/dist/esm/components/feedback/TourSpotlight.js +71 -0
  158. package/dist/esm/components/navigation/Breadcrumbs.js +75 -0
  159. package/dist/esm/components/navigation/Dropdown.js +53 -0
  160. package/dist/esm/components/navigation/FootNav.js +51 -0
  161. package/dist/esm/components/navigation/Navbar.js +437 -0
  162. package/dist/esm/components/navigation/Sidebar.js +567 -0
  163. package/dist/esm/components/rich-text/RichTextEditor.js +244 -0
  164. package/dist/esm/components/rich-text/RichTextToolbar.js +397 -0
  165. package/dist/esm/components/utility/Avatar.js +89 -0
  166. package/dist/esm/components/utility/Badge.js +47 -0
  167. package/dist/esm/components/utility/Card.js +85 -0
  168. package/dist/esm/components/utility/Carousel.js +97 -0
  169. package/dist/esm/components/utility/Condition.js +9 -0
  170. package/dist/esm/components/utility/FileUpload.js +177 -0
  171. package/dist/esm/components/utility/Pagination.js +111 -0
  172. package/dist/esm/components/utility/Sortable.js +190 -0
  173. package/dist/esm/components/utility/Stepper.js +101 -0
  174. package/dist/esm/hooks/useApi.js +169 -0
  175. package/dist/esm/hooks/useControllableState.js +32 -0
  176. package/dist/esm/hooks/useFocusTrap.js +85 -0
  177. package/dist/esm/hooks/useLatestRef.js +20 -0
  178. package/dist/esm/hooks/useMixins.js +100 -0
  179. package/dist/esm/hooks/useScrollLock.js +26 -0
  180. package/dist/esm/hooks/useSliderValues.js +135 -0
  181. package/dist/esm/hooks/useSortable.js +188 -0
  182. package/dist/esm/hooks/useTheme.js +53 -0
  183. package/dist/esm/hooks/useTourTarget.js +92 -0
  184. package/dist/esm/index.js +65 -0
  185. package/dist/esm/rich-text/index.js +1 -0
  186. package/dist/esm/store/index.js +26 -0
  187. package/dist/esm/store/store.js +107 -0
  188. package/dist/esm/store/storeSlice.js +45 -0
  189. package/dist/esm/utils/cn.js +11 -0
  190. package/dist/esm/utils/emitter.js +169 -0
  191. package/dist/esm/utils/helpers.js +161 -0
  192. package/dist/esm/utils/position.js +134 -0
  193. package/dist/hooks/useApi.d.ts +2 -1
  194. package/dist/hooks/useApi.d.ts.map +1 -1
  195. package/dist/hooks/useControllableState.d.ts +11 -0
  196. package/dist/hooks/useControllableState.d.ts.map +1 -0
  197. package/dist/hooks/useFocusTrap.d.ts +15 -0
  198. package/dist/hooks/useFocusTrap.d.ts.map +1 -0
  199. package/dist/hooks/useLatestRef.d.ts +8 -0
  200. package/dist/hooks/useLatestRef.d.ts.map +1 -0
  201. package/dist/hooks/useMixins.d.ts.map +1 -1
  202. package/dist/hooks/useScrollLock.d.ts +2 -0
  203. package/dist/hooks/useScrollLock.d.ts.map +1 -0
  204. package/dist/hooks/useSliderValues.d.ts +43 -0
  205. package/dist/hooks/useSliderValues.d.ts.map +1 -0
  206. package/dist/hooks/useSortable.d.ts +32 -0
  207. package/dist/hooks/useSortable.d.ts.map +1 -0
  208. package/dist/hooks/useTheme.d.ts.map +1 -1
  209. package/dist/hooks/useTourTarget.d.ts +15 -0
  210. package/dist/hooks/useTourTarget.d.ts.map +1 -0
  211. package/dist/index.css +1 -1
  212. package/dist/index.d.ts +8 -2
  213. package/dist/index.esm.css +1 -1
  214. package/dist/index.esm.js +4817 -2216
  215. package/dist/index.js +4822 -2214
  216. package/dist/rich-text/index.d.ts +5 -0
  217. package/dist/rich-text/index.d.ts.map +1 -0
  218. package/dist/rich-text/index.js +654 -0
  219. package/dist/tokens/brands/studio.css +14 -0
  220. package/dist/tokens/brands/studio.json +20 -0
  221. package/dist/types/charts.d.ts +97 -0
  222. package/dist/types/charts.d.ts.map +1 -0
  223. package/dist/types/index.d.ts +193 -5
  224. package/dist/types/index.d.ts.map +1 -1
  225. package/dist/types/rich-text.d.ts +50 -0
  226. package/dist/types/rich-text.d.ts.map +1 -0
  227. package/dist/utils/position.d.ts +30 -0
  228. package/dist/utils/position.d.ts.map +1 -0
  229. package/doc/README.md +20 -1
  230. package/mcp/catalog.snapshot.json +2505 -800
  231. package/mcp/server-core.mjs +30 -1
  232. package/package.json +51 -3
  233. package/rich-text/package.json +5 -0
  234. package/tokens/brands/studio.json +20 -0
package/DESIGN.md CHANGED
@@ -41,7 +41,7 @@ rounded:
41
41
  spacing:
42
42
  grid: 4px
43
43
  steps: [4, 8, 12, 16, 20, 24, 32]
44
- brands: [default, practicepro]
44
+ brands: [default, practicepro, studio]
45
45
  ---
46
46
 
47
47
  <!-- Generated by twreact-ui scripts/build-tokens.mjs from tokens/tokens.json. Edit the tokens or this generator, not this file. -->
@@ -76,7 +76,7 @@ Every Dreamtree product screen, and every prototype generated by Dreamtree Studi
76
76
 
77
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
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`.
79
+ - Products change their look with a **brand file** (`tokens/brands/<product>.json`), which overrides `--dt-*` variables. Brands available: `default`, `practicepro`, `studio`.
80
80
 
81
81
  ## 3. Type, space and shape
82
82
 
package/README.md CHANGED
@@ -64,6 +64,67 @@ 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. The only subpaths are the two **optional entry points** below, which exist
83
+ because they need an extra dependency. Existing file paths (`dist/index.css`, `dist/tokens/*`, `tokens/*`,
84
+ `dist/index.esm.js`, `package.json`, …) still resolve through the `exports` map.
85
+
86
+ ### Next.js App Router
87
+
88
+ Every module that uses React starts with `'use client'`, so Server Components can import
89
+ components directly. You no longer need a `"use client"` wrapper file that re-exports the library:
90
+
91
+ ```tsx
92
+ // app/page.tsx — a Server Component
93
+ import { Button, cn } from '@dreamtree-org/twreact-ui';
94
+
95
+ export default function Page() {
96
+ return <Button className={cn('mt-4')}>Save</Button>;
97
+ }
98
+ ```
99
+
100
+ `cn`, `Helpers` and `Emitter` carry no directive, so they also run on the server. Two React
101
+ Server Component rules still apply. You cannot pass functions (`onClick`, `render`, …) from a
102
+ Server Component to these Client Components. You also cannot dot into a client export, so
103
+ `<Card.Header>`, `<Tabs.List>` and `<Dialog.Footer>` must be used inside a `'use client'` file.
104
+ In Vite, Rollup can log `Module level directives cause errors when bundled, "use client" … was ignored`.
105
+ This message is harmless. To hide it, filter `MODULE_LEVEL_DIRECTIVE` in `build.rollupOptions.onwarn`.
106
+
107
+ ### Optional entry points: charts and rich text
108
+
109
+ Charts and the rich-text editor wrap third-party libraries, so they live on their own entry points
110
+ and use **optional peer dependencies**. If you never import them you install nothing extra, and
111
+ nothing changes in your bundle — the root `@dreamtree-org/twreact-ui` import never touches them.
112
+
113
+ | Entry point | Exports | Install first |
114
+ |---|---|---|
115
+ | `@dreamtree-org/twreact-ui/charts` | `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, `Sparkline` | `npm install recharts` (recharts 2.15+ or 3.x; React 18 apps also `npm install react-is@18`) |
116
+ | `@dreamtree-org/twreact-ui/rich-text` | `RichTextEditor` | `npm install @tiptap/react @tiptap/pm @tiptap/starter-kit` (Tiptap 3) |
117
+
118
+ ```jsx
119
+ import { LineChart, DonutChart } from '@dreamtree-org/twreact-ui/charts';
120
+ import { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';
121
+ ```
122
+
123
+ Both ship ESM (with `'use client'`), CommonJS and TypeScript declarations, and resolve under every
124
+ TypeScript `moduleResolution` (`bundler`, `node16`/`nodenext` and legacy `node`). Their styles are
125
+ already in `dist/index.css`. Rich-text output is HTML from the user — **sanitize it on the server**
126
+ before you store or render it (see [doc/RichTextEditor.md](doc/RichTextEditor.md#security-sanitize-the-html-on-the-server)).
127
+
67
128
  ## Quick Start
68
129
 
69
130
  ```jsx
@@ -147,7 +208,7 @@ Versatile button component with multiple variants and states.
147
208
 
148
209
  **Props:**
149
210
  - `variant`: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'success' | 'warning' (default: 'primary')
150
- - `size`: 'sm' | 'md' | 'lg' (default: 'md')
211
+ - `size`: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' (default: 'md') — heights h-7 / h-8 / h-10 / h-12 / h-14 / h-16
151
212
  - `disabled`: boolean
152
213
  - `loading`: boolean
153
214
  - `leftIcon`: ReactNode
@@ -172,6 +233,7 @@ Enhanced text input with validation states, icons, and clear functionality.
172
233
  - `placeholder`: string
173
234
  - `error`: string
174
235
  - `success`: string
236
+ - `helperText`: ReactNode — hint below the input, linked via `aria-describedby`; replaced by `error` / `success` while shown
175
237
  - `disabled`: boolean
176
238
  - `required`: boolean
177
239
  - `clearable`: boolean
@@ -197,17 +259,18 @@ Enhanced text input with validation states, icons, and clear functionality.
197
259
  Advanced select component with search, multi-select, and grouping support.
198
260
 
199
261
  **Props:**
200
- - `options`: Array<{value: any, label: string, disabled?: boolean}>
262
+ - `options`: Array<{value: any, label: string, disabled?: boolean, group?: string}> (with `grouped`: also `[{ label, options }]` groups)
201
263
  - `value`: any | Array<any>
202
264
  - `onChange`: (value: any) => void
203
265
  - `placeholder`: string (default: 'Select an option...')
204
- - `label`: string
266
+ - `label`: string — associated with the trigger (accessible name = label + value)
205
267
  - `error`: string
268
+ - `helperText`: ReactNode — hint below the control; replaced by `error` while shown
206
269
  - `disabled`: boolean
207
270
  - `required`: boolean
208
271
  - `multiSelect`: boolean
209
272
  - `searchable`: boolean
210
- - `grouped`: boolean
273
+ - `grouped`: boolean — groups objects or flat options with a `group` key; malformed input is skipped (console warning), never thrown
211
274
  - `creatable`: boolean
212
275
  - `onCreateOption`: (value: string) => void
213
276
  - `onSearch`: (term: string) => void
@@ -241,6 +304,8 @@ Declarative form builder with validation support.
241
304
  - `resetText`: string (default: 'Reset')
242
305
  - `showReset`: boolean (default: true)
243
306
 
307
+ 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).
308
+
244
309
  ```jsx
245
310
  const formFields = [
246
311
  {
@@ -273,7 +338,8 @@ actions, and expandable row details.
273
338
  - `data`: Array<object>
274
339
  - `columns`: Array<Column>
275
340
  - `sortable`: boolean (default: true)
276
- - `filterable`: boolean
341
+ - `filterable`: boolean — per-column filter inputs; filter client-side data as you type (not in `serverSide` mode)
342
+ - `globalSearch`: boolean — search box; filters client-side data with or without `filterable` (skips `searchable: false` columns)
277
343
  - `selectable`: boolean
278
344
  - `pagination`: boolean
279
345
  - `pageSize`: number (default: 25)
@@ -281,7 +347,7 @@ actions, and expandable row details.
281
347
  - `onFilter`: (filters: object) => void
282
348
  - `onSelectionChange`: (selectedRows: object[]) => void
283
349
  - `onRowClick`: (row: object, index: number) => void
284
- - `onPageChange`: (page: number) => void
350
+ - `onPageChange`: (page: number) => void — also called when the table clamps a page that no longer exists after the data shrinks
285
351
  - `hasDetails`: boolean | ((row, index) => boolean)
286
352
  - `DetailsComponent`: ReactComponent
287
353
  - `withAction`: boolean (default: true)
@@ -295,6 +361,15 @@ actions, and expandable row details.
295
361
  - `onSave`: ({ updatedRows, newRows, allData, tableData }) => void | Promise<allData | { data: allData } | void>
296
362
  - `onEdit`: ({ rowKey, colKey, newValue, row, action, isNew }) => void
297
363
  - `showSerial`: boolean (default: true)
364
+ - `showToolbar`: boolean (default: true) — `false` hides the toolbar (reload, page size, result count, pager header) and the column picker; interactive save controls still render
365
+ - `showColumnPicker`: boolean (default: true) — hide just the column-picker button and its column
366
+ - `wrapCells`: boolean (default: false) — let every cell wrap; a column's own `wrap` wins
367
+ - `mobileMaxFields`: number — cap the fields shown on each mobile card (column order, after `hideOnMobile`)
368
+ - `serverSide` / `totalRecords`: in server-side mode the "start - end of total" counter reads `totalRecords`
369
+
370
+ Column extras: `searchable: false` keeps a column out of the global search
371
+ (ids, codes), `wrap: true` lets its text wrap instead of staying on one line, and
372
+ `hideOnMobile: true` leaves it off the mobile card view (desktop is unaffected).
298
373
 
299
374
  An `Action` is `{ name, label, icon, render?, onClick?, hidden?, disabled?,
300
375
  inline? }`. `label`, `icon`, `hidden` and `disabled` each accept a value **or** a
@@ -335,6 +410,7 @@ Calendar-based date selection with keyboard navigation.
335
410
  - `placeholder`: string (default: 'Select date...')
336
411
  - `label`: string
337
412
  - `error`: string
413
+ - `helperText`: ReactNode — hint below the trigger; replaced by `error` while shown
338
414
  - `disabled`: boolean
339
415
  - `required`: boolean
340
416
  - `minDate`: Date | string
@@ -367,22 +443,31 @@ Collapsible sidebar navigation with nested items.
367
443
  - `logo`: ReactNode
368
444
  - `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>}
369
445
  - `onUserClick`: (user: object) => void
446
+ - `linkAs`: ElementType (default: `"a"`) — e.g. Next.js `Link`
447
+ - `expandedIds` / `defaultExpandedIds`: string[] — open groups (controlled / uncontrolled); `onExpandedChange`: (ids: string[]) => void
448
+ - `footer`: ReactNode | ({ collapsed, isMobile, closeMobile }) => ReactNode — pinned region above the user block
449
+ - `stickyOffset`: number | string — height of a top bar above the sidebar (sticks below it, fits the viewport); `containerClassName` for the outer sticky wrapper
450
+ - `isMobileOpen` / `setIsMobileOpen`: mobile drawer — inert while closed; focus-trapped, Escape-to-close and focus-restoring while open
451
+
452
+ 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
453
 
371
454
  ```jsx
372
455
  const sidebarItems = [
373
456
  {
374
457
  id: 'dashboard',
375
458
  label: 'Dashboard',
459
+ href: '/dashboard',
376
460
  icon: <Home className="h-4 w-4" />,
377
461
  active: true
378
462
  },
379
463
  {
380
464
  id: 'users',
381
465
  label: 'Users',
466
+ href: '/users',
382
467
  icon: <Users className="h-4 w-4" />,
383
468
  children: [
384
- { id: 'all-users', label: 'All Users' },
385
- { id: 'new-user', label: 'Add New User' }
469
+ { id: 'all-users', label: 'All Users', href: '/users' },
470
+ { id: 'new-user', label: 'Add New User', href: '/users/new' }
386
471
  ]
387
472
  }
388
473
  ];
@@ -398,9 +483,10 @@ const sidebarItems = [
398
483
  Top navigation bar with user menu and notifications.
399
484
 
400
485
  **Props:**
401
- - `logo`: ReactNode
486
+ - `logo`: ReactNode (truncates on narrow screens)
487
+ - `center`: ReactNode — centre slot (e.g. a search field); shrinks first so the bar fits a 390px phone
402
488
  - `items`: Array<NavItem>
403
- - `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>}
489
+ - `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
490
  - `notifications`: Array<Notification>
405
491
  - `searchable`: boolean
406
492
  - `onSearch`: (term: string) => void
@@ -420,6 +506,38 @@ Top navigation bar with user menu and notifications.
420
506
  />
421
507
  ```
422
508
 
509
+ #### FootNav
510
+ Fixed bottom tab bar for phones (`md:hidden`). Uses the semantic `bg-surface` / `border-line` tokens, so it follows dark mode.
511
+
512
+ **Props:**
513
+ - `items`: Array<{ id, label, icon, onClick?, active?, badge? }>
514
+ - `className`: string
515
+
516
+ 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.
517
+
518
+ ```jsx
519
+ <FootNav
520
+ items={[
521
+ { id: 'home', label: 'Home', icon: <Home />, active: true, onClick: () => navigate('/') },
522
+ { id: 'inbox', label: 'Inbox', icon: <Inbox />, badge: 3, onClick: () => navigate('/inbox') },
523
+ ]}
524
+ />
525
+ ```
526
+
527
+ #### Breadcrumbs
528
+ Breadcrumb trail with an optional Home link.
529
+
530
+ **Props:**
531
+ - `items`: Array<{ id?, label, href?, icon?, onClick? }> — `id` is optional (used as the React key when present)
532
+ - `separator`: ReactNode
533
+ - `showHome`: boolean (default: true)
534
+ - `homeIcon`: ReactNode
535
+ - `onHomeClick`: () => void
536
+
537
+ ```jsx
538
+ <Breadcrumbs items={[{ label: 'Projects', href: '/projects' }, { label: 'Alpha' }]} />
539
+ ```
540
+
423
541
  ### Feedback Components
424
542
 
425
543
  #### Alert
@@ -455,6 +573,23 @@ Notification messages with different types, actions, bottom progress bar, and po
455
573
  - `onClose`: (id: string) => void
456
574
  - `position`: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center'
457
575
 
576
+ **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`:
577
+
578
+ ```jsx
579
+ import { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';
580
+
581
+ <ToastProvider position="top-right">
582
+ <App />
583
+ </ToastProvider>;
584
+
585
+ function SaveButton() {
586
+ const { toast } = useToast();
587
+ return <button onClick={() => toast.success('Saved')}>Save</button>;
588
+ }
589
+ ```
590
+
591
+ **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.
592
+
458
593
  ```jsx
459
594
  import { useToast, ToastContainer } from '@dreamtree-org/twreact-ui';
460
595
 
@@ -567,6 +702,90 @@ const tabItems = [
567
702
  <Tabs items={tabItems} variant="pills" />
568
703
  ```
569
704
 
705
+ ### Overlays, inputs and lists
706
+
707
+ 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`.
708
+
709
+ | Component | Group | Use for |
710
+ |---|---|---|
711
+ | [`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` |
712
+ | [`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"` |
713
+ | [`Textarea`](doc/Textarea.md) | Core | Multi-line input mirroring `Input`: `label`, `error`, `helperText`, `required`, `showCount`/`maxLength`, `autoResize`/`maxRows` |
714
+ | [`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*` |
715
+ | [`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` |
716
+ | [`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 |
717
+
718
+ ```jsx
719
+ import { Drawer, Popover, Textarea, Slider, Sortable, Tour, Button } from '@dreamtree-org/twreact-ui';
720
+
721
+ <Drawer isOpen={open} onClose={() => setOpen(false)} title="Edit task" footer={<Button onClick={save}>Save</Button>}>
722
+ <Textarea label="Description" autoResize showCount maxLength={500} />
723
+ </Drawer>
724
+
725
+ <Popover trigger={<Button variant="outline">Filters</Button>} title="Filters">
726
+ <Slider label="Budget" defaultValue={[2000, 8000]} min={0} max={10000} step={500} showValue />
727
+ </Popover>
728
+
729
+ <Sortable aria-label="Backlog" items={tasks} onReorder={setTasks} renderItem={(t) => t.title} />
730
+
731
+ <Tour open={tourOpen} steps={[{ title: 'Welcome' }, { target: '[data-tour="new-task"]', title: 'Capture work' }]} onClose={() => setTourOpen(false)} />
732
+ ```
733
+
734
+ ### Charts (optional entry point)
735
+
736
+ Import from `@dreamtree-org/twreact-ui/charts` after `npm install recharts`. A data-first API over
737
+ recharts: pass rows as `data`, the x-axis key as `index` and the series keys as `categories`.
738
+ Colours are design tokens (`'primary'`, `'success-600'`, …) resolved from the `--dt-*` variables,
739
+ so charts follow your theme and dark mode. Every chart is responsive, `forwardRef` + `className` +
740
+ `...rest`, a named `role="img"` (`title` / `description`) with a visually-hidden data table for
741
+ screen readers (`accessibleTable`), shows `emptyState` for empty data, and disables animation under
742
+ `prefers-reduced-motion`.
743
+
744
+ | Component | Use for |
745
+ |---|---|
746
+ | [`LineChart`](doc/LineChart.md) | Trends over time (`curveType`, `showDots`, `connectNulls`) |
747
+ | [`BarChart`](doc/BarChart.md) | Comparisons — grouped, `stacked` or `horizontal` |
748
+ | [`AreaChart`](doc/AreaChart.md) | Volume over time, optionally `stacked` |
749
+ | [`DonutChart`](doc/DonutChart.md) | Part-to-whole (`variant="donut"` or `"pie"`, `centerLabel`) |
750
+ | [`Sparkline`](doc/Sparkline.md) | Word-sized trend for stat cards and table cells |
751
+
752
+ Shared props: `valueFormatter`, `height`, `showLegend`, `showTooltip`, `showXAxis`, `showYAxis`,
753
+ `showGrid`, `colors`, `animate`.
754
+
755
+ ```jsx
756
+ import { LineChart, DonutChart, Sparkline } from '@dreamtree-org/twreact-ui/charts';
757
+
758
+ <LineChart
759
+ data={[{ month: 'Jan', revenue: 4200 }, { month: 'Feb', revenue: 5100 }]}
760
+ index="month"
761
+ categories={['revenue']}
762
+ valueFormatter={(v) => `$${v.toLocaleString()}`}
763
+ title="Revenue, Jan–Feb"
764
+ />
765
+ <DonutChart data={byStatus} index="status" category="tasks" centerLabel="63 tasks" title="Tasks by status" />
766
+ <Sparkline data={weekly} category="signups" color="success" title="Signups, last 4 weeks" />
767
+ ```
768
+
769
+ ### Rich text (optional entry point)
770
+
771
+ [`RichTextEditor`](doc/RichTextEditor.md) — import from `@dreamtree-org/twreact-ui/rich-text` after
772
+ `npm install @tiptap/react @tiptap/pm @tiptap/starter-kit`. A Tiptap (StarterKit) field with the
773
+ same `label` / `error` / `helperText` / `required` wiring as `Input`; controlled (`value` +
774
+ `onChange(html)`) or uncontrolled (`defaultValue`), `outputFormat="json"` for Tiptap JSON,
775
+ `disabled` / `readOnly`, `minHeight` / `maxHeight`, and a `toolbar` (bold, italic, strike, code,
776
+ headings, lists, quote, link, undo/redo) that is a `role="toolbar"` of real buttons with
777
+ `aria-pressed` and arrow-key roving focus. The `ref` is an imperative handle (`editor`, `focus()`,
778
+ `getHTML()`, `getJSON()`, `setContent()`, `clear()`). SSR-safe: the editor mounts on the client.
779
+
780
+ ```jsx
781
+ import { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';
782
+
783
+ <RichTextEditor label="Description" placeholder="Describe the task…" value={html} onChange={setHtml} maxHeight={320} />
784
+ ```
785
+
786
+ > The HTML comes from the user. Sanitize it on the server (e.g. DOMPurify / `sanitize-html`) before
787
+ > storing or rendering it; this library ships no sanitizer.
788
+
570
789
  ### Page blocks
571
790
 
572
791
  Page-level patterns extracted from the Dreamtree products. Build pages from these before composing layouts by hand.
@@ -590,6 +809,29 @@ import { PageHeader, StatCard, StatusPill, EmptyState, Button } from '@dreamtree
590
809
 
591
810
  Component types now carry their props (`<Button variant="bogus" />` is a type error).
592
811
 
812
+ ### Hooks
813
+
814
+ #### useApi
815
+ Thin axios wrapper. Returns `{ data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith }`.
816
+
817
+ - `fetchApi(key, data?, needLoading?)` calls `apiMap[key](data)` with **one** argument.
818
+ - `fetchApiWith(key, args, { needLoading }?)` spreads `args` into the map entry — use it for entries like `(id, body) => ({...})`.
819
+
820
+ ```jsx
821
+ const { fetchApi, fetchApiWith } = useApi({
822
+ BASE_URL: 'https://api.example.com',
823
+ apiMap: {
824
+ getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),
825
+ updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),
826
+ },
827
+ });
828
+
829
+ fetchApi('getUser', 7);
830
+ fetchApiWith('updateUser', [7, { name: 'Ada' }]);
831
+ ```
832
+
833
+ See [doc/useApi.md](doc/useApi.md).
834
+
593
835
  ## Theming
594
836
 
595
837
  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.
@@ -601,6 +843,8 @@ Every colour in the compiled CSS is a CSS variable (`--dt-primary-600`, `--dt-su
601
843
  @import "@dreamtree-org/twreact-ui/dist/tokens/brands/practicepro.css"; /* or your own --dt-* overrides */
602
844
  ```
603
845
 
846
+ Shipped brands: `default`, `practicepro`, `studio` (Dreamtree Studio's own pages).
847
+
604
848
  **Use the same tokens in your own classes:**
605
849
 
606
850
  ```js
@@ -626,6 +870,10 @@ This adds semantic utilities next to the palettes: `bg-surface`, `bg-background`
626
870
 
627
871
  **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
872
 
873
+ **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.
874
+
875
+ **SSR:** `Tabs`, `Tooltip` and `Switch` generate ids with `React.useId()`, so server-rendered pages (Next.js) hydrate without mismatches.
876
+
629
877
  ## Dark Mode
630
878
 
631
879
  Enable dark mode with the built-in theme provider:
@@ -29,6 +29,17 @@ npm install react react-dom
29
29
  `react` / `react-dom` are **peer dependencies** at `^18.3.1`. React 19
30
30
  is not yet supported.
31
31
 
32
+ **Optional entry points** — install the peer only when the user needs it:
33
+
34
+ ```bash
35
+ # charts: @dreamtree-org/twreact-ui/charts
36
+ npm install recharts # React 18 apps also: npm install react-is@18
37
+ # rich text: @dreamtree-org/twreact-ui/rich-text
38
+ npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
39
+ ```
40
+
41
+ Apps that don't import these subpaths install and bundle nothing extra.
42
+
32
43
  ## Wiring (do not invent alternatives)
33
44
 
34
45
  ```jsx
@@ -63,6 +74,14 @@ module.exports = {
63
74
  };
64
75
  ```
65
76
 
77
+ Import from the package root only (`import { Button } from '@dreamtree-org/twreact-ui'`).
78
+ The root import tree-shakes, so do not suggest deep paths into `dist/` or `src/` for components.
79
+
80
+ **Next.js App Router:** components ship with `'use client'`, so a Server Component can import
81
+ them directly. Do not generate a `"use client"` wrapper file that re-exports the library.
82
+ Compound members (`Card.Header`, `Tabs.List`, `Dialog.Footer`) and function props (`onClick`)
83
+ still require a `'use client'` file. `cn`, `Helpers` and `Emitter` also work on the server.
84
+
66
85
  ## Live prop contracts via MCP (prefer this over guessing)
67
86
 
68
87
  An **MCP server bundled in this package** serves the authoritative prop
@@ -99,11 +118,13 @@ The library exports four kinds of things from `@dreamtree-org/twreact-ui`:
99
118
 
100
119
  | Group | Components |
101
120
  | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
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` |
121
+ | **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
122
  | **Navigation** | `Sidebar`, `Navbar`, `FootNav`, `Breadcrumbs` |
104
- | **Feedback** | `Dialog`, `Toast` (+ `ToastContainer`, `useToast`), `Alert` |
105
- | **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel` |
123
+ | **Feedback** | `Dialog`, `Toast` (+ `ToastProvider`, `ToastContainer`, `useToast`), `Alert`, `Drawer` (side sheet; `isOpen`/`onClose` like Dialog), `Tour` (onboarding spotlight) |
124
+ | **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel`, `Sortable` (drag + keyboard reorder) |
106
125
  | **Blocks** | `AppShell`, `PageHeader`, `StatCard`, `StatusPill`, `EmptyState`, `ErrorState`, `LoadingState`, `ConfirmDialog` — page-level patterns; build pages from these before composing layouts by hand |
126
+ | **Charts** (`@dreamtree-org/twreact-ui/charts`, peer `recharts`) | `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, `Sparkline` — NOT exported from the root |
127
+ | **Rich text** (`@dreamtree-org/twreact-ui/rich-text`, peers `@tiptap/react @tiptap/pm @tiptap/starter-kit`) | `RichTextEditor` — NOT exported from the root |
107
128
 
108
129
  ### Hooks
109
130
 
@@ -111,7 +132,7 @@ The library exports four kinds of things from `@dreamtree-org/twreact-ui`:
111
132
  | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
112
133
  | `useTheme` | Read/write the current theme (`light | dark`); pairs with `<ThemeProvider>`. Throws if used outside the provider. |
113
134
  | `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`. |
135
+ | `useApi` | Thin axios wrapper. Returns `{data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith}`. Accepts `BASE_URL`, `DEFAULT_HEADERS`, `apiMap`. |
115
136
  | `useMixins` | Bridge between component state, Redux store, and an in-memory cache. Power-user hook — prefer local React state for new code. |
116
137
 
117
138
  ### Store
@@ -149,8 +170,9 @@ Components use a **shared variant/size/focus vocabulary**:
149
170
 
150
171
  - **Variants:** `primary | secondary | outline | ghost | destructive | success | warning`
151
172
  (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).
173
+ - **Sizes:** `xs | sm | md | lg | xl | 2xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14 / h-16`
174
+ and padding `px-2 / px-3 / px-4 / px-6 / px-8 / px-10` (`Button` implements all six;
175
+ other components MAY support a subset and fall back to `md`).
154
176
  - **Focus ring:** `focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500`
155
177
  (the color token swaps for destructive / etc.).
156
178
  - **Dark mode:** Tailwind `darkMode: 'class'`. Every color choice has a `dark:` counterpart.
@@ -162,12 +184,24 @@ When suggesting a component or new variant value, use this exact vocabulary.
162
184
  **Blocks** are page-level patterns extracted from the products, so screens stop re-inventing them:
163
185
 
164
186
  - `AppShell` — sidebar + top bar + mobile drawer; pass `nav` items and `linkAs={Link}` in Next.js.
187
+ - `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
188
  - `PageHeader` — the page's only `h1`, subtitle, one primary action, optional `back` link.
166
189
  - `StatCard` — KPI tile (`label`, `value`, `icon`, `tone`, `change`).
167
190
  - `StatusPill` — map domain states to `{ label, tone }` once; unknown states are humanized, never raw.
168
191
  - `EmptyState`, `ErrorState`, `LoadingState` — every data card needs all three.
169
192
  - `ConfirmDialog` — use instead of `window.confirm()`; title is the question, button is the verb.
170
193
 
194
+ **Interaction primitives** — reach for these before hand-rolling the pattern:
195
+
196
+ - `Drawer` — side/edge sheet for record peeks and editors (`isOpen`, `onClose`, `title`, `placement`, `size`, `footer`); don't fake one with `Dialog size="lg"`.
197
+ - `Popover` — click-triggered panel with controls (`trigger={<Button>…</Button>}`, children or `({ close }) => …`); `Tooltip` is for read-only hover text only.
198
+ - `Textarea` — same API as `Input` plus `autoResize`/`maxRows`; never a raw `<textarea>`.
199
+ - `Slider` — `defaultValue={40}` for one thumb, `[low, high]` for a range; `onChangeEnd` to save after a drag.
200
+ - `Sortable` — `items` + `onReorder(newItems)` + `renderItem`; keyboard reordering and announcements are built in. Pass `handle` and spread `handleProps` on a grip for touch screens.
201
+ - Charts (`/charts`) — `<LineChart data={rows} index="month" categories={['revenue', 'costs']} />`; same shape for `BarChart` (`stacked`, `horizontal`) and `AreaChart` (`stacked`); `DonutChart` takes `index` (name key) + `category` (value key) + `centerLabel`; `Sparkline` takes `category` + `color`. Colours are tokens (`colors={['primary', 'success-600']}`), never hex. Always pass `title` (accessible name) and, for key charts, a `description` with the takeaway. `valueFormatter` formats tooltip/axis/table values.
202
+ - `RichTextEditor` (`/rich-text`) — `label`/`error`/`helperText` like `Input`; `value` + `onChange(html)` (or `outputFormat="json"`); `toolbar={['bold', 'italic', 'link']}` to trim. Tell the user to **sanitize the HTML server-side**; never render it with `dangerouslySetInnerHTML` unsanitized.
203
+ - `Tour` — onboarding coach marks: `steps=[{ target: '[data-tour="x"]', title, content, placement }]`, `open`, `onClose(reason)`; a missing target falls back to a centred card.
204
+
171
205
  **Tokens:** colours are CSS variables (`--dt-primary-600`, `--dt-surface`, …) generated from
172
206
  `tokens/tokens.json`. Use semantic classes (`bg-primary-600`, `bg-surface`, `text-foreground`,
173
207
  `text-muted`, `border-line`, `text-micro`, `rounded-card`) — never hex values or Tailwind's raw
@@ -216,17 +250,48 @@ import { Input } from '@dreamtree-org/twreact-ui';
216
250
  `Form` integrates with `react-hook-form` + `yup` (`@hookform/resolvers`).
217
251
  Validation rules live in consumer code.
218
252
 
253
+ **Helper text:** `Input`, `Select`, `DatePicker`, `DateRangePicker` and every
254
+ `Form` field take `helperText` (rendered below, linked via `aria-describedby`).
255
+ While `error` is set it **replaces** the helper text — don't render both. Don't
256
+ hand-roll a `<p>` hint under a field.
257
+
258
+ ```jsx
259
+ <Select label="Role" helperText="Controls access." options={roles} value={role} onChange={setRole} />
260
+ <Form fields={[
261
+ { type: 'email', name: 'email', label: 'Email', helperText: 'Receipts only.' },
262
+ { type: 'color', name: 'brand', label: 'Brand colour', swatches: ['#2563eb'] }, // ColorPicker
263
+ ]} />
264
+ ```
265
+
266
+ `Form` field types: `text | email | password | number | tel | url | select |
267
+ date | textarea | checkbox | radio | file | switch | color | custom`. Use
268
+ `type: 'color'` rather than a `custom` field wrapping `ColorPicker`.
269
+
270
+ **Select `grouped`** accepts `[{ label, options }]` groups **or** flat options
271
+ with a `group` key (`{ value, label, group: 'Code' }`) — no need to pre-group.
272
+
273
+ **Navbar** takes a `center` slot (search field / primary action) and shows
274
+ `user.name` initials when `user.avatar` is missing; it fits a 390px phone.
275
+
276
+ **Tooltip** around a component (e.g. `<Button>`) in a row/grid: use
277
+ `customTrigger={<Button>…</Button>}` so there is one tab stop, not two. A lone
278
+ native `<button>` / `<a href>` / `<input>` child is detected automatically.
279
+
219
280
  ### Toast
220
281
 
282
+ Wrap the app ONCE in `ToastProvider`; every `useToast()` below it shares one
283
+ queue and the provider renders it. (Without a provider each `useToast()` owns a
284
+ private queue that only shows where that same hook's `toasts` are passed to a
285
+ `<ToastContainer toasts={toasts} onRemove={removeToast} />`.)
286
+
221
287
  ```jsx
222
- import { ToastContainer, useToast } from '@dreamtree-org/twreact-ui';
288
+ import { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';
223
289
 
224
290
  function App() {
225
291
  return (
226
- <>
227
- <ToastContainer />
292
+ <ToastProvider position="top-right">
228
293
  <YourApp />
229
- </>
294
+ </ToastProvider>
230
295
  );
231
296
  }
232
297
 
@@ -256,17 +321,45 @@ toggles `data-theme` on `<html>`.
256
321
  ```jsx
257
322
  import { useApi } from '@dreamtree-org/twreact-ui';
258
323
 
259
- const { data, error, loading, sendRequest } = useApi({
324
+ const { data, error, loading, fetchData, fetchApi, fetchApiWith } = useApi({
260
325
  BASE_URL: 'https://api.example.com',
261
326
  DEFAULT_HEADERS: { Authorization: `Bearer ${token}` },
327
+ apiMap: {
328
+ getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),
329
+ updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),
330
+ },
262
331
  });
263
332
 
264
- useEffect(() => { sendRequest('GET', '/users'); }, []);
333
+ useEffect(() => { fetchData('/users', 'GET'); }, []);
334
+ fetchApi('getUser', 7); // apiMap.getUser(7) — ONE argument
335
+ fetchApiWith('updateUser', [7, { name: 'A' }]); // apiMap.updateUser(7, {...}) — spread
265
336
  ```
266
337
 
338
+ `fetchApi(key, data)` always passes the map entry exactly one argument; use
339
+ `fetchApiWith(key, [a, b])` for multi-argument entries.
340
+
267
341
  Treat `useApi` as a thin axios wrapper, not a caching/dedup layer. For
268
342
  cache, the consumer brings their own (React Query, SWR, etc.).
269
343
 
344
+ ### Charts and rich text (optional entry points)
345
+
346
+ ```jsx
347
+ import { LineChart, DonutChart, Sparkline } from '@dreamtree-org/twreact-ui/charts';
348
+ import { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';
349
+
350
+ <LineChart
351
+ data={[{ month: 'Jan', revenue: 4200 }, { month: 'Feb', revenue: 5100 }]}
352
+ index="month"
353
+ categories={['revenue']}
354
+ valueFormatter={(v) => `$${v.toLocaleString()}`}
355
+ title="Revenue, Jan–Feb"
356
+ />
357
+ <DonutChart data={byStatus} index="status" category="tasks" colors={['info', 'error', 'success']} centerLabel="63 tasks" title="Tasks by status" />
358
+ <Sparkline data={weekly} category="signups" color="success" title="Signups, last 4 weeks" />
359
+
360
+ <RichTextEditor label="Description" placeholder="Describe the task…" value={html} onChange={setHtml} maxHeight={320} />
361
+ ```
362
+
270
363
  ### cn
271
364
 
272
365
  ```jsx
@@ -282,7 +375,7 @@ import { cn } from '@dreamtree-org/twreact-ui';
282
375
  1. **Use library components.** When the user asks for a button, input,
283
376
  table, modal, toast, navbar, sidebar, breadcrumb, badge, card,
284
377
  stepper, file uploader, date picker, color picker, location picker,
285
- pagination, or carousel — reach into this library FIRST. Don't
378
+ pagination, carousel, chart or rich-text editor — reach into this library FIRST. Don't
286
379
  propose a hand-rolled `<div>` + Tailwind unless the library has no
287
380
  matching primitive.
288
381
  2. **Use the shared vocabulary.** `variant="primary"`, `size="md"`,
@@ -297,8 +390,11 @@ import { cn } from '@dreamtree-org/twreact-ui';
297
390
  "components render unstyled," check first whether their
298
391
  `tailwind.config.js` `content[]` includes
299
392
  `./node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}`.
300
- 6. **Don't deep-import.** Only `@dreamtree-org/twreact-ui` is public.
301
- Don't suggest `@dreamtree-org/twreact-ui/src/components/core/Button`.
393
+ 6. **Don't deep-import.** The public modules are `@dreamtree-org/twreact-ui`
394
+ plus the two optional entry points `@dreamtree-org/twreact-ui/charts` and
395
+ `@dreamtree-org/twreact-ui/rich-text` (each needs its peer installed).
396
+ Don't suggest `@dreamtree-org/twreact-ui/src/components/core/Button`, and
397
+ never import charts or `RichTextEditor` from the root.
302
398
  7. **React 18 only.** If the user is on React 19, warn them: this
303
399
  library has not yet been verified on React 19.
304
400
  8. **Accessibility-first.** Icon-only buttons need `aria-label`.
@@ -0,0 +1,5 @@
1
+ {
2
+ "main": "../dist/charts/index.js",
3
+ "module": "../dist/esm/charts/index.js",
4
+ "types": "../dist/charts/index.d.ts"
5
+ }