@dreamtree-org/twreact-ui 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -10
- package/ai-skills/dreamtree-ui.md +71 -12
- package/dist/ai-schema.json +452 -17
- package/dist/components/core/Accordion.d.ts.map +1 -1
- package/dist/components/core/Button.d.ts.map +1 -1
- package/dist/components/core/Checkbox.d.ts.map +1 -1
- package/dist/components/core/ColorPicker.d.ts.map +1 -1
- package/dist/components/core/DatePicker.d.ts.map +1 -1
- package/dist/components/core/DateRangePicker.d.ts.map +1 -1
- package/dist/components/core/Form.d.ts.map +1 -1
- package/dist/components/core/Input.d.ts.map +1 -1
- package/dist/components/core/LocationPicker.d.ts.map +1 -1
- package/dist/components/core/Popover.d.ts +4 -0
- package/dist/components/core/Popover.d.ts.map +1 -0
- package/dist/components/core/PriceRangePicker.d.ts.map +1 -1
- package/dist/components/core/ProgressBar.d.ts.map +1 -1
- package/dist/components/core/Radio.d.ts.map +1 -1
- package/dist/components/core/Rate.d.ts.map +1 -1
- package/dist/components/core/Select.d.ts.map +1 -1
- package/dist/components/core/Skeleton.d.ts.map +1 -1
- package/dist/components/core/Slider.d.ts +4 -0
- package/dist/components/core/Slider.d.ts.map +1 -0
- package/dist/components/core/SpeechToText.d.ts.map +1 -1
- package/dist/components/core/Switch.d.ts.map +1 -1
- package/dist/components/core/Table/ActionMenu.d.ts.map +1 -1
- package/dist/components/core/Table/ColumnMenu.d.ts +2 -1
- package/dist/components/core/Table/ColumnMenu.d.ts.map +1 -1
- package/dist/components/core/Table/MobileCard.d.ts +2 -2
- package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
- package/dist/components/core/Table/Table.d.ts.map +1 -1
- package/dist/components/core/Table/TableRow.d.ts +4 -2
- package/dist/components/core/Table/TableRow.d.ts.map +1 -1
- package/dist/components/core/Tabs.d.ts.map +1 -1
- package/dist/components/core/Textarea.d.ts +4 -0
- package/dist/components/core/Textarea.d.ts.map +1 -0
- package/dist/components/core/ThreeDotPopover.d.ts.map +1 -1
- package/dist/components/core/Tooltip.d.ts.map +1 -1
- package/dist/components/feedback/Dialog.d.ts.map +1 -1
- package/dist/components/feedback/Drawer.d.ts +4 -0
- package/dist/components/feedback/Drawer.d.ts.map +1 -0
- package/dist/components/feedback/Toast.d.ts +14 -5
- package/dist/components/feedback/Toast.d.ts.map +1 -1
- package/dist/components/feedback/Tour.d.ts +4 -0
- package/dist/components/feedback/Tour.d.ts.map +1 -0
- package/dist/components/feedback/TourSpotlight.d.ts +9 -0
- package/dist/components/feedback/TourSpotlight.d.ts.map +1 -0
- package/dist/components/navigation/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/navigation/FootNav.d.ts.map +1 -1
- package/dist/components/navigation/Navbar.d.ts.map +1 -1
- package/dist/components/navigation/Sidebar.d.ts +8 -2
- package/dist/components/navigation/Sidebar.d.ts.map +1 -1
- package/dist/components/utility/Badge.d.ts.map +1 -1
- package/dist/components/utility/FileUpload.d.ts.map +1 -1
- package/dist/components/utility/Sortable.d.ts +4 -0
- package/dist/components/utility/Sortable.d.ts.map +1 -0
- package/dist/esm/_vendor/object-assign/index.js +99 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.development.js +190 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/cjs/react-is.production.min.js +26 -0
- package/dist/esm/_vendor/prop-types/_vendor/react-is/index.js +19 -0
- package/dist/esm/_vendor/prop-types/checkPropTypes.js +115 -0
- package/dist/esm/_vendor/prop-types/factoryWithThrowingShims.js +74 -0
- package/dist/esm/_vendor/prop-types/factoryWithTypeCheckers.js +625 -0
- package/dist/esm/_vendor/prop-types/index.js +30 -0
- package/dist/esm/_vendor/prop-types/lib/ReactPropTypesSecret.js +21 -0
- package/dist/esm/_vendor/prop-types/lib/has.js +11 -0
- package/dist/esm/_virtual/_commonjsHelpers.js +5 -0
- package/dist/esm/_virtual/_rollupPluginBabelHelpers.js +329 -0
- package/dist/esm/_virtual/index.js +3 -0
- package/dist/esm/_virtual/index2.js +3 -0
- package/dist/esm/_virtual/react-is.development.js +3 -0
- package/dist/esm/_virtual/react-is.production.min.js +3 -0
- package/dist/esm/components/blocks/AppShell.js +205 -0
- package/dist/esm/components/blocks/ConfirmDialog.js +57 -0
- package/dist/esm/components/blocks/EmptyState.js +44 -0
- package/dist/esm/components/blocks/ErrorState.js +53 -0
- package/dist/esm/components/blocks/LoadingState.js +134 -0
- package/dist/esm/components/blocks/PageHeader.js +51 -0
- package/dist/esm/components/blocks/StatCard.js +79 -0
- package/dist/esm/components/blocks/StatusPill.js +55 -0
- package/dist/esm/components/core/Accordion.js +260 -0
- package/dist/esm/components/core/Button.js +59 -0
- package/dist/esm/components/core/Checkbox.js +188 -0
- package/dist/esm/components/core/ColorPicker.js +348 -0
- package/dist/esm/components/core/DatePicker.js +436 -0
- package/dist/esm/components/core/DateRangePicker.js +494 -0
- package/dist/esm/components/core/Form.js +522 -0
- package/dist/esm/components/core/Input.js +179 -0
- package/dist/esm/components/core/Loader.js +141 -0
- package/dist/esm/components/core/LocationPicker.js +161 -0
- package/dist/esm/components/core/Popover.js +171 -0
- package/dist/esm/components/core/PriceRangePicker.js +376 -0
- package/dist/esm/components/core/ProgressBar.js +152 -0
- package/dist/esm/components/core/Radio.js +156 -0
- package/dist/esm/components/core/Rate.js +197 -0
- package/dist/esm/components/core/RoundedTag.js +118 -0
- package/dist/esm/components/core/Select.js +857 -0
- package/dist/esm/components/core/Skeleton.js +100 -0
- package/dist/esm/components/core/Slider.js +134 -0
- package/dist/esm/components/core/SpeechToText.js +226 -0
- package/dist/esm/components/core/Switch.js +207 -0
- package/dist/esm/components/core/Table/ActionMenu.js +251 -0
- package/dist/esm/components/core/Table/ColumnMenu.js +55 -0
- package/dist/esm/components/core/Table/FieldInput.js +157 -0
- package/dist/esm/components/core/Table/MobileCard.js +187 -0
- package/dist/esm/components/core/Table/MobileFilters.js +72 -0
- package/dist/esm/components/core/Table/Pagination.js +157 -0
- package/dist/esm/components/core/Table/Table.js +1533 -0
- package/dist/esm/components/core/Table/TableRow.js +254 -0
- package/dist/esm/components/core/Table/constants.js +35 -0
- package/dist/esm/components/core/Table/helpers.js +124 -0
- package/dist/esm/components/core/Table/useClickOutside.js +26 -0
- package/dist/esm/components/core/Tabs.js +289 -0
- package/dist/esm/components/core/TextToSpeech.js +52 -0
- package/dist/esm/components/core/Textarea.js +125 -0
- package/dist/esm/components/core/ThreeDotPopover.js +160 -0
- package/dist/esm/components/core/Tooltip.js +372 -0
- package/dist/esm/components/feedback/Alert.js +96 -0
- package/dist/esm/components/feedback/Dialog.js +200 -0
- package/dist/esm/components/feedback/Drawer.js +171 -0
- package/dist/esm/components/feedback/Toast.js +362 -0
- package/dist/esm/components/feedback/Tour.js +214 -0
- package/dist/esm/components/feedback/TourSpotlight.js +71 -0
- package/dist/esm/components/navigation/Breadcrumbs.js +75 -0
- package/dist/esm/components/navigation/Dropdown.js +53 -0
- package/dist/esm/components/navigation/FootNav.js +51 -0
- package/dist/esm/components/navigation/Navbar.js +437 -0
- package/dist/esm/components/navigation/Sidebar.js +567 -0
- package/dist/esm/components/utility/Avatar.js +89 -0
- package/dist/esm/components/utility/Badge.js +47 -0
- package/dist/esm/components/utility/Card.js +85 -0
- package/dist/esm/components/utility/Carousel.js +97 -0
- package/dist/esm/components/utility/Condition.js +9 -0
- package/dist/esm/components/utility/FileUpload.js +177 -0
- package/dist/esm/components/utility/Pagination.js +111 -0
- package/dist/esm/components/utility/Sortable.js +190 -0
- package/dist/esm/components/utility/Stepper.js +101 -0
- package/dist/esm/hooks/useApi.js +169 -0
- package/dist/esm/hooks/useControllableState.js +32 -0
- package/dist/esm/hooks/useFocusTrap.js +85 -0
- package/dist/esm/hooks/useLatestRef.js +20 -0
- package/dist/esm/hooks/useMixins.js +100 -0
- package/dist/esm/hooks/useScrollLock.js +26 -0
- package/dist/esm/hooks/useSliderValues.js +135 -0
- package/dist/esm/hooks/useSortable.js +188 -0
- package/dist/esm/hooks/useTheme.js +53 -0
- package/dist/esm/hooks/useTourTarget.js +92 -0
- package/dist/esm/index.js +65 -0
- package/dist/esm/store/index.js +26 -0
- package/dist/esm/store/store.js +107 -0
- package/dist/esm/store/storeSlice.js +45 -0
- package/dist/esm/utils/cn.js +11 -0
- package/dist/esm/utils/emitter.js +169 -0
- package/dist/esm/utils/helpers.js +161 -0
- package/dist/esm/utils/position.js +134 -0
- package/dist/hooks/useApi.d.ts +2 -1
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useControllableState.d.ts +11 -0
- package/dist/hooks/useControllableState.d.ts.map +1 -0
- package/dist/hooks/useFocusTrap.d.ts +15 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -0
- package/dist/hooks/useLatestRef.d.ts +8 -0
- package/dist/hooks/useLatestRef.d.ts.map +1 -0
- package/dist/hooks/useMixins.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.d.ts +2 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -0
- package/dist/hooks/useSliderValues.d.ts +43 -0
- package/dist/hooks/useSliderValues.d.ts.map +1 -0
- package/dist/hooks/useSortable.d.ts +32 -0
- package/dist/hooks/useSortable.d.ts.map +1 -0
- package/dist/hooks/useTheme.d.ts.map +1 -1
- package/dist/hooks/useTourTarget.d.ts +15 -0
- package/dist/hooks/useTourTarget.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +4800 -2212
- package/dist/index.js +4805 -2210
- package/dist/types/index.d.ts +193 -5
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/position.d.ts +30 -0
- package/dist/utils/position.d.ts.map +1 -0
- package/doc/README.md +7 -1
- package/mcp/catalog.snapshot.json +911 -212
- package/package.json +14 -2
package/README.md
CHANGED
|
@@ -64,6 +64,45 @@ npm install @dreamtree-org/twreact-ui
|
|
|
64
64
|
yarn add @dreamtree-org/twreact-ui
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
Import the stylesheet once (app entry or root layout):
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
import '@dreamtree-org/twreact-ui/dist/index.css';
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Bundle size and tree-shaking
|
|
74
|
+
|
|
75
|
+
Import from the package root. Bundlers (Vite, webpack 5, esbuild, Rollup, Next.js) resolve it to a
|
|
76
|
+
per-module ES build (`dist/esm/`, via the `module` field and the `module` export condition), and the
|
|
77
|
+
package declares `"sideEffects": ["**/*.css"]`, so only the components you import end up in your
|
|
78
|
+
bundle. `import { Button } from '@dreamtree-org/twreact-ui'` costs about 8 KB gzip, down from about
|
|
79
|
+
100 KB. `require()`, Jest and plain Node keep getting the CommonJS build (`dist/index.js`).
|
|
80
|
+
|
|
81
|
+
There are no per-component subpaths such as `@dreamtree-org/twreact-ui/button`. The root import
|
|
82
|
+
already tree-shakes. Existing file paths (`dist/index.css`, `dist/tokens/*`, `tokens/*`,
|
|
83
|
+
`dist/index.esm.js`, `package.json`, …) still resolve through the `exports` map.
|
|
84
|
+
|
|
85
|
+
### Next.js App Router
|
|
86
|
+
|
|
87
|
+
Every module that uses React starts with `'use client'`, so Server Components can import
|
|
88
|
+
components directly. You no longer need a `"use client"` wrapper file that re-exports the library:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// app/page.tsx — a Server Component
|
|
92
|
+
import { Button, cn } from '@dreamtree-org/twreact-ui';
|
|
93
|
+
|
|
94
|
+
export default function Page() {
|
|
95
|
+
return <Button className={cn('mt-4')}>Save</Button>;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`cn`, `Helpers` and `Emitter` carry no directive, so they also run on the server. Two React
|
|
100
|
+
Server Component rules still apply. You cannot pass functions (`onClick`, `render`, …) from a
|
|
101
|
+
Server Component to these Client Components. You also cannot dot into a client export, so
|
|
102
|
+
`<Card.Header>`, `<Tabs.List>` and `<Dialog.Footer>` must be used inside a `'use client'` file.
|
|
103
|
+
In Vite, Rollup can log `Module level directives cause errors when bundled, "use client" … was ignored`.
|
|
104
|
+
This message is harmless. To hide it, filter `MODULE_LEVEL_DIRECTIVE` in `build.rollupOptions.onwarn`.
|
|
105
|
+
|
|
67
106
|
## Quick Start
|
|
68
107
|
|
|
69
108
|
```jsx
|
|
@@ -147,7 +186,7 @@ Versatile button component with multiple variants and states.
|
|
|
147
186
|
|
|
148
187
|
**Props:**
|
|
149
188
|
- `variant`: 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'success' | 'warning' (default: 'primary')
|
|
150
|
-
- `size`: 'sm' | 'md' | 'lg' (default: 'md')
|
|
189
|
+
- `size`: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' (default: 'md') — heights h-7 / h-8 / h-10 / h-12 / h-14 / h-16
|
|
151
190
|
- `disabled`: boolean
|
|
152
191
|
- `loading`: boolean
|
|
153
192
|
- `leftIcon`: ReactNode
|
|
@@ -172,6 +211,7 @@ Enhanced text input with validation states, icons, and clear functionality.
|
|
|
172
211
|
- `placeholder`: string
|
|
173
212
|
- `error`: string
|
|
174
213
|
- `success`: string
|
|
214
|
+
- `helperText`: ReactNode — hint below the input, linked via `aria-describedby`; replaced by `error` / `success` while shown
|
|
175
215
|
- `disabled`: boolean
|
|
176
216
|
- `required`: boolean
|
|
177
217
|
- `clearable`: boolean
|
|
@@ -197,17 +237,18 @@ Enhanced text input with validation states, icons, and clear functionality.
|
|
|
197
237
|
Advanced select component with search, multi-select, and grouping support.
|
|
198
238
|
|
|
199
239
|
**Props:**
|
|
200
|
-
- `options`: Array<{value: any, label: string, disabled?: boolean}>
|
|
240
|
+
- `options`: Array<{value: any, label: string, disabled?: boolean, group?: string}> (with `grouped`: also `[{ label, options }]` groups)
|
|
201
241
|
- `value`: any | Array<any>
|
|
202
242
|
- `onChange`: (value: any) => void
|
|
203
243
|
- `placeholder`: string (default: 'Select an option...')
|
|
204
|
-
- `label`: string
|
|
244
|
+
- `label`: string — associated with the trigger (accessible name = label + value)
|
|
205
245
|
- `error`: string
|
|
246
|
+
- `helperText`: ReactNode — hint below the control; replaced by `error` while shown
|
|
206
247
|
- `disabled`: boolean
|
|
207
248
|
- `required`: boolean
|
|
208
249
|
- `multiSelect`: boolean
|
|
209
250
|
- `searchable`: boolean
|
|
210
|
-
- `grouped`: boolean
|
|
251
|
+
- `grouped`: boolean — groups objects or flat options with a `group` key; malformed input is skipped (console warning), never thrown
|
|
211
252
|
- `creatable`: boolean
|
|
212
253
|
- `onCreateOption`: (value: string) => void
|
|
213
254
|
- `onSearch`: (term: string) => void
|
|
@@ -241,6 +282,8 @@ Declarative form builder with validation support.
|
|
|
241
282
|
- `resetText`: string (default: 'Reset')
|
|
242
283
|
- `showReset`: boolean (default: true)
|
|
243
284
|
|
|
285
|
+
Field types: `text`, `email`, `password`, `number`, `tel`, `url`, `select`, `date`, `textarea`, `checkbox`, `radio`, `file`, `switch`, `color` (mounts `ColorPicker`; accepts `swatches` / `showAlpha`), `custom`. Every field accepts `helperText` (hint under the field; the validation error replaces it while shown).
|
|
286
|
+
|
|
244
287
|
```jsx
|
|
245
288
|
const formFields = [
|
|
246
289
|
{
|
|
@@ -273,7 +316,8 @@ actions, and expandable row details.
|
|
|
273
316
|
- `data`: Array<object>
|
|
274
317
|
- `columns`: Array<Column>
|
|
275
318
|
- `sortable`: boolean (default: true)
|
|
276
|
-
- `filterable`: boolean
|
|
319
|
+
- `filterable`: boolean — per-column filter inputs; filter client-side data as you type (not in `serverSide` mode)
|
|
320
|
+
- `globalSearch`: boolean — search box; filters client-side data with or without `filterable` (skips `searchable: false` columns)
|
|
277
321
|
- `selectable`: boolean
|
|
278
322
|
- `pagination`: boolean
|
|
279
323
|
- `pageSize`: number (default: 25)
|
|
@@ -281,7 +325,7 @@ actions, and expandable row details.
|
|
|
281
325
|
- `onFilter`: (filters: object) => void
|
|
282
326
|
- `onSelectionChange`: (selectedRows: object[]) => void
|
|
283
327
|
- `onRowClick`: (row: object, index: number) => void
|
|
284
|
-
- `onPageChange`: (page: number) => void
|
|
328
|
+
- `onPageChange`: (page: number) => void — also called when the table clamps a page that no longer exists after the data shrinks
|
|
285
329
|
- `hasDetails`: boolean | ((row, index) => boolean)
|
|
286
330
|
- `DetailsComponent`: ReactComponent
|
|
287
331
|
- `withAction`: boolean (default: true)
|
|
@@ -295,6 +339,15 @@ actions, and expandable row details.
|
|
|
295
339
|
- `onSave`: ({ updatedRows, newRows, allData, tableData }) => void | Promise<allData | { data: allData } | void>
|
|
296
340
|
- `onEdit`: ({ rowKey, colKey, newValue, row, action, isNew }) => void
|
|
297
341
|
- `showSerial`: boolean (default: true)
|
|
342
|
+
- `showToolbar`: boolean (default: true) — `false` hides the toolbar (reload, page size, result count, pager header) and the column picker; interactive save controls still render
|
|
343
|
+
- `showColumnPicker`: boolean (default: true) — hide just the column-picker button and its column
|
|
344
|
+
- `wrapCells`: boolean (default: false) — let every cell wrap; a column's own `wrap` wins
|
|
345
|
+
- `mobileMaxFields`: number — cap the fields shown on each mobile card (column order, after `hideOnMobile`)
|
|
346
|
+
- `serverSide` / `totalRecords`: in server-side mode the "start - end of total" counter reads `totalRecords`
|
|
347
|
+
|
|
348
|
+
Column extras: `searchable: false` keeps a column out of the global search
|
|
349
|
+
(ids, codes), `wrap: true` lets its text wrap instead of staying on one line, and
|
|
350
|
+
`hideOnMobile: true` leaves it off the mobile card view (desktop is unaffected).
|
|
298
351
|
|
|
299
352
|
An `Action` is `{ name, label, icon, render?, onClick?, hidden?, disabled?,
|
|
300
353
|
inline? }`. `label`, `icon`, `hidden` and `disabled` each accept a value **or** a
|
|
@@ -335,6 +388,7 @@ Calendar-based date selection with keyboard navigation.
|
|
|
335
388
|
- `placeholder`: string (default: 'Select date...')
|
|
336
389
|
- `label`: string
|
|
337
390
|
- `error`: string
|
|
391
|
+
- `helperText`: ReactNode — hint below the trigger; replaced by `error` while shown
|
|
338
392
|
- `disabled`: boolean
|
|
339
393
|
- `required`: boolean
|
|
340
394
|
- `minDate`: Date | string
|
|
@@ -367,22 +421,31 @@ Collapsible sidebar navigation with nested items.
|
|
|
367
421
|
- `logo`: ReactNode
|
|
368
422
|
- `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>}
|
|
369
423
|
- `onUserClick`: (user: object) => void
|
|
424
|
+
- `linkAs`: ElementType (default: `"a"`) — e.g. Next.js `Link`
|
|
425
|
+
- `expandedIds` / `defaultExpandedIds`: string[] — open groups (controlled / uncontrolled); `onExpandedChange`: (ids: string[]) => void
|
|
426
|
+
- `footer`: ReactNode | ({ collapsed, isMobile, closeMobile }) => ReactNode — pinned region above the user block
|
|
427
|
+
- `stickyOffset`: number | string — height of a top bar above the sidebar (sticks below it, fits the viewport); `containerClassName` for the outer sticky wrapper
|
|
428
|
+
- `isMobileOpen` / `setIsMobileOpen`: mobile drawer — inert while closed; focus-trapped, Escape-to-close and focus-restoring while open
|
|
429
|
+
|
|
430
|
+
Every item renders as an `<a>`. Give an item an `href` and it becomes a real link, so Ctrl/Cmd-click or middle-click opens it in a new tab. Parents with an `href` navigate and expand; their chevron toggles without navigating. The group holding an `active: true` item (any depth) opens on load. Items take `badge` (+ accessible `badgeLabel`) and top-level `group` (section heading, a divider when collapsed — same as `AppShell`). With `onUserClick` the user block is a real `<button>`.
|
|
370
431
|
|
|
371
432
|
```jsx
|
|
372
433
|
const sidebarItems = [
|
|
373
434
|
{
|
|
374
435
|
id: 'dashboard',
|
|
375
436
|
label: 'Dashboard',
|
|
437
|
+
href: '/dashboard',
|
|
376
438
|
icon: <Home className="h-4 w-4" />,
|
|
377
439
|
active: true
|
|
378
440
|
},
|
|
379
441
|
{
|
|
380
442
|
id: 'users',
|
|
381
443
|
label: 'Users',
|
|
444
|
+
href: '/users',
|
|
382
445
|
icon: <Users className="h-4 w-4" />,
|
|
383
446
|
children: [
|
|
384
|
-
{ id: 'all-users', label: 'All Users' },
|
|
385
|
-
{ id: 'new-user', label: 'Add New User' }
|
|
447
|
+
{ id: 'all-users', label: 'All Users', href: '/users' },
|
|
448
|
+
{ id: 'new-user', label: 'Add New User', href: '/users/new' }
|
|
386
449
|
]
|
|
387
450
|
}
|
|
388
451
|
];
|
|
@@ -398,9 +461,10 @@ const sidebarItems = [
|
|
|
398
461
|
Top navigation bar with user menu and notifications.
|
|
399
462
|
|
|
400
463
|
**Props:**
|
|
401
|
-
- `logo`: ReactNode
|
|
464
|
+
- `logo`: ReactNode (truncates on narrow screens)
|
|
465
|
+
- `center`: ReactNode — centre slot (e.g. a search field); shrinks first so the bar fits a 390px phone
|
|
402
466
|
- `items`: Array<NavItem>
|
|
403
|
-
- `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>}
|
|
467
|
+
- `user`: {name: string, email: string, avatar?: string, menuItems?: Array<MenuItem>} — without `avatar`, the menu shows `name` initials; below `sm` the logout icon moves into the user menu as "Sign out"
|
|
404
468
|
- `notifications`: Array<Notification>
|
|
405
469
|
- `searchable`: boolean
|
|
406
470
|
- `onSearch`: (term: string) => void
|
|
@@ -420,6 +484,38 @@ Top navigation bar with user menu and notifications.
|
|
|
420
484
|
/>
|
|
421
485
|
```
|
|
422
486
|
|
|
487
|
+
#### FootNav
|
|
488
|
+
Fixed bottom tab bar for phones (`md:hidden`). Uses the semantic `bg-surface` / `border-line` tokens, so it follows dark mode.
|
|
489
|
+
|
|
490
|
+
**Props:**
|
|
491
|
+
- `items`: Array<{ id, label, icon, onClick?, active?, badge? }>
|
|
492
|
+
- `className`: string
|
|
493
|
+
|
|
494
|
+
The `active` item gets a tinted primary background, `text-primary-700` and `aria-current="page"` (inactive tabs are neutral `text-muted`). Badges sit on their own item's icon. Items share the width and labels truncate, so five tabs fit a 375px screen.
|
|
495
|
+
|
|
496
|
+
```jsx
|
|
497
|
+
<FootNav
|
|
498
|
+
items={[
|
|
499
|
+
{ id: 'home', label: 'Home', icon: <Home />, active: true, onClick: () => navigate('/') },
|
|
500
|
+
{ id: 'inbox', label: 'Inbox', icon: <Inbox />, badge: 3, onClick: () => navigate('/inbox') },
|
|
501
|
+
]}
|
|
502
|
+
/>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### Breadcrumbs
|
|
506
|
+
Breadcrumb trail with an optional Home link.
|
|
507
|
+
|
|
508
|
+
**Props:**
|
|
509
|
+
- `items`: Array<{ id?, label, href?, icon?, onClick? }> — `id` is optional (used as the React key when present)
|
|
510
|
+
- `separator`: ReactNode
|
|
511
|
+
- `showHome`: boolean (default: true)
|
|
512
|
+
- `homeIcon`: ReactNode
|
|
513
|
+
- `onHomeClick`: () => void
|
|
514
|
+
|
|
515
|
+
```jsx
|
|
516
|
+
<Breadcrumbs items={[{ label: 'Projects', href: '/projects' }, { label: 'Alpha' }]} />
|
|
517
|
+
```
|
|
518
|
+
|
|
423
519
|
### Feedback Components
|
|
424
520
|
|
|
425
521
|
#### Alert
|
|
@@ -455,6 +551,23 @@ Notification messages with different types, actions, bottom progress bar, and po
|
|
|
455
551
|
- `onClose`: (id: string) => void
|
|
456
552
|
- `position`: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center' | 'bottom-center'
|
|
457
553
|
|
|
554
|
+
**App-level provider (recommended).** Wrap the app once in `ToastProvider`; every `useToast()` below it shares one queue and the provider renders it — no per-screen `ToastContainer`:
|
|
555
|
+
|
|
556
|
+
```jsx
|
|
557
|
+
import { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';
|
|
558
|
+
|
|
559
|
+
<ToastProvider position="top-right">
|
|
560
|
+
<App />
|
|
561
|
+
</ToastProvider>;
|
|
562
|
+
|
|
563
|
+
function SaveButton() {
|
|
564
|
+
const { toast } = useToast();
|
|
565
|
+
return <button onClick={() => toast.success('Saved')}>Save</button>;
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Standalone (no provider).** Outside a provider each `useToast()` owns its own queue; render it with `ToastContainer`. Under a provider, a `ToastContainer` handed the provider's own `toasts` renders nothing, so it never double-renders while you migrate.
|
|
570
|
+
|
|
458
571
|
```jsx
|
|
459
572
|
import { useToast, ToastContainer } from '@dreamtree-org/twreact-ui';
|
|
460
573
|
|
|
@@ -567,6 +680,35 @@ const tabItems = [
|
|
|
567
680
|
<Tabs items={tabItems} variant="pills" />
|
|
568
681
|
```
|
|
569
682
|
|
|
683
|
+
### Overlays, inputs and lists
|
|
684
|
+
|
|
685
|
+
Interaction primitives that products used to compose by hand. All are `forwardRef`, accept `className` + `...rest`, are keyboard-operable with explicit ARIA, dark-mode aware and respect `prefers-reduced-motion`.
|
|
686
|
+
|
|
687
|
+
| Component | Group | Use for |
|
|
688
|
+
|---|---|---|
|
|
689
|
+
| [`Drawer`](doc/Drawer.md) | Feedback | Side/edge sheet (`placement` right/left/top/bottom, `size` sm–full) — record peeks and editors. Same `isOpen`/`onClose` contract as `Dialog`; focus trap, Escape/backdrop close, focus restore, scroll lock; `Drawer.Header/Body/Footer` |
|
|
690
|
+
| [`Popover`](doc/Popover.md) | Core | Anchored, click-triggered panel with interactive content (unlike `Tooltip`). Controlled/uncontrolled `open`, `placement` with flip, Escape/outside-click close, `aria-expanded`/`aria-controls`/`aria-haspopup="dialog"` |
|
|
691
|
+
| [`Textarea`](doc/Textarea.md) | Core | Multi-line input mirroring `Input`: `label`, `error`, `helperText`, `required`, `showCount`/`maxLength`, `autoResize`/`maxRows` |
|
|
692
|
+
| [`Slider`](doc/Slider.md) | Core | Single value or `[low, high]` range; `min`/`max`/`step`, arrows/PageUp/PageDown/Home/End, `role="slider"` with full `aria-value*` |
|
|
693
|
+
| [`Sortable`](doc/Sortable.md) | Utility | Drag-to-reorder list (pointer events, no extra dependency) + keyboard reordering (Space, arrows, Space/Escape) with live announcements; `items`, `onReorder`, `renderItem`, `getKey` |
|
|
694
|
+
| [`Tour`](doc/Tour.md) | Feedback | Step-based onboarding: spotlight on each `target` (selector or ref), Back/Next/Skip/Finish, `open`/`onClose`/`onFinish`, scrolls targets into view |
|
|
695
|
+
|
|
696
|
+
```jsx
|
|
697
|
+
import { Drawer, Popover, Textarea, Slider, Sortable, Tour, Button } from '@dreamtree-org/twreact-ui';
|
|
698
|
+
|
|
699
|
+
<Drawer isOpen={open} onClose={() => setOpen(false)} title="Edit task" footer={<Button onClick={save}>Save</Button>}>
|
|
700
|
+
<Textarea label="Description" autoResize showCount maxLength={500} />
|
|
701
|
+
</Drawer>
|
|
702
|
+
|
|
703
|
+
<Popover trigger={<Button variant="outline">Filters</Button>} title="Filters">
|
|
704
|
+
<Slider label="Budget" defaultValue={[2000, 8000]} min={0} max={10000} step={500} showValue />
|
|
705
|
+
</Popover>
|
|
706
|
+
|
|
707
|
+
<Sortable aria-label="Backlog" items={tasks} onReorder={setTasks} renderItem={(t) => t.title} />
|
|
708
|
+
|
|
709
|
+
<Tour open={tourOpen} steps={[{ title: 'Welcome' }, { target: '[data-tour="new-task"]', title: 'Capture work' }]} onClose={() => setTourOpen(false)} />
|
|
710
|
+
```
|
|
711
|
+
|
|
570
712
|
### Page blocks
|
|
571
713
|
|
|
572
714
|
Page-level patterns extracted from the Dreamtree products. Build pages from these before composing layouts by hand.
|
|
@@ -590,6 +732,29 @@ import { PageHeader, StatCard, StatusPill, EmptyState, Button } from '@dreamtree
|
|
|
590
732
|
|
|
591
733
|
Component types now carry their props (`<Button variant="bogus" />` is a type error).
|
|
592
734
|
|
|
735
|
+
### Hooks
|
|
736
|
+
|
|
737
|
+
#### useApi
|
|
738
|
+
Thin axios wrapper. Returns `{ data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith }`.
|
|
739
|
+
|
|
740
|
+
- `fetchApi(key, data?, needLoading?)` calls `apiMap[key](data)` with **one** argument.
|
|
741
|
+
- `fetchApiWith(key, args, { needLoading }?)` spreads `args` into the map entry — use it for entries like `(id, body) => ({...})`.
|
|
742
|
+
|
|
743
|
+
```jsx
|
|
744
|
+
const { fetchApi, fetchApiWith } = useApi({
|
|
745
|
+
BASE_URL: 'https://api.example.com',
|
|
746
|
+
apiMap: {
|
|
747
|
+
getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),
|
|
748
|
+
updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
fetchApi('getUser', 7);
|
|
753
|
+
fetchApiWith('updateUser', [7, { name: 'Ada' }]);
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
See [doc/useApi.md](doc/useApi.md).
|
|
757
|
+
|
|
593
758
|
## Theming
|
|
594
759
|
|
|
595
760
|
Every colour in the compiled CSS is a CSS variable (`--dt-primary-600`, `--dt-surface`, …) generated from `tokens/tokens.json` (W3C Design Tokens format). The defaults equal the classic palette, so nothing changes until you override a variable.
|
|
@@ -626,6 +791,10 @@ This adds semantic utilities next to the palettes: `bg-surface`, `bg-background`
|
|
|
626
791
|
|
|
627
792
|
**Design rules:** [`DESIGN.md`](DESIGN.md) is the rule set for product screens and AI agents: tokens only, blocks first, loading, empty and error states, 390 px, no native dialogs. It is generated from the tokens (`npm run build:tokens`).
|
|
628
793
|
|
|
794
|
+
**Colour props are overrides, not defaults.** Without colour props, `ProgressBar`, `Checkbox`, `Rate` and `Skeleton` use token classes, so a brand file recolours them. Their HEX props (`color`, `bgColor`, `borderColor`, `activeColor`, `inactiveColor`) still work but are deprecated.
|
|
795
|
+
|
|
796
|
+
**SSR:** `Tabs`, `Tooltip` and `Switch` generate ids with `React.useId()`, so server-rendered pages (Next.js) hydrate without mismatches.
|
|
797
|
+
|
|
629
798
|
## Dark Mode
|
|
630
799
|
|
|
631
800
|
Enable dark mode with the built-in theme provider:
|
|
@@ -63,6 +63,14 @@ module.exports = {
|
|
|
63
63
|
};
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
+
Import from the package root only (`import { Button } from '@dreamtree-org/twreact-ui'`).
|
|
67
|
+
The root import tree-shakes, so do not suggest deep paths into `dist/` or `src/` for components.
|
|
68
|
+
|
|
69
|
+
**Next.js App Router:** components ship with `'use client'`, so a Server Component can import
|
|
70
|
+
them directly. Do not generate a `"use client"` wrapper file that re-exports the library.
|
|
71
|
+
Compound members (`Card.Header`, `Tabs.List`, `Dialog.Footer`) and function props (`onClick`)
|
|
72
|
+
still require a `'use client'` file. `cn`, `Helpers` and `Emitter` also work on the server.
|
|
73
|
+
|
|
66
74
|
## Live prop contracts via MCP (prefer this over guessing)
|
|
67
75
|
|
|
68
76
|
An **MCP server bundled in this package** serves the authoritative prop
|
|
@@ -99,10 +107,10 @@ The library exports four kinds of things from `@dreamtree-org/twreact-ui`:
|
|
|
99
107
|
|
|
100
108
|
| Group | Components |
|
|
101
109
|
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
-
| **Core** | `Input`, `Button`, `Select`, `Table`, `Form`, `Accordion`, `Checkbox`, `ColorPicker`, `DatePicker`, `DateRangePicker`, `Loader`, `LocationPicker`, `PriceRangePicker`, `ProgressBar`, `Radio`, `Rate`, `RoundedTag`, `Skeleton`, `Switch`, `Tabs`, `ThreeDotPopover`, `Tooltip`, `SpeechToText`, `TextToSpeech` |
|
|
110
|
+
| **Core** | `Input`, `Button`, `Select`, `Table`, `Form`, `Accordion`, `Checkbox`, `ColorPicker`, `DatePicker`, `DateRangePicker`, `Loader`, `LocationPicker`, `PriceRangePicker`, `ProgressBar`, `Radio`, `Rate`, `RoundedTag`, `Skeleton`, `Switch`, `Tabs`, `ThreeDotPopover`, `Tooltip`, `SpeechToText`, `TextToSpeech`, `Textarea`, `Slider`, `Popover` |
|
|
103
111
|
| **Navigation** | `Sidebar`, `Navbar`, `FootNav`, `Breadcrumbs` |
|
|
104
|
-
| **Feedback** | `Dialog`, `Toast` (+ `ToastContainer`, `useToast`), `Alert`
|
|
105
|
-
| **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel`
|
|
112
|
+
| **Feedback** | `Dialog`, `Toast` (+ `ToastProvider`, `ToastContainer`, `useToast`), `Alert`, `Drawer` (side sheet; `isOpen`/`onClose` like Dialog), `Tour` (onboarding spotlight) |
|
|
113
|
+
| **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel`, `Sortable` (drag + keyboard reorder) |
|
|
106
114
|
| **Blocks** | `AppShell`, `PageHeader`, `StatCard`, `StatusPill`, `EmptyState`, `ErrorState`, `LoadingState`, `ConfirmDialog` — page-level patterns; build pages from these before composing layouts by hand |
|
|
107
115
|
|
|
108
116
|
### Hooks
|
|
@@ -111,7 +119,7 @@ The library exports four kinds of things from `@dreamtree-org/twreact-ui`:
|
|
|
111
119
|
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
120
|
| `useTheme` | Read/write the current theme (`light | dark`); pairs with `<ThemeProvider>`. Throws if used outside the provider. |
|
|
113
121
|
| `ThemeProvider` | Provider that persists theme to `localStorage` (`dreamtree-theme`) and toggles `data-theme` on `<html>`. |
|
|
114
|
-
| `useApi` | Thin axios wrapper. Returns `{data, error, loading,
|
|
122
|
+
| `useApi` | Thin axios wrapper. Returns `{data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith}`. Accepts `BASE_URL`, `DEFAULT_HEADERS`, `apiMap`. |
|
|
115
123
|
| `useMixins` | Bridge between component state, Redux store, and an in-memory cache. Power-user hook — prefer local React state for new code. |
|
|
116
124
|
|
|
117
125
|
### Store
|
|
@@ -149,8 +157,9 @@ Components use a **shared variant/size/focus vocabulary**:
|
|
|
149
157
|
|
|
150
158
|
- **Variants:** `primary | secondary | outline | ghost | destructive | success | warning`
|
|
151
159
|
(component-specific extras like `Badge: info` and `Alert: neutral` are documented per-component).
|
|
152
|
-
- **Sizes:** `xs | sm | md | lg | xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14`
|
|
153
|
-
and padding `px-2 / px-3 / px-4 / px-6 / px-8` (
|
|
160
|
+
- **Sizes:** `xs | sm | md | lg | xl | 2xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14 / h-16`
|
|
161
|
+
and padding `px-2 / px-3 / px-4 / px-6 / px-8 / px-10` (`Button` implements all six;
|
|
162
|
+
other components MAY support a subset and fall back to `md`).
|
|
154
163
|
- **Focus ring:** `focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500`
|
|
155
164
|
(the color token swaps for destructive / etc.).
|
|
156
165
|
- **Dark mode:** Tailwind `darkMode: 'class'`. Every color choice has a `dark:` counterpart.
|
|
@@ -162,12 +171,22 @@ When suggesting a component or new variant value, use this exact vocabulary.
|
|
|
162
171
|
**Blocks** are page-level patterns extracted from the products, so screens stop re-inventing them:
|
|
163
172
|
|
|
164
173
|
- `AppShell` — sidebar + top bar + mobile drawer; pass `nav` items and `linkAs={Link}` in Next.js.
|
|
174
|
+
- `Sidebar` — every item renders as an `<a>`; give items an `href` (and `linkAs={Link}` in Next.js) so Ctrl/Cmd-click opens a new tab. Parents with `href` navigate + expand; the chevron toggles. Mark the current page `active: true` and its group opens on load (control groups with `expandedIds`/`defaultExpandedIds`/`onExpandedChange`). Top-level `group: 'Name'` adds section headings; `badge` (+ `badgeLabel: '3 unread'`) shows counts; `footer` pins links above the user block; `stickyOffset={64}` when a top bar sits above it. The mobile drawer (`isMobileOpen`/`setIsMobileOpen`) is inert when shut and closes on Escape.
|
|
165
175
|
- `PageHeader` — the page's only `h1`, subtitle, one primary action, optional `back` link.
|
|
166
176
|
- `StatCard` — KPI tile (`label`, `value`, `icon`, `tone`, `change`).
|
|
167
177
|
- `StatusPill` — map domain states to `{ label, tone }` once; unknown states are humanized, never raw.
|
|
168
178
|
- `EmptyState`, `ErrorState`, `LoadingState` — every data card needs all three.
|
|
169
179
|
- `ConfirmDialog` — use instead of `window.confirm()`; title is the question, button is the verb.
|
|
170
180
|
|
|
181
|
+
**Interaction primitives** — reach for these before hand-rolling the pattern:
|
|
182
|
+
|
|
183
|
+
- `Drawer` — side/edge sheet for record peeks and editors (`isOpen`, `onClose`, `title`, `placement`, `size`, `footer`); don't fake one with `Dialog size="lg"`.
|
|
184
|
+
- `Popover` — click-triggered panel with controls (`trigger={<Button>…</Button>}`, children or `({ close }) => …`); `Tooltip` is for read-only hover text only.
|
|
185
|
+
- `Textarea` — same API as `Input` plus `autoResize`/`maxRows`; never a raw `<textarea>`.
|
|
186
|
+
- `Slider` — `defaultValue={40}` for one thumb, `[low, high]` for a range; `onChangeEnd` to save after a drag.
|
|
187
|
+
- `Sortable` — `items` + `onReorder(newItems)` + `renderItem`; keyboard reordering and announcements are built in. Pass `handle` and spread `handleProps` on a grip for touch screens.
|
|
188
|
+
- `Tour` — onboarding coach marks: `steps=[{ target: '[data-tour="x"]', title, content, placement }]`, `open`, `onClose(reason)`; a missing target falls back to a centred card.
|
|
189
|
+
|
|
171
190
|
**Tokens:** colours are CSS variables (`--dt-primary-600`, `--dt-surface`, …) generated from
|
|
172
191
|
`tokens/tokens.json`. Use semantic classes (`bg-primary-600`, `bg-surface`, `text-foreground`,
|
|
173
192
|
`text-muted`, `border-line`, `text-micro`, `rounded-card`) — never hex values or Tailwind's raw
|
|
@@ -216,17 +235,48 @@ import { Input } from '@dreamtree-org/twreact-ui';
|
|
|
216
235
|
`Form` integrates with `react-hook-form` + `yup` (`@hookform/resolvers`).
|
|
217
236
|
Validation rules live in consumer code.
|
|
218
237
|
|
|
238
|
+
**Helper text:** `Input`, `Select`, `DatePicker`, `DateRangePicker` and every
|
|
239
|
+
`Form` field take `helperText` (rendered below, linked via `aria-describedby`).
|
|
240
|
+
While `error` is set it **replaces** the helper text — don't render both. Don't
|
|
241
|
+
hand-roll a `<p>` hint under a field.
|
|
242
|
+
|
|
243
|
+
```jsx
|
|
244
|
+
<Select label="Role" helperText="Controls access." options={roles} value={role} onChange={setRole} />
|
|
245
|
+
<Form fields={[
|
|
246
|
+
{ type: 'email', name: 'email', label: 'Email', helperText: 'Receipts only.' },
|
|
247
|
+
{ type: 'color', name: 'brand', label: 'Brand colour', swatches: ['#2563eb'] }, // ColorPicker
|
|
248
|
+
]} />
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`Form` field types: `text | email | password | number | tel | url | select |
|
|
252
|
+
date | textarea | checkbox | radio | file | switch | color | custom`. Use
|
|
253
|
+
`type: 'color'` rather than a `custom` field wrapping `ColorPicker`.
|
|
254
|
+
|
|
255
|
+
**Select `grouped`** accepts `[{ label, options }]` groups **or** flat options
|
|
256
|
+
with a `group` key (`{ value, label, group: 'Code' }`) — no need to pre-group.
|
|
257
|
+
|
|
258
|
+
**Navbar** takes a `center` slot (search field / primary action) and shows
|
|
259
|
+
`user.name` initials when `user.avatar` is missing; it fits a 390px phone.
|
|
260
|
+
|
|
261
|
+
**Tooltip** around a component (e.g. `<Button>`) in a row/grid: use
|
|
262
|
+
`customTrigger={<Button>…</Button>}` so there is one tab stop, not two. A lone
|
|
263
|
+
native `<button>` / `<a href>` / `<input>` child is detected automatically.
|
|
264
|
+
|
|
219
265
|
### Toast
|
|
220
266
|
|
|
267
|
+
Wrap the app ONCE in `ToastProvider`; every `useToast()` below it shares one
|
|
268
|
+
queue and the provider renders it. (Without a provider each `useToast()` owns a
|
|
269
|
+
private queue that only shows where that same hook's `toasts` are passed to a
|
|
270
|
+
`<ToastContainer toasts={toasts} onRemove={removeToast} />`.)
|
|
271
|
+
|
|
221
272
|
```jsx
|
|
222
|
-
import {
|
|
273
|
+
import { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';
|
|
223
274
|
|
|
224
275
|
function App() {
|
|
225
276
|
return (
|
|
226
|
-
|
|
227
|
-
<ToastContainer />
|
|
277
|
+
<ToastProvider position="top-right">
|
|
228
278
|
<YourApp />
|
|
229
|
-
|
|
279
|
+
</ToastProvider>
|
|
230
280
|
);
|
|
231
281
|
}
|
|
232
282
|
|
|
@@ -256,14 +306,23 @@ toggles `data-theme` on `<html>`.
|
|
|
256
306
|
```jsx
|
|
257
307
|
import { useApi } from '@dreamtree-org/twreact-ui';
|
|
258
308
|
|
|
259
|
-
const { data, error, loading,
|
|
309
|
+
const { data, error, loading, fetchData, fetchApi, fetchApiWith } = useApi({
|
|
260
310
|
BASE_URL: 'https://api.example.com',
|
|
261
311
|
DEFAULT_HEADERS: { Authorization: `Bearer ${token}` },
|
|
312
|
+
apiMap: {
|
|
313
|
+
getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),
|
|
314
|
+
updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),
|
|
315
|
+
},
|
|
262
316
|
});
|
|
263
317
|
|
|
264
|
-
useEffect(() => {
|
|
318
|
+
useEffect(() => { fetchData('/users', 'GET'); }, []);
|
|
319
|
+
fetchApi('getUser', 7); // apiMap.getUser(7) — ONE argument
|
|
320
|
+
fetchApiWith('updateUser', [7, { name: 'A' }]); // apiMap.updateUser(7, {...}) — spread
|
|
265
321
|
```
|
|
266
322
|
|
|
323
|
+
`fetchApi(key, data)` always passes the map entry exactly one argument; use
|
|
324
|
+
`fetchApiWith(key, [a, b])` for multi-argument entries.
|
|
325
|
+
|
|
267
326
|
Treat `useApi` as a thin axios wrapper, not a caching/dedup layer. For
|
|
268
327
|
cache, the consumer brings their own (React Query, SWR, etc.).
|
|
269
328
|
|