@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.
- package/DESIGN.md +2 -2
- package/README.md +258 -10
- package/ai-skills/dreamtree-ui.md +111 -15
- package/charts/package.json +5 -0
- package/dist/_shared/cn-DPPQUJu1.js +138 -0
- package/dist/ai-schema.json +1252 -179
- package/dist/charts/index.d.ts +9 -0
- package/dist/charts/index.d.ts.map +1 -0
- package/dist/charts/index.js +769 -0
- package/dist/components/charts/AreaChart.d.ts +4 -0
- package/dist/components/charts/AreaChart.d.ts.map +1 -0
- package/dist/components/charts/BarChart.d.ts +4 -0
- package/dist/components/charts/BarChart.d.ts.map +1 -0
- package/dist/components/charts/CartesianChart.d.ts +5 -0
- package/dist/components/charts/CartesianChart.d.ts.map +1 -0
- package/dist/components/charts/ChartFrame.d.ts +18 -0
- package/dist/components/charts/ChartFrame.d.ts.map +1 -0
- package/dist/components/charts/DonutChart.d.ts +4 -0
- package/dist/components/charts/DonutChart.d.ts.map +1 -0
- package/dist/components/charts/LineChart.d.ts +4 -0
- package/dist/components/charts/LineChart.d.ts.map +1 -0
- package/dist/components/charts/Sparkline.d.ts +5 -0
- package/dist/components/charts/Sparkline.d.ts.map +1 -0
- package/dist/components/charts/chartTheme.d.ts +26 -0
- package/dist/components/charts/chartTheme.d.ts.map +1 -0
- package/dist/components/core/Accordion.d.ts.map +1 -1
- package/dist/components/core/Button.d.ts.map +1 -1
- package/dist/components/core/Checkbox.d.ts.map +1 -1
- package/dist/components/core/ColorPicker.d.ts.map +1 -1
- package/dist/components/core/DatePicker.d.ts.map +1 -1
- package/dist/components/core/DateRangePicker.d.ts.map +1 -1
- package/dist/components/core/Form.d.ts.map +1 -1
- package/dist/components/core/Input.d.ts.map +1 -1
- package/dist/components/core/LocationPicker.d.ts.map +1 -1
- package/dist/components/core/Popover.d.ts +4 -0
- package/dist/components/core/Popover.d.ts.map +1 -0
- package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
- package/dist/components/core/ProgressBar.d.ts.map +1 -1
- package/dist/components/core/Radio.d.ts.map +1 -1
- package/dist/components/core/Rate.d.ts.map +1 -1
- package/dist/components/core/Select.d.ts.map +1 -1
- package/dist/components/core/Skeleton.d.ts.map +1 -1
- package/dist/components/core/Slider.d.ts +4 -0
- package/dist/components/core/Slider.d.ts.map +1 -0
- package/dist/components/core/SpeechToText.d.ts.map +1 -1
- package/dist/components/core/Switch.d.ts.map +1 -1
- package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
- package/dist/components/core/Table/ColumnMenu.d.ts +2 -1
- package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
- package/dist/components/core/Table/MobileCard.d.ts +2 -2
- package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
- package/dist/components/core/Table/Table.d.ts.map +1 -1
- package/dist/components/core/Table/TableRow.d.ts +4 -2
- package/dist/components/core/Table/TableRow.d.ts.map +1 -1
- package/dist/components/core/Tabs.d.ts.map +1 -1
- package/dist/components/core/Textarea.d.ts +4 -0
- package/dist/components/core/Textarea.d.ts.map +1 -0
- package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
- package/dist/components/core/Tooltip.d.ts.map +1 -1
- package/dist/components/feedback/Dialog.d.ts.map +1 -1
- package/dist/components/feedback/Drawer.d.ts +4 -0
- package/dist/components/feedback/Drawer.d.ts.map +1 -0
- package/dist/components/feedback/Toast.d.ts +14 -5
- package/dist/components/feedback/Toast.d.ts.map +1 -1
- package/dist/components/feedback/Tour.d.ts +4 -0
- package/dist/components/feedback/Tour.d.ts.map +1 -0
- package/dist/components/feedback/TourSpotlight.d.ts +9 -0
- package/dist/components/feedback/TourSpotlight.d.ts.map +1 -0
- package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/navigation/FootNav.d.ts.map +1 -1
- package/dist/components/navigation/Navbar.d.ts.map +1 -1
- package/dist/components/navigation/Sidebar.d.ts +8 -2
- package/dist/components/navigation/Sidebar.d.ts.map +1 -1
- package/dist/components/rich-text/RichTextEditor.d.ts +4 -0
- package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -0
- package/dist/components/rich-text/RichTextToolbar.d.ts +219 -0
- package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -0
- package/dist/components/utility/Badge.d.ts.map +1 -1
- package/dist/components/utility/FileUpload.d.ts.map +1 -1
- package/dist/components/utility/Sortable.d.ts +4 -0
- package/dist/components/utility/Sortable.d.ts.map +1 -0
- package/dist/esm/_vendor/object-assign/index.js +99 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.development.js +190 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.production.min.js +26 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/index.js +19 -0
- package/dist/esm/_vendor/prop-types/checkPropTypes.js +115 -0
- package/dist/esm/_vendor/prop-types/factoryWithThrowingShims.js +74 -0
- package/dist/esm/_vendor/prop-types/factoryWithTypeCheckers.js +625 -0
- package/dist/esm/_vendor/prop-types/index.js +30 -0
- package/dist/esm/_vendor/prop-types/lib/ReactPropTypesSecret.js +21 -0
- package/dist/esm/_vendor/prop-types/lib/has.js +11 -0
- package/dist/esm/_virtual/_commonjsHelpers.js +5 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
- package/dist/esm/_virtual/index.js +3 -0
- package/dist/esm/_virtual/index2.js +3 -0
- package/dist/esm/_virtual/react-is.development.js +3 -0
- package/dist/esm/_virtual/react-is.production.min.js +3 -0
- package/dist/esm/charts/index.js +5 -0
- package/dist/esm/components/blocks/AppShell.js +205 -0
- package/dist/esm/components/blocks/ConfirmDialog.js +57 -0
- package/dist/esm/components/blocks/EmptyState.js +44 -0
- package/dist/esm/components/blocks/ErrorState.js +53 -0
- package/dist/esm/components/blocks/LoadingState.js +134 -0
- package/dist/esm/components/blocks/PageHeader.js +51 -0
- package/dist/esm/components/blocks/StatCard.js +79 -0
- package/dist/esm/components/blocks/StatusPill.js +55 -0
- package/dist/esm/components/charts/AreaChart.js +16 -0
- package/dist/esm/components/charts/BarChart.js +16 -0
- package/dist/esm/components/charts/CartesianChart.js +250 -0
- package/dist/esm/components/charts/ChartFrame.js +170 -0
- package/dist/esm/components/charts/DonutChart.js +119 -0
- package/dist/esm/components/charts/LineChart.js +16 -0
- package/dist/esm/components/charts/Sparkline.js +110 -0
- package/dist/esm/components/charts/chartTheme.js +109 -0
- package/dist/esm/components/core/Accordion.js +260 -0
- package/dist/esm/components/core/Button.js +59 -0
- package/dist/esm/components/core/Checkbox.js +188 -0
- package/dist/esm/components/core/ColorPicker.js +348 -0
- package/dist/esm/components/core/DatePicker.js +436 -0
- package/dist/esm/components/core/DateRangePicker.js +494 -0
- package/dist/esm/components/core/Form.js +522 -0
- package/dist/esm/components/core/Input.js +179 -0
- package/dist/esm/components/core/Loader.js +141 -0
- package/dist/esm/components/core/LocationPicker.js +161 -0
- package/dist/esm/components/core/Popover.js +171 -0
- package/dist/esm/components/core/PriceRangePicker.js +376 -0
- package/dist/esm/components/core/ProgressBar.js +152 -0
- package/dist/esm/components/core/Radio.js +156 -0
- package/dist/esm/components/core/Rate.js +197 -0
- package/dist/esm/components/core/RoundedTag.js +118 -0
- package/dist/esm/components/core/Select.js +857 -0
- package/dist/esm/components/core/Skeleton.js +100 -0
- package/dist/esm/components/core/Slider.js +134 -0
- package/dist/esm/components/core/SpeechToText.js +226 -0
- package/dist/esm/components/core/Switch.js +207 -0
- package/dist/esm/components/core/Table/ActionMenu.js +251 -0
- package/dist/esm/components/core/Table/ColumnMenu.js +55 -0
- package/dist/esm/components/core/Table/FieldInput.js +157 -0
- package/dist/esm/components/core/Table/MobileCard.js +187 -0
- package/dist/esm/components/core/Table/MobileFilters.js +72 -0
- package/dist/esm/components/core/Table/Pagination.js +157 -0
- package/dist/esm/components/core/Table/Table.js +1533 -0
- package/dist/esm/components/core/Table/TableRow.js +254 -0
- package/dist/esm/components/core/Table/constants.js +35 -0
- package/dist/esm/components/core/Table/helpers.js +124 -0
- package/dist/esm/components/core/Table/useClickOutside.js +26 -0
- package/dist/esm/components/core/Tabs.js +289 -0
- package/dist/esm/components/core/TextToSpeech.js +52 -0
- package/dist/esm/components/core/Textarea.js +125 -0
- package/dist/esm/components/core/ThreeDotPopover.js +160 -0
- package/dist/esm/components/core/Tooltip.js +372 -0
- package/dist/esm/components/feedback/Alert.js +96 -0
- package/dist/esm/components/feedback/Dialog.js +200 -0
- package/dist/esm/components/feedback/Drawer.js +171 -0
- package/dist/esm/components/feedback/Toast.js +362 -0
- package/dist/esm/components/feedback/Tour.js +214 -0
- package/dist/esm/components/feedback/TourSpotlight.js +71 -0
- package/dist/esm/components/navigation/Breadcrumbs.js +75 -0
- package/dist/esm/components/navigation/Dropdown.js +53 -0
- package/dist/esm/components/navigation/FootNav.js +51 -0
- package/dist/esm/components/navigation/Navbar.js +437 -0
- package/dist/esm/components/navigation/Sidebar.js +567 -0
- package/dist/esm/components/rich-text/RichTextEditor.js +244 -0
- package/dist/esm/components/rich-text/RichTextToolbar.js +397 -0
- package/dist/esm/components/utility/Avatar.js +89 -0
- package/dist/esm/components/utility/Badge.js +47 -0
- package/dist/esm/components/utility/Card.js +85 -0
- package/dist/esm/components/utility/Carousel.js +97 -0
- package/dist/esm/components/utility/Condition.js +9 -0
- package/dist/esm/components/utility/FileUpload.js +177 -0
- package/dist/esm/components/utility/Pagination.js +111 -0
- package/dist/esm/components/utility/Sortable.js +190 -0
- package/dist/esm/components/utility/Stepper.js +101 -0
- package/dist/esm/hooks/useApi.js +169 -0
- package/dist/esm/hooks/useControllableState.js +32 -0
- package/dist/esm/hooks/useFocusTrap.js +85 -0
- package/dist/esm/hooks/useLatestRef.js +20 -0
- package/dist/esm/hooks/useMixins.js +100 -0
- package/dist/esm/hooks/useScrollLock.js +26 -0
- package/dist/esm/hooks/useSliderValues.js +135 -0
- package/dist/esm/hooks/useSortable.js +188 -0
- package/dist/esm/hooks/useTheme.js +53 -0
- package/dist/esm/hooks/useTourTarget.js +92 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/rich-text/index.js +1 -0
- package/dist/esm/store/index.js +26 -0
- package/dist/esm/store/store.js +107 -0
- package/dist/esm/store/storeSlice.js +45 -0
- package/dist/esm/utils/cn.js +11 -0
- package/dist/esm/utils/emitter.js +169 -0
- package/dist/esm/utils/helpers.js +161 -0
- package/dist/esm/utils/position.js +134 -0
- package/dist/hooks/useApi.d.ts +2 -1
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useControllableState.d.ts +11 -0
- package/dist/hooks/useControllableState.d.ts.map +1 -0
- package/dist/hooks/useFocusTrap.d.ts +15 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -0
- package/dist/hooks/useLatestRef.d.ts +8 -0
- package/dist/hooks/useLatestRef.d.ts.map +1 -0
- package/dist/hooks/useMixins.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.d.ts +2 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -0
- package/dist/hooks/useSliderValues.d.ts +43 -0
- package/dist/hooks/useSliderValues.d.ts.map +1 -0
- package/dist/hooks/useSortable.d.ts +32 -0
- package/dist/hooks/useSortable.d.ts.map +1 -0
- package/dist/hooks/useTheme.d.ts.map +1 -1
- package/dist/hooks/useTourTarget.d.ts +15 -0
- package/dist/hooks/useTourTarget.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +4817 -2216
- package/dist/index.js +4822 -2214
- package/dist/rich-text/index.d.ts +5 -0
- package/dist/rich-text/index.d.ts.map +1 -0
- package/dist/rich-text/index.js +654 -0
- package/dist/tokens/brands/studio.css +14 -0
- package/dist/tokens/brands/studio.json +20 -0
- package/dist/types/charts.d.ts +97 -0
- package/dist/types/charts.d.ts.map +1 -0
- package/dist/types/index.d.ts +193 -5
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/rich-text.d.ts +50 -0
- package/dist/types/rich-text.d.ts.map +1 -0
- package/dist/utils/position.d.ts +30 -0
- package/dist/utils/position.d.ts.map +1 -0
- package/doc/README.md +20 -1
- package/mcp/catalog.snapshot.json +2505 -800
- package/mcp/server-core.mjs +30 -1
- package/package.json +51 -3
- package/rich-text/package.json +5 -0
- 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,
|
|
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` (
|
|
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 {
|
|
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,
|
|
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(() => {
|
|
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
|
|
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.**
|
|
301
|
-
|
|
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`.
|