@dreamtree-org/twreact-ui 1.3.8 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/DESIGN.md +11 -3
  2. package/README.md +7 -1
  3. package/ai-skills/dreamtree-ui.md +2 -2
  4. package/dist/charts/index.js +69 -24
  5. package/dist/components/charts/chartTheme.d.ts +2 -5
  6. package/dist/components/charts/chartTheme.d.ts.map +1 -1
  7. package/dist/components/core/Table/Table.d.ts.map +1 -1
  8. package/dist/components/core/Table/useClickOutside.d.ts +5 -2
  9. package/dist/components/core/Table/useClickOutside.d.ts.map +1 -1
  10. package/dist/components/utility/Portal.d.ts +6 -0
  11. package/dist/components/utility/Portal.d.ts.map +1 -0
  12. package/dist/esm/components/charts/CartesianChart.js +2 -1
  13. package/dist/esm/components/charts/DonutChart.js +2 -1
  14. package/dist/esm/components/charts/Sparkline.js +2 -1
  15. package/dist/esm/components/charts/chartTheme.js +1 -22
  16. package/dist/esm/components/core/Button.js +8 -8
  17. package/dist/esm/components/core/Table/Table.js +11 -5
  18. package/dist/esm/hooks/useClickOutside.js +51 -0
  19. package/dist/esm/hooks/useMediaQuery.js +61 -0
  20. package/dist/esm/hooks/useReducedMotion.js +17 -0
  21. package/dist/esm/hooks/useScrollLock.js +76 -9
  22. package/dist/esm/hooks/useTheme.js +6 -1
  23. package/dist/esm/utils/position.js +6 -0
  24. package/dist/hooks/useClickOutside.d.ts +14 -0
  25. package/dist/hooks/useClickOutside.d.ts.map +1 -0
  26. package/dist/hooks/useFloatingPosition.d.ts +37 -0
  27. package/dist/hooks/useFloatingPosition.d.ts.map +1 -0
  28. package/dist/hooks/useMediaQuery.d.ts +22 -0
  29. package/dist/hooks/useMediaQuery.d.ts.map +1 -0
  30. package/dist/hooks/usePresence.d.ts +16 -0
  31. package/dist/hooks/usePresence.d.ts.map +1 -0
  32. package/dist/hooks/usePresentation.d.ts +9 -0
  33. package/dist/hooks/usePresentation.d.ts.map +1 -0
  34. package/dist/hooks/useReducedMotion.d.ts +10 -0
  35. package/dist/hooks/useReducedMotion.d.ts.map +1 -0
  36. package/dist/hooks/useScrollLock.d.ts +11 -1
  37. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  38. package/dist/hooks/useTheme.d.ts.map +1 -1
  39. package/dist/index.css +1 -1
  40. package/dist/index.esm.css +1 -1
  41. package/dist/index.esm.js +142 -34
  42. package/dist/index.js +142 -34
  43. package/dist/tokens/tailwind3-preset.cjs +101 -8
  44. package/dist/tokens/theme.css +27 -0
  45. package/dist/tokens/tokens.json +175 -0
  46. package/dist/tokens/vars.css +2 -0
  47. package/dist/utils/position.d.ts.map +1 -1
  48. package/dist/utils/touchTarget.d.ts +1 -0
  49. package/dist/utils/touchTarget.d.ts.map +1 -1
  50. package/mcp/catalog.snapshot.json +1 -1
  51. package/package.json +2 -1
  52. package/tokens/lib.cjs +35 -0
  53. package/tokens/tokens.json +58 -2
  54. package/dist/esm/components/core/Table/useClickOutside.js +0 -26
@@ -5370,5 +5370,5 @@
5370
5370
  "Tour": "# Tour\n\nStep-based onboarding tour (coach marks): dims the page, spotlights each step's target element and shows a step card beside it with Back / Next / Skip / Finish. Generalises the `SpotlightTour` that TaskPro and ProcurementPro each hand-rolled. A step without a target — or whose target never renders — shows as a centred card, so the tour never stalls.\n\n## Import\n\n```jsx\nimport { Tour } from '@dreamtree-org/twreact-ui';\n```\n\n## Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `open` | `boolean` | `false` | Show the tour |\n| `steps` | `array` | `[]` | `TourStep[]`: `{ target, title, content, placement }` (see below) |\n| `onClose` | `function` | — | `(reason) => void`; reason is `skip`, `escape`, `close` or `finish`. Set `open` to false here |\n| `onFinish` | `function` | — | Called by the Finish button on the last step (before `onClose('finish')`) |\n| `step` | `number` | — | Controlled step index |\n| `defaultStep` | `number` | `0` | Uncontrolled starting step; the tour restarts here every time it opens |\n| `onStepChange` | `function` | — | `(index, step) => void`; use it to navigate routes before a step shows |\n| `labels` | `object` | — | Override `next`, `back`, `skip`, `finish`, `close` and `progress(current, total)` |\n| `targetTimeout` | `number` | `3000` | How long (ms) to wait for a step's target before falling back to a centred card |\n| `spotlightPadding` | `number` | `6` | Space in px between the target and the spotlight ring |\n| `className` | `string` | — | Extra classes for the step card (merged with `cn`) |\n| `...rest` | `HTMLAttributes` | — | Forwarded to the step card; `ref` points at it |\n\n## Step shape\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `target` | `string, ref, element or function` | CSS selector (e.g. `[data-tour=\"new-task\"]`), a React ref, a DOM element, or `() => element`. Omit for a centred intro/outro card |\n| `title` | `ReactNode` | Card heading; labels the dialog |\n| `content` | `ReactNode` | Card body; describes the dialog |\n| `placement` | `string` | `top`, `bottom` (default), `left`, `right` or `center`; flips when there is no room |\n\n## Examples\n\n### Basic\n\n```jsx\nconst steps = [\n { title: 'Welcome', content: 'A 30-second tour of your workspace.' },\n { target: '[data-tour=\"new-task\"]', title: 'Capture work', content: 'New task opens a quick form.', placement: 'bottom' },\n { target: searchRef, title: 'Jump anywhere', content: 'Search tasks and projects.', placement: 'right' },\n];\n\nconst [open, setOpen] = useState(false);\n\n<Button onClick={() => setOpen(true)}>Take the tour</Button>\n<Tour open={open} steps={steps} onClose={() => setOpen(false)} onFinish={() => markTourSeen()} />\n```\n\n### Route-aware (react-router)\n\n```jsx\n<Tour\n open={open}\n steps={steps}\n onStepChange={(i, step) => step.route && navigate(step.route)}\n onClose={() => setOpen(false)}\n/>\n```\n\nExtra fields on a step (like `route`) are passed back to you untouched. The tour waits up to `targetTimeout` for the next screen's target to render.\n\n## Accessibility & keyboard\n\n| Behavior | Detail |\n|----------|--------|\n| Role | The **step card** is `role=\"dialog\"` + `aria-modal=\"true\"`, labelled by the step title and described by its content (the full-screen layer has no role, so global `[role=dialog]` styles never paint over the page) |\n| Focus | Moves to the card on open and on every step change; `Tab` is trapped in the card; focus returns to the opener on close |\n| `Escape` | Closes with reason `escape` |\n| `ArrowRight` / `ArrowLeft` | Next / previous step (ignored while typing in a field inside the card) |\n| Scroll into view | The target is scrolled into view through its nearest scrolling ancestor only (never `overflow: hidden` shells); smooth unless `prefers-reduced-motion` |\n| Reduced motion | Spotlight and card transitions are disabled under `prefers-reduced-motion` |\n| Scroll lock | Body scroll is locked while open |\n| Progress | \"Step 2 of 5\" text is visible and read with the card |\n| Dark mode | Card uses `dark:` variants; the spotlight ring switches to `primary-400` |\n\n[← Component overview](README.md)\n",
5371
5371
  "useApi": "# useApi\n\nThin axios wrapper hook. Sends requests by URL (`fetchData`) or by key from an `apiMap` (`fetchApi` / `fetchApiWith`) and tracks `data`, `error`, `loading` and `lastRequest`. It is not a cache/dedup layer — bring React Query or SWR for that.\n\n## Import\n\n```jsx\nimport { useApi } from '@dreamtree-org/twreact-ui';\n```\n\n## Props\n\nOptions object passed to `useApi(options)`.\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `BASE_URL` | `string` | `null` | axios `baseURL` |\n| `DEFAULT_HEADERS` | `object` | `{}` | Headers sent with every request |\n| `apiMap` | `Record<string, (...args) => { url, method, headers?, data? }>` | `{}` | Named request builders. Entries may take any number of positional arguments |\n| `createRequestInstance` | `function` | `axios.create` | `(baseURL, defaultHeaders) => AxiosInstance` |\n| `sendRequest` | `function` | — | `(instance, method, url, data, headers) => Promise<AxiosResponse>`; GET/HEAD/DELETE send no body |\n\n## Returns\n\n| Field | Type | Description |\n|------|------|-------------|\n| `data` | `any` | Last successful `response.data` |\n| `error` | `Error \\| null` | Last error |\n| `loading` | `boolean` | True while a request with `needLoading` is in flight |\n| `lastRequest` | `string \\| null` | Key of the last `fetchApi` / `fetchApiWith` call |\n| `fetchData(url, method, data?, headers?, { needLoading? })` | `Promise` | Send a request directly |\n| `fetchApi(apiKey, data?, needLoading = true)` | `Promise` | Call `apiMap[apiKey](data)` — **exactly one argument** — and send the result |\n| `fetchApiWith(apiKey, args = [], { needLoading = true })` | `Promise` | Call `apiMap[apiKey](...args)` — every element of `args` is a positional argument — and send the result |\n\n### fetchApi vs fetchApiWith\n\n`fetchApi` always calls the map entry with ONE argument. An entry written as `(id, body) => ({...})` called through `fetchApi('updateUser', id)` gets `body === undefined`, so nothing is sent. Use `fetchApiWith` for multi-argument entries:\n\n```jsx\nfetchApiWith('updateUser', [id, body]); // apiMap.updateUser(id, body)\nfetchApiWith('updateUser', [id, body], { needLoading: false });\n```\n\n`fetchApi` is unchanged; existing single-argument callers keep working.\n\n## Examples\n\n```jsx\nconst apiMap = {\n getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),\n updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),\n};\n\nfunction Profile({ id }) {\n const { data, loading, error, fetchApi, fetchApiWith } = useApi({\n BASE_URL: 'https://api.example.com',\n DEFAULT_HEADERS: { Authorization: `Bearer ${token}` },\n apiMap,\n });\n\n useEffect(() => { fetchApi('getUser', id); }, [id]);\n\n const save = (body) => fetchApiWith('updateUser', [id, body]);\n // …\n}\n```\n\n[← Component overview](README.md)\n"
5372
5372
  },
5373
- "skill": "# Dreamtree UI — AI assistant reference\n\n> Skill installed by `npx @dreamtree-org/twreact-ui init --ai <provider>`.\n> Source of truth: [`@dreamtree-org/twreact-ui`](https://www.npmjs.com/package/@dreamtree-org/twreact-ui).\n> Re-run the installer to refresh this block when the library updates.\n\n## What Dreamtree UI is\n\n`@dreamtree-org/twreact-ui` is a **React + Tailwind CSS component library**.\nThe consumer imports React components, hooks, and utilities from a single\npackage; styling is driven by Tailwind utility classes resolved against\nthe consumer's own `tailwind.config.js`. Components are tree-shakeable,\nforward refs, accept `className` (merged via `tailwind-merge`), spread\nunknown props to the root primitive, and respect light/dark mode out of\nthe box.\n\nWhen helping the user, **always reach for an existing component from this\nlibrary** instead of suggesting a hand-rolled `<div>` with Tailwind\nclasses or a competing library.\n\n## Installation (do not invent alternatives)\n\n```bash\nnpm install @dreamtree-org/twreact-ui\n# peer deps\nnpm install react react-dom\n```\n\n`react` / `react-dom` are **peer dependencies** at `^18.3.1`. React 19\nis not yet supported.\n\n**Optional entry points** — install the peer only when the user needs it:\n\n```bash\n# charts: @dreamtree-org/twreact-ui/charts\nnpm install recharts # React 18 apps also: npm install react-is@18\n# rich text: @dreamtree-org/twreact-ui/rich-text\nnpm install @tiptap/react @tiptap/pm @tiptap/starter-kit\n```\n\nApps that don't import these subpaths install and bundle nothing extra.\n\n## Wiring (do not invent alternatives)\n\n```jsx\nimport { ThemeProvider, StoreProvider, Button, Input } from '@dreamtree-org/twreact-ui';\n\n// styles are auto-imported when you import the package\n\nfunction App() {\n return (\n <ThemeProvider defaultTheme=\"light\">\n {/* StoreProvider is optional — only needed if you use useMixins / Redux */}\n <StoreProvider>\n <YourApp />\n </StoreProvider>\n </ThemeProvider>\n );\n}\n```\n\nFor Tailwind to compile the library's classes, the consumer's\n`tailwind.config.js` `content[]` MUST include the package:\n\n```js\n// consumer's tailwind.config.js\nmodule.exports = {\n content: [\n './src/**/*.{js,jsx,ts,tsx}',\n './node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}',\n ],\n darkMode: 'class', // matches the library's strategy\n theme: { extend: { /* override primary/secondary/error/success/warning palettes here */ } },\n};\n```\n\nImport from the package root only (`import { Button } from '@dreamtree-org/twreact-ui'`).\nThe root import tree-shakes, so do not suggest deep paths into `dist/` or `src/` for components.\n\n**Next.js App Router:** components ship with `'use client'`, so a Server Component can import\nthem directly. Do not generate a `\"use client\"` wrapper file that re-exports the library.\nCompound members (`Card.Header`, `Tabs.List`, `Dialog.Footer`) and function props (`onClick`)\nstill require a `'use client'` file. `cn`, `Helpers` and `Emitter` also work on the server.\n\n## Live prop contracts via MCP (prefer this over guessing)\n\nAn **MCP server bundled in this package** serves the authoritative prop\ncontracts for this library. If your client supports the Model Context Protocol,\nwire it up and **query it instead of guessing prop names**:\n\n```jsonc\n{\n \"mcpServers\": {\n \"dreamtree-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@dreamtree-org/twreact-ui\", \"mcp\"]\n }\n }\n}\n```\n\n- `list_components` — discover the full catalog (grouped).\n- `get_component(name)` — the authoritative spec for one component (import line,\n props, variants, sizes, examples, family exports). **Call this before using a\n component.** Accepts a family-export name (`useToast` → `Toast`).\n- `search_components(query)` — keyword search by capability.\n\nResources: `dreamtree://skill` (this guide) and `dreamtree://docs/<Component>`.\nPrompt: `compose_ui`. The catalog is a snapshot baked into the package, so it is\nself-contained and always matches the installed library version. The lists below\nare the fallback when the MCP server isn't connected.\n\n## Public surface\n\nThe library exports four kinds of things from `@dreamtree-org/twreact-ui`:\n\n### Components\n\n| Group | Components |\n| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **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` |\n| **Navigation** | `Sidebar`, `Navbar`, `FootNav`, `Breadcrumbs` |\n| **Feedback** | `Dialog`, `Toast` (+ `ToastProvider`, `ToastContainer`, `useToast`), `Alert`, `Drawer` (side sheet; `isOpen`/`onClose` like Dialog), `Tour` (onboarding spotlight) |\n| **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel`, `Sortable` (drag + keyboard reorder) |\n| **Blocks** | `AppShell`, `PageHeader`, `StatCard`, `StatusPill`, `EmptyState`, `ErrorState`, `LoadingState`, `ConfirmDialog` — page-level patterns; build pages from these before composing layouts by hand |\n| **Charts** (`@dreamtree-org/twreact-ui/charts`, peer `recharts`) | `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, `Sparkline` — NOT exported from the root |\n| **Rich text** (`@dreamtree-org/twreact-ui/rich-text`, peers `@tiptap/react @tiptap/pm @tiptap/starter-kit`) | `RichTextEditor` — NOT exported from the root |\n\n### Hooks\n\n| Hook | Purpose |\n| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |\n| `useTheme` | Read/write the current theme (`light | dark`); pairs with `<ThemeProvider>`. Throws if used outside the provider. |\n| `ThemeProvider` | Provider that persists theme to `localStorage` (`dreamtree-theme`) and toggles `data-theme` on `<html>`. |\n| `useApi` | Thin axios wrapper. Returns `{data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith}`. Accepts `BASE_URL`, `DEFAULT_HEADERS`, `apiMap`. |\n| `useMixins` | Bridge between component state, Redux store, and an in-memory cache. Power-user hook — prefer local React state for new code. |\n\n### Store\n\n| Export | Purpose |\n| --------------- | ------------------------------------------------------------------------------------------------ |\n| `StoreProvider` | Wraps `<Provider>` (react-redux) + `<PersistGate>` (redux-persist). Boots a slice + listener mw. |\n\n### Utils\n\n| Export | Signature | Use for |\n| --------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ |\n| `cn` | `cn(...inputs: ClassValue[]): string` | Merge Tailwind class strings (`twMerge(clsx(...))` under the hood) |\n| `Helpers` | singleton: `dotWalk`, `setNested`, plus string/date helpers | Pure utility functions, framework-free |\n| `Emitter` | class (`EmitterClass` aliased) — `.on`, `.off`, `.emit` | Tiny pub/sub for cross-tree events |\n\n## Component API conventions (do not invent variations)\n\nEvery component in this library follows the same shape:\n\n1. **`forwardRef`** when wrapping a single DOM element. Consumers may attach refs.\n2. **`className` is merged** via `cn(...)` — caller classes win on conflict.\n3. **`...rest` props pass through** to the root primitive (button, input, …).\n4. **Defaults via destructuring**, e.g. `variant = 'primary', size = 'md'`.\n5. **Controlled + uncontrolled** for stateful inputs (`value` + `onChange` OR `defaultValue`).\n6. **Theming is class-based**, never via a `color` prop. Re-skin via Tailwind config.\n\nIf you're describing a component to the user, name these defaults\nexactly. Do not propose a different shape (e.g. inventing a `color` prop\non `Button` or suggesting `tw` prop merging).\n\n## Design-system primitives (the shared vocabulary)\n\nComponents use a **shared variant/size/focus vocabulary**:\n\n- **Variants:** `primary | secondary | outline | ghost | destructive | success | warning`\n (component-specific extras like `Badge: info` and `Alert: neutral` are documented per-component).\n- **Sizes:** `xs | sm | md | lg | xl | 2xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14 / h-16`\n and padding `px-2 / px-3 / px-4 / px-6 / px-8 / px-10` (`Button` implements all six;\n other components MAY support a subset and fall back to `md`).\n- **Focus ring:** `focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500`\n (the color token swaps for destructive / etc.).\n- **Dark mode:** Tailwind `darkMode: 'class'`. Every color choice has a `dark:` counterpart.\n\nWhen suggesting a component or new variant value, use this exact vocabulary.\n\n## Page blocks and design tokens\n\n**Blocks** are page-level patterns extracted from the products, so screens stop re-inventing them:\n\n- `AppShell` — sidebar + top bar + mobile drawer; pass `nav` items and `linkAs={Link}` in Next.js.\n- `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.\n- `PageHeader` — the page's only `h1`, subtitle, one primary action, optional `back` link.\n- `StatCard` — KPI tile (`label`, `value`, `icon`, `tone`, `change`).\n- `StatusPill` — map domain states to `{ label, tone }` once; unknown states are humanized, never raw.\n- `EmptyState`, `ErrorState`, `LoadingState` — every data card needs all three.\n- `ConfirmDialog` — use instead of `window.confirm()`; title is the question, button is the verb.\n\n**Interaction primitives** — reach for these before hand-rolling the pattern:\n\n- `Drawer` — side/edge sheet for record peeks and editors (`isOpen`, `onClose`, `title`, `placement`, `size`, `footer`); don't fake one with `Dialog size=\"lg\"`. On phones use `placement=\"bottom\"` (+ `swipeToClose`) as the bottom sheet: it already has a grab `handle`, rounded top and safe-area padding — don't add them with `className`.\n- `FootNav` — phone tab bar. A centre \"+\" is an item with `variant: 'action'` (raised primary circle); give it `'aria-haspopup': 'dialog'` and `'aria-expanded'` when it opens a sheet.\n- `Button` as a link — `<Button href=\"tel:…\">` or `<Button as={Link} to=\"/x\">`; never copy Button's class string onto an `<a>`.\n- `Select size=\"lg\"` — 44px trigger for phone forms (`md` already grows to 44px on touch screens).\n- `Stepper` steps take `title` (or its alias `label`). `Radio` follows the primary token by default — don't pass `radioColor` to match the brand.\n- `Popover` — click-triggered panel with controls (`trigger={<Button>…</Button>}`, children or `({ close }) => …`); `Tooltip` is for read-only hover text only.\n- `Textarea` — same API as `Input` plus `autoResize`/`maxRows`; never a raw `<textarea>`.\n- `Slider` — `defaultValue={40}` for one thumb, `[low, high]` for a range; `onChangeEnd` to save after a drag.\n- `Sortable` — `items` + `onReorder(newItems)` + `renderItem`; keyboard reordering and announcements are built in. Pass `handle` and spread `handleProps` on a grip for touch screens.\n- 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.\n- `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.\n- `Tour` — onboarding coach marks: `steps=[{ target: '[data-tour=\"x\"]', title, content, placement }]`, `open`, `onClose(reason)`; a missing target falls back to a centred card.\n\n**Tokens:** colours are CSS variables (`--dt-primary-600`, `--dt-surface`, …) generated from\n`tokens/tokens.json`. Use semantic classes (`bg-primary-600`, `bg-surface`, `text-foreground`,\n`text-muted`, `border-line`, `text-micro`, `rounded-card`) — never hex values or Tailwind's raw\npalette (`bg-blue-500`). A product's brand lives in its own repo as `dreamtree.brand.json`;\n`npx @dreamtree-org/twreact-ui brand build --out app/brand.css` turns it into the CSS to load after\ntwreact-ui's CSS. To change a product's colours, edit that file, never the components.\nThe full rule set for screens is `DESIGN.md` in the package root.\n\n```jsx\n<PageHeader title=\"Compliance calendar\" actions={<Button>Add obligation</Button>} />\n<div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatCard label=\"Due this week\" value={18} icon={<CalendarDays className=\"h-6 w-6\" />} />\n <StatCard label=\"Overdue\" value={3} tone=\"error\" icon={<AlertCircle className=\"h-6 w-6\" />} />\n</div>\n<Card>{loading ? <LoadingState /> : error ? <ErrorState onRetry={retry} /> : rows.length ? <Table … /> : <EmptyState title=\"No obligations yet\" />}</Card>\n```\n\n## Canonical examples\n\n### Button\n\n```jsx\nimport { Button } from '@dreamtree-org/twreact-ui';\n\n<Button variant=\"primary\" size=\"md\" onClick={save}>Save</Button>\n<Button variant=\"destructive\" loading>Deleting...</Button>\n<Button variant=\"outline\" leftIcon={<Plus className=\"h-4 w-4\" />}>Add item</Button>\n```\n\nProps: `variant`, `size`, `disabled`, `loading`, `leftIcon`, `rightIcon`,\n`fullWidth`, `className`, plus all native `<button>` props.\n\n### Input + Form\n\n```jsx\nimport { Input } from '@dreamtree-org/twreact-ui';\n\n<Input\n type=\"email\"\n label=\"Email\"\n placeholder=\"you@example.com\"\n required\n clearable\n error={errors.email?.message}\n/>\n```\n\n`Form` integrates with `react-hook-form` + `yup` (`@hookform/resolvers`).\nValidation rules live in consumer code.\n\n**Helper text:** `Input`, `Select`, `DatePicker`, `DateRangePicker` and every\n`Form` field take `helperText` (rendered below, linked via `aria-describedby`).\nWhile `error` is set it **replaces** the helper text — don't render both. Don't\nhand-roll a `<p>` hint under a field.\n\n```jsx\n<Select label=\"Role\" helperText=\"Controls access.\" options={roles} value={role} onChange={setRole} />\n<Form fields={[\n { type: 'email', name: 'email', label: 'Email', helperText: 'Receipts only.' },\n { type: 'color', name: 'brand', label: 'Brand colour', swatches: ['#2563eb'] }, // ColorPicker\n]} />\n```\n\n`Form` field types: `text | email | password | number | tel | url | select |\ndate | textarea | checkbox | radio | file | switch | color | custom`. Use\n`type: 'color'` rather than a `custom` field wrapping `ColorPicker`.\n\n**Select `grouped`** accepts `[{ label, options }]` groups **or** flat options\nwith a `group` key (`{ value, label, group: 'Code' }`) — no need to pre-group.\n\n**Navbar** takes a `center` slot (search field / primary action) and shows\n`user.name` initials when `user.avatar` is missing; it fits a 390px phone.\n\n**Tooltip** around a component (e.g. `<Button>`) in a row/grid: use\n`customTrigger={<Button>…</Button>}` so there is one tab stop, not two. A lone\nnative `<button>` / `<a href>` / `<input>` child is detected automatically.\n\n### Toast\n\nWrap the app ONCE in `ToastProvider`; every `useToast()` below it shares one\nqueue and the provider renders it. (Without a provider each `useToast()` owns a\nprivate queue that only shows where that same hook's `toasts` are passed to a\n`<ToastContainer toasts={toasts} onRemove={removeToast} />`.)\n\n```jsx\nimport { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';\n\nfunction App() {\n return (\n <ToastProvider position=\"top-right\">\n <YourApp />\n </ToastProvider>\n );\n}\n\nfunction SomeButton() {\n const { toast } = useToast();\n return <button onClick={() => toast.success('Saved!')}>Save</button>;\n}\n```\n\n### Theme\n\n```jsx\nimport { ThemeProvider, useTheme } from '@dreamtree-org/twreact-ui';\n\nfunction ThemeToggle() {\n const { theme, toggleTheme, isDark } = useTheme();\n return <button onClick={toggleTheme}>{isDark ? '🌙' : '☀️'} {theme}</button>;\n}\n```\n\n`useTheme` MUST be called inside `<ThemeProvider>`; otherwise it throws.\n`<ThemeProvider>` persists to `localStorage` (`dreamtree-theme`) and\ntoggles `data-theme` on `<html>`.\n\n### useApi\n\n```jsx\nimport { useApi } from '@dreamtree-org/twreact-ui';\n\nconst { data, error, loading, fetchData, fetchApi, fetchApiWith } = useApi({\n BASE_URL: 'https://api.example.com',\n DEFAULT_HEADERS: { Authorization: `Bearer ${token}` },\n apiMap: {\n getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),\n updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),\n },\n});\n\nuseEffect(() => { fetchData('/users', 'GET'); }, []);\nfetchApi('getUser', 7); // apiMap.getUser(7) — ONE argument\nfetchApiWith('updateUser', [7, { name: 'A' }]); // apiMap.updateUser(7, {...}) — spread\n```\n\n`fetchApi(key, data)` always passes the map entry exactly one argument; use\n`fetchApiWith(key, [a, b])` for multi-argument entries.\n\nTreat `useApi` as a thin axios wrapper, not a caching/dedup layer. For\ncache, the consumer brings their own (React Query, SWR, etc.).\n\n### Charts and rich text (optional entry points)\n\n```jsx\nimport { LineChart, DonutChart, Sparkline } from '@dreamtree-org/twreact-ui/charts';\nimport { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';\n\n<LineChart\n data={[{ month: 'Jan', revenue: 4200 }, { month: 'Feb', revenue: 5100 }]}\n index=\"month\"\n categories={['revenue']}\n valueFormatter={(v) => `$${v.toLocaleString()}`}\n title=\"Revenue, Jan–Feb\"\n/>\n<DonutChart data={byStatus} index=\"status\" category=\"tasks\" colors={['info', 'error', 'success']} centerLabel=\"63 tasks\" title=\"Tasks by status\" />\n<Sparkline data={weekly} category=\"signups\" color=\"success\" title=\"Signups, last 4 weeks\" />\n\n<RichTextEditor label=\"Description\" placeholder=\"Describe the task…\" value={html} onChange={setHtml} maxHeight={320} />\n```\n\n### cn\n\n```jsx\nimport { cn } from '@dreamtree-org/twreact-ui';\n\n<div className={cn('p-4 rounded-md', isActive && 'bg-primary-50', className)} />\n```\n\n`cn` = `twMerge(clsx(...))` — later classes win on Tailwind conflicts.\n\n## Rules for AI assistants helping consumers\n\n1. **Use library components.** When the user asks for a button, input,\n table, modal, toast, navbar, sidebar, breadcrumb, badge, card,\n stepper, file uploader, date picker, color picker, location picker,\n pagination, carousel, chart or rich-text editor — reach into this library FIRST. Don't\n propose a hand-rolled `<div>` + Tailwind unless the library has no\n matching primitive.\n2. **Use the shared vocabulary.** `variant=\"primary\"`, `size=\"md\"`,\n `leftIcon={...}`, `fullWidth`, `clearable` — these are exact names.\n Don't invent `color=\"blue\"` or `iconLeft={...}`.\n3. **Wrap in `<ThemeProvider>`.** Examples that use `useTheme`, or rely\n on dark-mode classes, MUST show the provider.\n4. **`<StoreProvider>` is optional.** Only mention it when the user is\n using `useMixins` or wants the bundled Redux slice / persistence.\n Don't insist on it for every example.\n5. **Tailwind content[] config.** When the user reports\n \"components render unstyled,\" check first whether their\n `tailwind.config.js` `content[]` includes\n `./node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}`.\n6. **Don't deep-import.** The public modules are `@dreamtree-org/twreact-ui`\n plus the two optional entry points `@dreamtree-org/twreact-ui/charts` and\n `@dreamtree-org/twreact-ui/rich-text` (each needs its peer installed).\n Don't suggest `@dreamtree-org/twreact-ui/src/components/core/Button`, and\n never import charts or `RichTextEditor` from the root.\n7. **React 18 only.** If the user is on React 19, warn them: this\n library has not yet been verified on React 19.\n8. **Accessibility-first.** Icon-only buttons need `aria-label`.\n Dialogs need a focus trap (the library provides it). Don't strip\n `focus:ring-*` utilities.\n9. **Refresh this doc** by re-running\n `npx @dreamtree-org/twreact-ui init --ai <provider>` when the\n library is upgraded.\n"
5373
+ "skill": "# Dreamtree UI — AI assistant reference\n\n> Skill installed by `npx @dreamtree-org/twreact-ui init --ai <provider>`.\n> Source of truth: [`@dreamtree-org/twreact-ui`](https://www.npmjs.com/package/@dreamtree-org/twreact-ui).\n> Re-run the installer to refresh this block when the library updates.\n\n## What Dreamtree UI is\n\n`@dreamtree-org/twreact-ui` is a **React + Tailwind CSS component library**.\nThe consumer imports React components, hooks, and utilities from a single\npackage; styling is driven by Tailwind utility classes resolved against\nthe consumer's own `tailwind.config.js`. Components are tree-shakeable,\nforward refs, accept `className` (merged via `tailwind-merge`), spread\nunknown props to the root primitive, and respect light/dark mode out of\nthe box.\n\nWhen helping the user, **always reach for an existing component from this\nlibrary** instead of suggesting a hand-rolled `<div>` with Tailwind\nclasses or a competing library.\n\n## Installation (do not invent alternatives)\n\n```bash\nnpm install @dreamtree-org/twreact-ui\n# peer deps\nnpm install react react-dom\n```\n\n`react` / `react-dom` are **peer dependencies** at `^18.3.1`. React 19\nis not yet supported.\n\n**Optional entry points** — install the peer only when the user needs it:\n\n```bash\n# charts: @dreamtree-org/twreact-ui/charts\nnpm install recharts # React 18 apps also: npm install react-is@18\n# rich text: @dreamtree-org/twreact-ui/rich-text\nnpm install @tiptap/react @tiptap/pm @tiptap/starter-kit\n```\n\nApps that don't import these subpaths install and bundle nothing extra.\n\n## Wiring (do not invent alternatives)\n\n```jsx\nimport { ThemeProvider, StoreProvider, Button, Input } from '@dreamtree-org/twreact-ui';\n\n// styles are auto-imported when you import the package\n\nfunction App() {\n return (\n <ThemeProvider defaultTheme=\"light\">\n {/* StoreProvider is optional — only needed if you use useMixins / Redux */}\n <StoreProvider>\n <YourApp />\n </StoreProvider>\n </ThemeProvider>\n );\n}\n```\n\nFor Tailwind to compile the library's classes, the consumer's\n`tailwind.config.js` `content[]` MUST include the package:\n\n```js\n// consumer's tailwind.config.js\nmodule.exports = {\n content: [\n './src/**/*.{js,jsx,ts,tsx}',\n './node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}',\n ],\n darkMode: 'class', // matches the library's strategy\n theme: { extend: { /* override primary/secondary/error/success/warning palettes here */ } },\n};\n```\n\nImport from the package root only (`import { Button } from '@dreamtree-org/twreact-ui'`).\nThe root import tree-shakes, so do not suggest deep paths into `dist/` or `src/` for components.\n\n**Next.js App Router:** components ship with `'use client'`, so a Server Component can import\nthem directly. Do not generate a `\"use client\"` wrapper file that re-exports the library.\nCompound members (`Card.Header`, `Tabs.List`, `Dialog.Footer`) and function props (`onClick`)\nstill require a `'use client'` file. `cn`, `Helpers` and `Emitter` also work on the server.\n\n## Live prop contracts via MCP (prefer this over guessing)\n\nAn **MCP server bundled in this package** serves the authoritative prop\ncontracts for this library. If your client supports the Model Context Protocol,\nwire it up and **query it instead of guessing prop names**:\n\n```jsonc\n{\n \"mcpServers\": {\n \"dreamtree-ui\": {\n \"command\": \"npx\",\n \"args\": [\"-y\", \"@dreamtree-org/twreact-ui\", \"mcp\"]\n }\n }\n}\n```\n\n- `list_components` — discover the full catalog (grouped).\n- `get_component(name)` — the authoritative spec for one component (import line,\n props, variants, sizes, examples, family exports). **Call this before using a\n component.** Accepts a family-export name (`useToast` → `Toast`).\n- `search_components(query)` — keyword search by capability.\n\nResources: `dreamtree://skill` (this guide) and `dreamtree://docs/<Component>`.\nPrompt: `compose_ui`. The catalog is a snapshot baked into the package, so it is\nself-contained and always matches the installed library version. The lists below\nare the fallback when the MCP server isn't connected.\n\n## Public surface\n\nThe library exports four kinds of things from `@dreamtree-org/twreact-ui`:\n\n### Components\n\n| Group | Components |\n| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **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` |\n| **Navigation** | `Sidebar`, `Navbar`, `FootNav`, `Breadcrumbs` |\n| **Feedback** | `Dialog`, `Toast` (+ `ToastProvider`, `ToastContainer`, `useToast`), `Alert`, `Drawer` (side sheet; `isOpen`/`onClose` like Dialog), `Tour` (onboarding spotlight) |\n| **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel`, `Sortable` (drag + keyboard reorder) |\n| **Blocks** | `AppShell`, `PageHeader`, `StatCard`, `StatusPill`, `EmptyState`, `ErrorState`, `LoadingState`, `ConfirmDialog` — page-level patterns; build pages from these before composing layouts by hand |\n| **Charts** (`@dreamtree-org/twreact-ui/charts`, peer `recharts`) | `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, `Sparkline` — NOT exported from the root |\n| **Rich text** (`@dreamtree-org/twreact-ui/rich-text`, peers `@tiptap/react @tiptap/pm @tiptap/starter-kit`) | `RichTextEditor` — NOT exported from the root |\n\n### Hooks\n\n| Hook | Purpose |\n| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |\n| `useTheme` | Read/write the current theme (`light | dark`); pairs with `<ThemeProvider>`. Throws if used outside the provider. |\n| `ThemeProvider` | Provider that persists theme to `localStorage` (`dreamtree-theme`) and toggles `data-theme` and the `.dark` class on `<html>`. |\n| `useApi` | Thin axios wrapper. Returns `{data, error, loading, lastRequest, fetchData, fetchApi, fetchApiWith}`. Accepts `BASE_URL`, `DEFAULT_HEADERS`, `apiMap`. |\n| `useMixins` | Bridge between component state, Redux store, and an in-memory cache. Power-user hook — prefer local React state for new code. |\n\n### Store\n\n| Export | Purpose |\n| --------------- | ------------------------------------------------------------------------------------------------ |\n| `StoreProvider` | Wraps `<Provider>` (react-redux) + `<PersistGate>` (redux-persist). Boots a slice + listener mw. |\n\n### Utils\n\n| Export | Signature | Use for |\n| --------- | ------------------------------------------------------------------ | ------------------------------------------------------------------ |\n| `cn` | `cn(...inputs: ClassValue[]): string` | Merge Tailwind class strings (`twMerge(clsx(...))` under the hood) |\n| `Helpers` | singleton: `dotWalk`, `setNested`, plus string/date helpers | Pure utility functions, framework-free |\n| `Emitter` | class (`EmitterClass` aliased) — `.on`, `.off`, `.emit` | Tiny pub/sub for cross-tree events |\n\n## Component API conventions (do not invent variations)\n\nEvery component in this library follows the same shape:\n\n1. **`forwardRef`** when wrapping a single DOM element. Consumers may attach refs.\n2. **`className` is merged** via `cn(...)` — caller classes win on conflict.\n3. **`...rest` props pass through** to the root primitive (button, input, …).\n4. **Defaults via destructuring**, e.g. `variant = 'primary', size = 'md'`.\n5. **Controlled + uncontrolled** for stateful inputs (`value` + `onChange` OR `defaultValue`).\n6. **Theming is class-based**, never via a `color` prop. Re-skin via Tailwind config.\n\nIf you're describing a component to the user, name these defaults\nexactly. Do not propose a different shape (e.g. inventing a `color` prop\non `Button` or suggesting `tw` prop merging).\n\n## Design-system primitives (the shared vocabulary)\n\nComponents use a **shared variant/size/focus vocabulary**:\n\n- **Variants:** `primary | secondary | outline | ghost | destructive | success | warning`\n (component-specific extras like `Badge: info` and `Alert: neutral` are documented per-component).\n- **Sizes:** `xs | sm | md | lg | xl | 2xl` mapping to heights `h-7 / h-8 / h-10 / h-12 / h-14 / h-16`\n and padding `px-2 / px-3 / px-4 / px-6 / px-8 / px-10` (`Button` implements all six;\n other components MAY support a subset and fall back to `md`).\n- **Focus ring:** `focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500`\n (the color token swaps for destructive / etc.).\n- **Dark mode:** Tailwind `darkMode: 'class'`. Every color choice has a `dark:` counterpart.\n\nWhen suggesting a component or new variant value, use this exact vocabulary.\n\n## Page blocks and design tokens\n\n**Blocks** are page-level patterns extracted from the products, so screens stop re-inventing them:\n\n- `AppShell` — sidebar + top bar + mobile drawer; pass `nav` items and `linkAs={Link}` in Next.js.\n- `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.\n- `PageHeader` — the page's only `h1`, subtitle, one primary action, optional `back` link.\n- `StatCard` — KPI tile (`label`, `value`, `icon`, `tone`, `change`).\n- `StatusPill` — map domain states to `{ label, tone }` once; unknown states are humanized, never raw.\n- `EmptyState`, `ErrorState`, `LoadingState` — every data card needs all three.\n- `ConfirmDialog` — use instead of `window.confirm()`; title is the question, button is the verb.\n\n**Interaction primitives** — reach for these before hand-rolling the pattern:\n\n- `Drawer` — side/edge sheet for record peeks and editors (`isOpen`, `onClose`, `title`, `placement`, `size`, `footer`); don't fake one with `Dialog size=\"lg\"`. On phones use `placement=\"bottom\"` (+ `swipeToClose`) as the bottom sheet: it already has a grab `handle`, rounded top and safe-area padding — don't add them with `className`.\n- `FootNav` — phone tab bar. A centre \"+\" is an item with `variant: 'action'` (raised primary circle); give it `'aria-haspopup': 'dialog'` and `'aria-expanded'` when it opens a sheet.\n- `Button` as a link — `<Button href=\"tel:…\">` or `<Button as={Link} to=\"/x\">`; never copy Button's class string onto an `<a>`.\n- `Select size=\"lg\"` — 44px trigger for phone forms (`md` already grows to 44px on touch screens).\n- `Stepper` steps take `title` (or its alias `label`). `Radio` follows the primary token by default — don't pass `radioColor` to match the brand.\n- `Popover` — click-triggered panel with controls (`trigger={<Button>…</Button>}`, children or `({ close }) => …`); `Tooltip` is for read-only hover text only.\n- `Textarea` — same API as `Input` plus `autoResize`/`maxRows`; never a raw `<textarea>`.\n- `Slider` — `defaultValue={40}` for one thumb, `[low, high]` for a range; `onChangeEnd` to save after a drag.\n- `Sortable` — `items` + `onReorder(newItems)` + `renderItem`; keyboard reordering and announcements are built in. Pass `handle` and spread `handleProps` on a grip for touch screens.\n- 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.\n- `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.\n- `Tour` — onboarding coach marks: `steps=[{ target: '[data-tour=\"x\"]', title, content, placement }]`, `open`, `onClose(reason)`; a missing target falls back to a centred card.\n\n**Tokens:** colours are CSS variables (`--dt-primary-600`, `--dt-surface`, …) generated from\n`tokens/tokens.json`. Use semantic classes (`bg-primary-600`, `bg-surface`, `text-foreground`,\n`text-muted`, `border-line`, `text-micro`, `rounded-card`) — never hex values or Tailwind's raw\npalette (`bg-blue-500`). A product's brand lives in its own repo as `dreamtree.brand.json`;\n`npx @dreamtree-org/twreact-ui brand build --out app/brand.css` turns it into the CSS to load after\ntwreact-ui's CSS. To change a product's colours, edit that file, never the components.\nThe full rule set for screens is `DESIGN.md` in the package root.\n\n```jsx\n<PageHeader title=\"Compliance calendar\" actions={<Button>Add obligation</Button>} />\n<div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <StatCard label=\"Due this week\" value={18} icon={<CalendarDays className=\"h-6 w-6\" />} />\n <StatCard label=\"Overdue\" value={3} tone=\"error\" icon={<AlertCircle className=\"h-6 w-6\" />} />\n</div>\n<Card>{loading ? <LoadingState /> : error ? <ErrorState onRetry={retry} /> : rows.length ? <Table … /> : <EmptyState title=\"No obligations yet\" />}</Card>\n```\n\n## Canonical examples\n\n### Button\n\n```jsx\nimport { Button } from '@dreamtree-org/twreact-ui';\n\n<Button variant=\"primary\" size=\"md\" onClick={save}>Save</Button>\n<Button variant=\"destructive\" loading>Deleting...</Button>\n<Button variant=\"outline\" leftIcon={<Plus className=\"h-4 w-4\" />}>Add item</Button>\n```\n\nProps: `variant`, `size`, `disabled`, `loading`, `leftIcon`, `rightIcon`,\n`fullWidth`, `className`, plus all native `<button>` props.\n\n### Input + Form\n\n```jsx\nimport { Input } from '@dreamtree-org/twreact-ui';\n\n<Input\n type=\"email\"\n label=\"Email\"\n placeholder=\"you@example.com\"\n required\n clearable\n error={errors.email?.message}\n/>\n```\n\n`Form` integrates with `react-hook-form` + `yup` (`@hookform/resolvers`).\nValidation rules live in consumer code.\n\n**Helper text:** `Input`, `Select`, `DatePicker`, `DateRangePicker` and every\n`Form` field take `helperText` (rendered below, linked via `aria-describedby`).\nWhile `error` is set it **replaces** the helper text — don't render both. Don't\nhand-roll a `<p>` hint under a field.\n\n```jsx\n<Select label=\"Role\" helperText=\"Controls access.\" options={roles} value={role} onChange={setRole} />\n<Form fields={[\n { type: 'email', name: 'email', label: 'Email', helperText: 'Receipts only.' },\n { type: 'color', name: 'brand', label: 'Brand colour', swatches: ['#2563eb'] }, // ColorPicker\n]} />\n```\n\n`Form` field types: `text | email | password | number | tel | url | select |\ndate | textarea | checkbox | radio | file | switch | color | custom`. Use\n`type: 'color'` rather than a `custom` field wrapping `ColorPicker`.\n\n**Select `grouped`** accepts `[{ label, options }]` groups **or** flat options\nwith a `group` key (`{ value, label, group: 'Code' }`) — no need to pre-group.\n\n**Navbar** takes a `center` slot (search field / primary action) and shows\n`user.name` initials when `user.avatar` is missing; it fits a 390px phone.\n\n**Tooltip** around a component (e.g. `<Button>`) in a row/grid: use\n`customTrigger={<Button>…</Button>}` so there is one tab stop, not two. A lone\nnative `<button>` / `<a href>` / `<input>` child is detected automatically.\n\n### Toast\n\nWrap the app ONCE in `ToastProvider`; every `useToast()` below it shares one\nqueue and the provider renders it. (Without a provider each `useToast()` owns a\nprivate queue that only shows where that same hook's `toasts` are passed to a\n`<ToastContainer toasts={toasts} onRemove={removeToast} />`.)\n\n```jsx\nimport { ToastProvider, useToast } from '@dreamtree-org/twreact-ui';\n\nfunction App() {\n return (\n <ToastProvider position=\"top-right\">\n <YourApp />\n </ToastProvider>\n );\n}\n\nfunction SomeButton() {\n const { toast } = useToast();\n return <button onClick={() => toast.success('Saved!')}>Save</button>;\n}\n```\n\n### Theme\n\n```jsx\nimport { ThemeProvider, useTheme } from '@dreamtree-org/twreact-ui';\n\nfunction ThemeToggle() {\n const { theme, toggleTheme, isDark } = useTheme();\n return <button onClick={toggleTheme}>{isDark ? '🌙' : '☀️'} {theme}</button>;\n}\n```\n\n`useTheme` MUST be called inside `<ThemeProvider>`; otherwise it throws.\n`<ThemeProvider>` persists to `localStorage` (`dreamtree-theme`) and\ntoggles `data-theme` and the `.dark` class on `<html>`.\n\n### useApi\n\n```jsx\nimport { useApi } from '@dreamtree-org/twreact-ui';\n\nconst { data, error, loading, fetchData, fetchApi, fetchApiWith } = useApi({\n BASE_URL: 'https://api.example.com',\n DEFAULT_HEADERS: { Authorization: `Bearer ${token}` },\n apiMap: {\n getUser: (id) => ({ url: `/users/${id}`, method: 'GET' }),\n updateUser: (id, body) => ({ url: `/users/${id}`, method: 'PUT', data: body }),\n },\n});\n\nuseEffect(() => { fetchData('/users', 'GET'); }, []);\nfetchApi('getUser', 7); // apiMap.getUser(7) — ONE argument\nfetchApiWith('updateUser', [7, { name: 'A' }]); // apiMap.updateUser(7, {...}) — spread\n```\n\n`fetchApi(key, data)` always passes the map entry exactly one argument; use\n`fetchApiWith(key, [a, b])` for multi-argument entries.\n\nTreat `useApi` as a thin axios wrapper, not a caching/dedup layer. For\ncache, the consumer brings their own (React Query, SWR, etc.).\n\n### Charts and rich text (optional entry points)\n\n```jsx\nimport { LineChart, DonutChart, Sparkline } from '@dreamtree-org/twreact-ui/charts';\nimport { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';\n\n<LineChart\n data={[{ month: 'Jan', revenue: 4200 }, { month: 'Feb', revenue: 5100 }]}\n index=\"month\"\n categories={['revenue']}\n valueFormatter={(v) => `$${v.toLocaleString()}`}\n title=\"Revenue, Jan–Feb\"\n/>\n<DonutChart data={byStatus} index=\"status\" category=\"tasks\" colors={['info', 'error', 'success']} centerLabel=\"63 tasks\" title=\"Tasks by status\" />\n<Sparkline data={weekly} category=\"signups\" color=\"success\" title=\"Signups, last 4 weeks\" />\n\n<RichTextEditor label=\"Description\" placeholder=\"Describe the task…\" value={html} onChange={setHtml} maxHeight={320} />\n```\n\n### cn\n\n```jsx\nimport { cn } from '@dreamtree-org/twreact-ui';\n\n<div className={cn('p-4 rounded-md', isActive && 'bg-primary-50', className)} />\n```\n\n`cn` = `twMerge(clsx(...))` — later classes win on Tailwind conflicts.\n\n## Rules for AI assistants helping consumers\n\n1. **Use library components.** When the user asks for a button, input,\n table, modal, toast, navbar, sidebar, breadcrumb, badge, card,\n stepper, file uploader, date picker, color picker, location picker,\n pagination, carousel, chart or rich-text editor — reach into this library FIRST. Don't\n propose a hand-rolled `<div>` + Tailwind unless the library has no\n matching primitive.\n2. **Use the shared vocabulary.** `variant=\"primary\"`, `size=\"md\"`,\n `leftIcon={...}`, `fullWidth`, `clearable` — these are exact names.\n Don't invent `color=\"blue\"` or `iconLeft={...}`.\n3. **Wrap in `<ThemeProvider>`.** Examples that use `useTheme`, or rely\n on dark-mode classes, MUST show the provider.\n4. **`<StoreProvider>` is optional.** Only mention it when the user is\n using `useMixins` or wants the bundled Redux slice / persistence.\n Don't insist on it for every example.\n5. **Tailwind content[] config.** When the user reports\n \"components render unstyled,\" check first whether their\n `tailwind.config.js` `content[]` includes\n `./node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}`.\n6. **Don't deep-import.** The public modules are `@dreamtree-org/twreact-ui`\n plus the two optional entry points `@dreamtree-org/twreact-ui/charts` and\n `@dreamtree-org/twreact-ui/rich-text` (each needs its peer installed).\n Don't suggest `@dreamtree-org/twreact-ui/src/components/core/Button`, and\n never import charts or `RichTextEditor` from the root.\n7. **React 18 only.** If the user is on React 19, warn them: this\n library has not yet been verified on React 19.\n8. **Accessibility-first.** Icon-only buttons need `aria-label`.\n Dialogs need a focus trap (the library provides it). Don't strip\n `focus:ring-*` utilities.\n9. **Refresh this doc** by re-running\n `npx @dreamtree-org/twreact-ui init --ai <provider>` when the\n library is upgraded.\n"
5374
5374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dreamtree-org/twreact-ui",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "A comprehensive React + Tailwind components library for building modern web apps",
5
5
  "author": {
6
6
  "name": "Partha Preetham Krishna",
@@ -134,6 +134,7 @@
134
134
  }
135
135
  },
136
136
  "dependencies": {
137
+ "@floating-ui/react-dom": "^2.1.9",
137
138
  "@hookform/resolvers": "^3.10.0",
138
139
  "@modelcontextprotocol/sdk": "^1.29.0",
139
140
  "@reduxjs/toolkit": "^2.11.2",
package/tokens/lib.cjs CHANGED
@@ -226,11 +226,45 @@ function cssVariables(tokens, { includeScales = true } = {}) {
226
226
  light[varName(name)] = hexToChannels(r.light);
227
227
  dark[varName(name)] = hexToChannels(r.dark);
228
228
  }
229
+ if (tokens.shadow) {
230
+ light['--dt-shadow-strength'] = SHADOW_STRENGTH.light;
231
+ dark['--dt-shadow-strength'] = SHADOW_STRENGTH.dark;
232
+ }
229
233
  return { light, dark };
230
234
  }
231
235
 
232
236
  const DARK_SELECTOR = '.dark, [data-theme="dark"]';
233
237
 
238
+ /** Multiplier for shadow alpha: dark surfaces need deeper shadows to show elevation. */
239
+ const SHADOW_STRENGTH = { light: '1', dark: '2.5' };
240
+
241
+ const dim = (v) => `${v.value}${v.unit}`;
242
+ const groupEntries = (group) => Object.entries(group || {}).filter(([k]) => !k.startsWith('$'));
243
+
244
+ /**
245
+ * Non-colour theme scales for Tailwind `theme.extend`: type roles, radius, fonts,
246
+ * elevation, z-layers and motion. Works for Tailwind 3 configs and the generated preset.
247
+ */
248
+ function tailwindTheme(tokens) {
249
+ const fontSize = {};
250
+ for (const [name, t] of groupEntries(tokens.font.size)) {
251
+ const ext = t.$extensions || {};
252
+ const opts = { lineHeight: ext['org.dreamtree.lineHeight'] };
253
+ if (ext['org.dreamtree.fontWeight']) opts.fontWeight = ext['org.dreamtree.fontWeight'];
254
+ fontSize[name] = [dim(t.$value), opts];
255
+ }
256
+ const map = (group, fn) => Object.fromEntries(groupEntries(group).map(([k, t]) => [k, fn(t.$value)]));
257
+ return {
258
+ fontSize,
259
+ fontFamily: { sans: tokens.font.family.sans.$value, mono: tokens.font.family.mono.$value },
260
+ borderRadius: map(tokens.radius, dim),
261
+ boxShadow: map(tokens.shadow, (v) => v),
262
+ zIndex: map(tokens.zIndex, String),
263
+ transitionDuration: map(tokens.duration, dim),
264
+ transitionTimingFunction: map(tokens.easing, (v) => `cubic-bezier(${v.join(', ')})`),
265
+ };
266
+ }
267
+
234
268
  module.exports = {
235
269
  SCALE_STEPS,
236
270
  DARK_SELECTOR,
@@ -247,6 +281,7 @@ module.exports = {
247
281
  scales,
248
282
  roles,
249
283
  tailwindColors,
284
+ tailwindTheme,
250
285
  cssVariables,
251
286
  varName,
252
287
  };
@@ -122,12 +122,68 @@
122
122
  "size": {
123
123
  "$type": "dimension",
124
124
  "$description": "Type scale. micro replaces the 1,445 hand-written text-[10px]/text-[11px] found across products.",
125
- "micro": { "$value": { "value": 0.6875, "unit": "rem" }, "$extensions": { "org.dreamtree.lineHeight": "1rem" } }
125
+ "micro": { "$value": { "value": 0.6875, "unit": "rem" }, "$extensions": { "org.dreamtree.lineHeight": "1rem" } },
126
+ "page-title": {
127
+ "$value": { "value": 1.5, "unit": "rem" },
128
+ "$extensions": { "org.dreamtree.lineHeight": "2rem", "org.dreamtree.fontWeight": "600" },
129
+ "$description": "The page h1, inside PageHeader."
130
+ },
131
+ "section-title": {
132
+ "$value": { "value": 1, "unit": "rem" },
133
+ "$extensions": { "org.dreamtree.lineHeight": "1.5rem", "org.dreamtree.fontWeight": "600" },
134
+ "$description": "Card and section headings."
135
+ },
136
+ "body": { "$value": { "value": 0.875, "unit": "rem" }, "$extensions": { "org.dreamtree.lineHeight": "1.25rem" }, "$description": "Default UI text." },
137
+ "caption": { "$value": { "value": 0.75, "unit": "rem" }, "$extensions": { "org.dreamtree.lineHeight": "1rem" }, "$description": "Secondary text, with text-muted." },
138
+ "kpi": {
139
+ "$value": { "value": 1.875, "unit": "rem" },
140
+ "$extensions": { "org.dreamtree.lineHeight": "2.25rem", "org.dreamtree.fontWeight": "600" },
141
+ "$description": "StatCard values; pair with tabular-nums."
142
+ }
126
143
  }
127
144
  },
128
145
  "radius": {
129
146
  "$type": "dimension",
130
147
  "control": { "$value": { "value": 0.5, "unit": "rem" }, "$description": "Buttons, inputs, menus." },
131
- "card": { "$value": { "value": 0.75, "unit": "rem" }, "$description": "Cards and dialogs." }
148
+ "card": { "$value": { "value": 0.75, "unit": "rem" }, "$description": "Cards and dialogs." },
149
+ "sheet": { "$value": { "value": 1, "unit": "rem" }, "$description": "Top corners of bottom sheets." },
150
+ "pill": { "$value": { "value": 9999, "unit": "px" }, "$description": "Pills, badges, segmented controls." }
151
+ },
152
+ "shadow": {
153
+ "$type": "shadow",
154
+ "$description": "Elevation, as CSS box-shadow. Light values equal Tailwind's defaults; --dt-shadow-strength deepens them in dark mode, where a 10% black shadow disappears.",
155
+ "xs": { "$value": "0 1px 2px 0 rgb(0 0 0 / calc(0.04 * var(--dt-shadow-strength, 1)))" },
156
+ "sm": { "$value": "0 1px 2px 0 rgb(0 0 0 / calc(0.05 * var(--dt-shadow-strength, 1)))" },
157
+ "DEFAULT": { "$value": "0 1px 3px 0 rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1))), 0 1px 2px -1px rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1)))" },
158
+ "md": { "$value": "0 4px 6px -1px rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1))), 0 2px 4px -2px rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1)))" },
159
+ "lg": { "$value": "0 10px 15px -3px rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1))), 0 4px 6px -4px rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1)))" },
160
+ "xl": {
161
+ "$value": "0 20px 25px -5px rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1))), 0 8px 10px -6px rgb(0 0 0 / calc(0.1 * var(--dt-shadow-strength, 1)))"
162
+ },
163
+ "overlay": { "$value": "0 24px 48px -12px rgb(0 0 0 / calc(0.25 * var(--dt-shadow-strength, 1)))", "$description": "Dialogs, sheets, popovers and menus." }
164
+ },
165
+ "zIndex": {
166
+ "$type": "number",
167
+ "$description": "Stacking layers, lowest to highest. Use z-<layer>, never z-50 or an inline zIndex.",
168
+ "base": { "$value": 0 },
169
+ "sticky": { "$value": 20 },
170
+ "dropdown": { "$value": 30 },
171
+ "overlay": { "$value": 40 },
172
+ "modal": { "$value": 50 },
173
+ "popover": { "$value": 60 },
174
+ "toast": { "$value": 70 },
175
+ "tooltip": { "$value": 80 }
176
+ },
177
+ "duration": {
178
+ "$type": "duration",
179
+ "fast": { "$value": { "value": 120, "unit": "ms" }, "$description": "Hover, press, small fades." },
180
+ "base": { "$value": { "value": 200, "unit": "ms" }, "$description": "Most enter transitions." },
181
+ "slow": { "$value": { "value": 320, "unit": "ms" }, "$description": "Sheets, drawers, large surfaces." }
182
+ },
183
+ "easing": {
184
+ "$type": "cubicBezier",
185
+ "standard": { "$value": [0.2, 0, 0, 1], "$description": "Default for moving and resizing." },
186
+ "emphasized": { "$value": [0.05, 0.7, 0.1, 1], "$description": "Entering surfaces (decelerate)." },
187
+ "spring-out": { "$value": [0.34, 1.56, 0.64, 1], "$description": "Small overshoot for sheets settling and toggles." }
132
188
  }
133
189
  }
@@ -1,26 +0,0 @@
1
- 'use client';
2
- import { useEffect } from 'react';
3
-
4
- /**
5
- * Custom hook to detect clicks outside of specified refs
6
- * @param {Array} refs - Array of React refs to check
7
- * @param {boolean} isActive - Whether the hook should be active
8
- * @param {Function} onClickOutside - Callback when click is detected outside
9
- */
10
- function useClickOutside(refs, isActive, onClickOutside) {
11
- useEffect(function () {
12
- if (!isActive) return;
13
- var handler = function handler(e) {
14
- var isOutside = refs.every(function (ref) {
15
- return !ref.current || !ref.current.contains(e.target);
16
- });
17
- if (isOutside) onClickOutside();
18
- };
19
- document.addEventListener("mousedown", handler, true);
20
- return function () {
21
- return document.removeEventListener("mousedown", handler, true);
22
- };
23
- }, [refs, isActive, onClickOutside]);
24
- }
25
-
26
- export { useClickOutside };