@dreamtree-org/twreact-ui 1.3.1 → 1.3.3

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 (75) hide show
  1. package/DESIGN.md +2 -2
  2. package/README.md +90 -5
  3. package/ai-skills/dreamtree-ui.md +43 -4
  4. package/bin/cli.mjs +72 -4
  5. package/charts/package.json +5 -0
  6. package/dist/_shared/cn-DPPQUJu1.js +138 -0
  7. package/dist/ai-schema.json +639 -1
  8. package/dist/charts/index.d.ts +9 -0
  9. package/dist/charts/index.d.ts.map +1 -0
  10. package/dist/charts/index.js +797 -0
  11. package/dist/components/charts/AreaChart.d.ts +4 -0
  12. package/dist/components/charts/AreaChart.d.ts.map +1 -0
  13. package/dist/components/charts/BarChart.d.ts +4 -0
  14. package/dist/components/charts/BarChart.d.ts.map +1 -0
  15. package/dist/components/charts/CartesianChart.d.ts +5 -0
  16. package/dist/components/charts/CartesianChart.d.ts.map +1 -0
  17. package/dist/components/charts/ChartFrame.d.ts +18 -0
  18. package/dist/components/charts/ChartFrame.d.ts.map +1 -0
  19. package/dist/components/charts/DonutChart.d.ts +4 -0
  20. package/dist/components/charts/DonutChart.d.ts.map +1 -0
  21. package/dist/components/charts/LineChart.d.ts +4 -0
  22. package/dist/components/charts/LineChart.d.ts.map +1 -0
  23. package/dist/components/charts/Sparkline.d.ts +5 -0
  24. package/dist/components/charts/Sparkline.d.ts.map +1 -0
  25. package/dist/components/charts/chartTheme.d.ts +36 -0
  26. package/dist/components/charts/chartTheme.d.ts.map +1 -0
  27. package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
  28. package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
  29. package/dist/components/core/Table/Table.d.ts.map +1 -1
  30. package/dist/components/rich-text/RichTextEditor.d.ts +4 -0
  31. package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -0
  32. package/dist/components/rich-text/RichTextToolbar.d.ts +219 -0
  33. package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -0
  34. package/dist/components/utility/Stepper.d.ts.map +1 -1
  35. package/dist/esm/charts/index.js +5 -0
  36. package/dist/esm/components/blocks/StatCard.js +1 -1
  37. package/dist/esm/components/charts/AreaChart.js +16 -0
  38. package/dist/esm/components/charts/BarChart.js +16 -0
  39. package/dist/esm/components/charts/CartesianChart.js +255 -0
  40. package/dist/esm/components/charts/ChartFrame.js +170 -0
  41. package/dist/esm/components/charts/DonutChart.js +122 -0
  42. package/dist/esm/components/charts/LineChart.js +16 -0
  43. package/dist/esm/components/charts/Sparkline.js +113 -0
  44. package/dist/esm/components/charts/chartTheme.js +126 -0
  45. package/dist/esm/components/core/Select.js +2 -2
  46. package/dist/esm/components/core/Table/MobileCard.js +5 -2
  47. package/dist/esm/components/core/Table/MobileFilters.js +10 -3
  48. package/dist/esm/components/core/Table/Table.js +10 -7
  49. package/dist/esm/components/rich-text/RichTextEditor.js +244 -0
  50. package/dist/esm/components/rich-text/RichTextToolbar.js +397 -0
  51. package/dist/esm/components/utility/Stepper.js +6 -4
  52. package/dist/esm/rich-text/index.js +1 -0
  53. package/dist/index.css +1 -1
  54. package/dist/index.esm.css +1 -1
  55. package/dist/index.esm.js +50 -22
  56. package/dist/index.js +50 -22
  57. package/dist/rich-text/index.d.ts +5 -0
  58. package/dist/rich-text/index.d.ts.map +1 -0
  59. package/dist/rich-text/index.js +654 -0
  60. package/dist/tokens/brands/practicepro.json +1 -0
  61. package/dist/tokens/brands/studio.css +14 -0
  62. package/dist/tokens/brands/studio.json +21 -0
  63. package/dist/types/charts.d.ts +97 -0
  64. package/dist/types/charts.d.ts.map +1 -0
  65. package/dist/types/rich-text.d.ts +50 -0
  66. package/dist/types/rich-text.d.ts.map +1 -0
  67. package/doc/README.md +13 -0
  68. package/mcp/catalog.snapshot.json +1016 -10
  69. package/mcp/server-core.mjs +30 -1
  70. package/package.json +39 -2
  71. package/rich-text/package.json +5 -0
  72. package/tokens/brand.schema.json +612 -0
  73. package/tokens/brands/practicepro.json +1 -0
  74. package/tokens/brands/studio.json +21 -0
  75. package/tokens/lib.cjs +131 -7
package/DESIGN.md CHANGED
@@ -41,7 +41,7 @@ rounded:
41
41
  spacing:
42
42
  grid: 4px
43
43
  steps: [4, 8, 12, 16, 20, 24, 32]
44
- brands: [default, practicepro]
44
+ brand: dreamtree.brand.json in each product repo (build its CSS with `npx @dreamtree-org/twreact-ui brand build`)
45
45
  ---
46
46
 
47
47
  <!-- Generated by twreact-ui scripts/build-tokens.mjs from tokens/tokens.json. Edit the tokens or this generator, not this file. -->
@@ -76,7 +76,7 @@ Every Dreamtree product screen, and every prototype generated by Dreamtree Studi
76
76
 
77
77
  - **One accent per screen.** `primary` marks the primary action, the active nav item, the current step and progress. Red (`error`) is only for errors, overdue items and destructive actions.
78
78
  - Badge and status fills use the 100 step with 800 text, which keeps contrast at or above 4.5:1.
79
- - Products change their look with a **brand file** (`tokens/brands/<product>.json`), which overrides `--dt-*` variables. Brands available: `default`, `practicepro`.
79
+ - Each product keeps its look in its own repo, in **`dreamtree.brand.json`** (its `--dt-*` overrides: colour scales, roles, font). `npx @dreamtree-org/twreact-ui brand build` turns it into the CSS the product loads after twreact-ui's CSS. Never restyle a product with raw colours; change its brand file.
80
80
 
81
81
  ## 3. Type, space and shape
82
82
 
package/README.md CHANGED
@@ -79,7 +79,8 @@ bundle. `import { Button } from '@dreamtree-org/twreact-ui'` costs about 8 KB gz
79
79
  100 KB. `require()`, Jest and plain Node keep getting the CommonJS build (`dist/index.js`).
80
80
 
81
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/*`,
82
+ already tree-shakes. The only subpaths are the two **optional entry points** below, which exist
83
+ because they need an extra dependency. Existing file paths (`dist/index.css`, `dist/tokens/*`, `tokens/*`,
83
84
  `dist/index.esm.js`, `package.json`, …) still resolve through the `exports` map.
84
85
 
85
86
  ### Next.js App Router
@@ -103,6 +104,27 @@ Server Component to these Client Components. You also cannot dot into a client e
103
104
  In Vite, Rollup can log `Module level directives cause errors when bundled, "use client" … was ignored`.
104
105
  This message is harmless. To hide it, filter `MODULE_LEVEL_DIRECTIVE` in `build.rollupOptions.onwarn`.
105
106
 
107
+ ### Optional entry points: charts and rich text
108
+
109
+ Charts and the rich-text editor wrap third-party libraries, so they live on their own entry points
110
+ and use **optional peer dependencies**. If you never import them you install nothing extra, and
111
+ nothing changes in your bundle — the root `@dreamtree-org/twreact-ui` import never touches them.
112
+
113
+ | Entry point | Exports | Install first |
114
+ |---|---|---|
115
+ | `@dreamtree-org/twreact-ui/charts` | `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, `Sparkline` | `npm install recharts` (recharts 2.15+ or 3.x; React 18 apps also `npm install react-is@18`) |
116
+ | `@dreamtree-org/twreact-ui/rich-text` | `RichTextEditor` | `npm install @tiptap/react @tiptap/pm @tiptap/starter-kit` (Tiptap 3) |
117
+
118
+ ```jsx
119
+ import { LineChart, DonutChart } from '@dreamtree-org/twreact-ui/charts';
120
+ import { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';
121
+ ```
122
+
123
+ Both ship ESM (with `'use client'`), CommonJS and TypeScript declarations, and resolve under every
124
+ TypeScript `moduleResolution` (`bundler`, `node16`/`nodenext` and legacy `node`). Their styles are
125
+ already in `dist/index.css`. Rich-text output is HTML from the user — **sanitize it on the server**
126
+ before you store or render it (see [doc/RichTextEditor.md](doc/RichTextEditor.md#security-sanitize-the-html-on-the-server)).
127
+
106
128
  ## Quick Start
107
129
 
108
130
  ```jsx
@@ -256,7 +278,7 @@ Advanced select component with search, multi-select, and grouping support.
256
278
  - `selectAllOption`: boolean
257
279
  - `closeOnSelect`: boolean
258
280
  - `maxTagCount`: number (default: 3)
259
- - `allowClear`: boolean
281
+ - `allowClear`: boolean (default: false; set it when the field may be emptied)
260
282
 
261
283
  ```jsx
262
284
  <Select
@@ -709,6 +731,61 @@ import { Drawer, Popover, Textarea, Slider, Sortable, Tour, Button } from '@drea
709
731
  <Tour open={tourOpen} steps={[{ title: 'Welcome' }, { target: '[data-tour="new-task"]', title: 'Capture work' }]} onClose={() => setTourOpen(false)} />
710
732
  ```
711
733
 
734
+ ### Charts (optional entry point)
735
+
736
+ Import from `@dreamtree-org/twreact-ui/charts` after `npm install recharts`. A data-first API over
737
+ recharts: pass rows as `data`, the x-axis key as `index` and the series keys as `categories`.
738
+ Colours are design tokens (`'primary'`, `'success-600'`, …) resolved from the `--dt-*` variables,
739
+ so charts follow your theme and dark mode. Every chart is responsive, `forwardRef` + `className` +
740
+ `...rest`, a named `role="img"` (`title` / `description`) with a visually-hidden data table for
741
+ screen readers (`accessibleTable`), shows `emptyState` for empty data, and disables animation under
742
+ `prefers-reduced-motion`.
743
+
744
+ | Component | Use for |
745
+ |---|---|
746
+ | [`LineChart`](doc/LineChart.md) | Trends over time (`curveType`, `showDots`, `connectNulls`) |
747
+ | [`BarChart`](doc/BarChart.md) | Comparisons — grouped, `stacked` or `horizontal` |
748
+ | [`AreaChart`](doc/AreaChart.md) | Volume over time, optionally `stacked` |
749
+ | [`DonutChart`](doc/DonutChart.md) | Part-to-whole (`variant="donut"` or `"pie"`, `centerLabel`) |
750
+ | [`Sparkline`](doc/Sparkline.md) | Word-sized trend for stat cards and table cells |
751
+
752
+ Shared props: `valueFormatter`, `height`, `showLegend`, `showTooltip`, `showXAxis`, `showYAxis`,
753
+ `showGrid`, `colors`, `animate`.
754
+
755
+ ```jsx
756
+ import { LineChart, DonutChart, Sparkline } from '@dreamtree-org/twreact-ui/charts';
757
+
758
+ <LineChart
759
+ data={[{ month: 'Jan', revenue: 4200 }, { month: 'Feb', revenue: 5100 }]}
760
+ index="month"
761
+ categories={['revenue']}
762
+ valueFormatter={(v) => `$${v.toLocaleString()}`}
763
+ title="Revenue, Jan–Feb"
764
+ />
765
+ <DonutChart data={byStatus} index="status" category="tasks" centerLabel="63 tasks" title="Tasks by status" />
766
+ <Sparkline data={weekly} category="signups" color="success" title="Signups, last 4 weeks" />
767
+ ```
768
+
769
+ ### Rich text (optional entry point)
770
+
771
+ [`RichTextEditor`](doc/RichTextEditor.md) — import from `@dreamtree-org/twreact-ui/rich-text` after
772
+ `npm install @tiptap/react @tiptap/pm @tiptap/starter-kit`. A Tiptap (StarterKit) field with the
773
+ same `label` / `error` / `helperText` / `required` wiring as `Input`; controlled (`value` +
774
+ `onChange(html)`) or uncontrolled (`defaultValue`), `outputFormat="json"` for Tiptap JSON,
775
+ `disabled` / `readOnly`, `minHeight` / `maxHeight`, and a `toolbar` (bold, italic, strike, code,
776
+ headings, lists, quote, link, undo/redo) that is a `role="toolbar"` of real buttons with
777
+ `aria-pressed` and arrow-key roving focus. The `ref` is an imperative handle (`editor`, `focus()`,
778
+ `getHTML()`, `getJSON()`, `setContent()`, `clear()`). SSR-safe: the editor mounts on the client.
779
+
780
+ ```jsx
781
+ import { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';
782
+
783
+ <RichTextEditor label="Description" placeholder="Describe the task…" value={html} onChange={setHtml} maxHeight={320} />
784
+ ```
785
+
786
+ > The HTML comes from the user. Sanitize it on the server (e.g. DOMPurify / `sanitize-html`) before
787
+ > storing or rendering it; this library ships no sanitizer.
788
+
712
789
  ### Page blocks
713
790
 
714
791
  Page-level patterns extracted from the Dreamtree products. Build pages from these before composing layouts by hand.
@@ -759,13 +836,21 @@ See [doc/useApi.md](doc/useApi.md).
759
836
 
760
837
  Every colour in the compiled CSS is a CSS variable (`--dt-primary-600`, `--dt-surface`, …) generated from `tokens/tokens.json` (W3C Design Tokens format). The defaults equal the classic palette, so nothing changes until you override a variable.
761
838
 
762
- **Re-theme with a brand file** (no Tailwind config needed, works with the precompiled CSS):
839
+ **Your brand lives in your repo.** Keep it in `dreamtree.brand.json` at the repo root (colour scales, roles, font, logo, and optional landing-page colours) and build the CSS from it. No Tailwind config is needed, and it works with the precompiled CSS:
840
+
841
+ ```bash
842
+ npx @dreamtree-org/twreact-ui brand init --id acme --name "Acme" # starter file
843
+ npx @dreamtree-org/twreact-ui brand build --out app/brand.css # only the --dt-* variables you override
844
+ npx @dreamtree-org/twreact-ui brand build --out app/brand.css --check # in CI: fails if the CSS is stale
845
+ ```
763
846
 
764
847
  ```css
765
848
  @import "@dreamtree-org/twreact-ui/dist/index.css";
766
- @import "@dreamtree-org/twreact-ui/dist/tokens/brands/practicepro.css"; /* or your own --dt-* overrides */
849
+ @import "./brand.css";
767
850
  ```
768
851
 
852
+ The file is checked against `tokens/brand.schema.json` (add `"$schema": "https://unpkg.com/@dreamtree-org/twreact-ui/tokens/brand.schema.json"` for editor help). `brand validate` lists every problem with its fix, and `--scope ".my-app"` limits the brand to part of a page. twreact-ui ships only `default`; the `practicepro` and `studio` brands are deprecated copies until 1.5.0.
853
+
769
854
  **Use the same tokens in your own classes:**
770
855
 
771
856
  ```js
@@ -793,7 +878,7 @@ This adds semantic utilities next to the palettes: `bg-surface`, `bg-background`
793
878
 
794
879
  **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
880
 
796
- **SSR:** `Tabs`, `Tooltip` and `Switch` generate ids with `React.useId()`, so server-rendered pages (Next.js) hydrate without mismatches.
881
+ **SSR:** `Tabs`, `Tooltip` and `Switch` generate ids with `React.useId()`, so server-rendered pages (Next.js) hydrate without mismatches. The `/charts` components do the same for their SVG ids, and they render token colours and reduced-motion state the way the server did until hydration finishes.
797
882
 
798
883
  ## Dark Mode
799
884
 
@@ -29,6 +29,17 @@ npm install react react-dom
29
29
  `react` / `react-dom` are **peer dependencies** at `^18.3.1`. React 19
30
30
  is not yet supported.
31
31
 
32
+ **Optional entry points** — install the peer only when the user needs it:
33
+
34
+ ```bash
35
+ # charts: @dreamtree-org/twreact-ui/charts
36
+ npm install recharts # React 18 apps also: npm install react-is@18
37
+ # rich text: @dreamtree-org/twreact-ui/rich-text
38
+ npm install @tiptap/react @tiptap/pm @tiptap/starter-kit
39
+ ```
40
+
41
+ Apps that don't import these subpaths install and bundle nothing extra.
42
+
32
43
  ## Wiring (do not invent alternatives)
33
44
 
34
45
  ```jsx
@@ -112,6 +123,8 @@ The library exports four kinds of things from `@dreamtree-org/twreact-ui`:
112
123
  | **Feedback** | `Dialog`, `Toast` (+ `ToastProvider`, `ToastContainer`, `useToast`), `Alert`, `Drawer` (side sheet; `isOpen`/`onClose` like Dialog), `Tour` (onboarding spotlight) |
113
124
  | **Utility** | `Badge`, `Avatar`, `Card`, `Pagination`, `Stepper`, `FileUpload`, `Condition`, `Carousel`, `Sortable` (drag + keyboard reorder) |
114
125
  | **Blocks** | `AppShell`, `PageHeader`, `StatCard`, `StatusPill`, `EmptyState`, `ErrorState`, `LoadingState`, `ConfirmDialog` — page-level patterns; build pages from these before composing layouts by hand |
126
+ | **Charts** (`@dreamtree-org/twreact-ui/charts`, peer `recharts`) | `LineChart`, `BarChart`, `AreaChart`, `DonutChart`, `Sparkline` — NOT exported from the root |
127
+ | **Rich text** (`@dreamtree-org/twreact-ui/rich-text`, peers `@tiptap/react @tiptap/pm @tiptap/starter-kit`) | `RichTextEditor` — NOT exported from the root |
115
128
 
116
129
  ### Hooks
117
130
 
@@ -185,12 +198,16 @@ When suggesting a component or new variant value, use this exact vocabulary.
185
198
  - `Textarea` — same API as `Input` plus `autoResize`/`maxRows`; never a raw `<textarea>`.
186
199
  - `Slider` — `defaultValue={40}` for one thumb, `[low, high]` for a range; `onChangeEnd` to save after a drag.
187
200
  - `Sortable` — `items` + `onReorder(newItems)` + `renderItem`; keyboard reordering and announcements are built in. Pass `handle` and spread `handleProps` on a grip for touch screens.
201
+ - Charts (`/charts`) — `<LineChart data={rows} index="month" categories={['revenue', 'costs']} />`; same shape for `BarChart` (`stacked`, `horizontal`) and `AreaChart` (`stacked`); `DonutChart` takes `index` (name key) + `category` (value key) + `centerLabel`; `Sparkline` takes `category` + `color`. Colours are tokens (`colors={['primary', 'success-600']}`), never hex. Always pass `title` (accessible name) and, for key charts, a `description` with the takeaway. `valueFormatter` formats tooltip/axis/table values.
202
+ - `RichTextEditor` (`/rich-text`) — `label`/`error`/`helperText` like `Input`; `value` + `onChange(html)` (or `outputFormat="json"`); `toolbar={['bold', 'italic', 'link']}` to trim. Tell the user to **sanitize the HTML server-side**; never render it with `dangerouslySetInnerHTML` unsanitized.
188
203
  - `Tour` — onboarding coach marks: `steps=[{ target: '[data-tour="x"]', title, content, placement }]`, `open`, `onClose(reason)`; a missing target falls back to a centred card.
189
204
 
190
205
  **Tokens:** colours are CSS variables (`--dt-primary-600`, `--dt-surface`, …) generated from
191
206
  `tokens/tokens.json`. Use semantic classes (`bg-primary-600`, `bg-surface`, `text-foreground`,
192
207
  `text-muted`, `border-line`, `text-micro`, `rounded-card`) — never hex values or Tailwind's raw
193
- palette (`bg-blue-500`). A product re-themes by loading `dist/tokens/brands/<brand>.css`.
208
+ palette (`bg-blue-500`). A product's brand lives in its own repo as `dreamtree.brand.json`;
209
+ `npx @dreamtree-org/twreact-ui brand build --out app/brand.css` turns it into the CSS to load after
210
+ twreact-ui's CSS. To change a product's colours, edit that file, never the components.
194
211
  The full rule set for screens is `DESIGN.md` in the package root.
195
212
 
196
213
  ```jsx
@@ -326,6 +343,25 @@ fetchApiWith('updateUser', [7, { name: 'A' }]); // apiMap.updateUser(7, {...})
326
343
  Treat `useApi` as a thin axios wrapper, not a caching/dedup layer. For
327
344
  cache, the consumer brings their own (React Query, SWR, etc.).
328
345
 
346
+ ### Charts and rich text (optional entry points)
347
+
348
+ ```jsx
349
+ import { LineChart, DonutChart, Sparkline } from '@dreamtree-org/twreact-ui/charts';
350
+ import { RichTextEditor } from '@dreamtree-org/twreact-ui/rich-text';
351
+
352
+ <LineChart
353
+ data={[{ month: 'Jan', revenue: 4200 }, { month: 'Feb', revenue: 5100 }]}
354
+ index="month"
355
+ categories={['revenue']}
356
+ valueFormatter={(v) => `$${v.toLocaleString()}`}
357
+ title="Revenue, Jan–Feb"
358
+ />
359
+ <DonutChart data={byStatus} index="status" category="tasks" colors={['info', 'error', 'success']} centerLabel="63 tasks" title="Tasks by status" />
360
+ <Sparkline data={weekly} category="signups" color="success" title="Signups, last 4 weeks" />
361
+
362
+ <RichTextEditor label="Description" placeholder="Describe the task…" value={html} onChange={setHtml} maxHeight={320} />
363
+ ```
364
+
329
365
  ### cn
330
366
 
331
367
  ```jsx
@@ -341,7 +377,7 @@ import { cn } from '@dreamtree-org/twreact-ui';
341
377
  1. **Use library components.** When the user asks for a button, input,
342
378
  table, modal, toast, navbar, sidebar, breadcrumb, badge, card,
343
379
  stepper, file uploader, date picker, color picker, location picker,
344
- pagination, or carousel — reach into this library FIRST. Don't
380
+ pagination, carousel, chart or rich-text editor — reach into this library FIRST. Don't
345
381
  propose a hand-rolled `<div>` + Tailwind unless the library has no
346
382
  matching primitive.
347
383
  2. **Use the shared vocabulary.** `variant="primary"`, `size="md"`,
@@ -356,8 +392,11 @@ import { cn } from '@dreamtree-org/twreact-ui';
356
392
  "components render unstyled," check first whether their
357
393
  `tailwind.config.js` `content[]` includes
358
394
  `./node_modules/@dreamtree-org/twreact-ui/dist/**/*.{js,mjs}`.
359
- 6. **Don't deep-import.** Only `@dreamtree-org/twreact-ui` is public.
360
- Don't suggest `@dreamtree-org/twreact-ui/src/components/core/Button`.
395
+ 6. **Don't deep-import.** The public modules are `@dreamtree-org/twreact-ui`
396
+ plus the two optional entry points `@dreamtree-org/twreact-ui/charts` and
397
+ `@dreamtree-org/twreact-ui/rich-text` (each needs its peer installed).
398
+ Don't suggest `@dreamtree-org/twreact-ui/src/components/core/Button`, and
399
+ never import charts or `RichTextEditor` from the root.
361
400
  7. **React 18 only.** If the user is on React 19, warn them: this
362
401
  library has not yet been verified on React 19.
363
402
  8. **Accessibility-first.** Icon-only buttons need `aria-label`.
package/bin/cli.mjs CHANGED
@@ -17,7 +17,8 @@
17
17
 
18
18
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
19
19
  import { fileURLToPath } from "node:url";
20
- import { dirname, join, resolve } from "node:path";
20
+ import { dirname, join, resolve, relative } from "node:path";
21
+ import { createRequire } from "node:module";
21
22
 
22
23
  const __dirname = dirname(fileURLToPath(import.meta.url));
23
24
  const PKG_ROOT = resolve(__dirname, "..");
@@ -56,14 +57,20 @@ function printHelp() {
56
57
  `${PKG} CLI (v${VERSION})\n\n` +
57
58
  `Usage:\n` +
58
59
  ` npx ${PKG} init --ai <provider> [--out <path>] [--force]\n` +
59
- ` npx ${PKG} mcp\n\n` +
60
+ ` npx ${PKG} mcp\n` +
61
+ ` npx ${PKG} brand init|validate|build [dreamtree.brand.json] [options]\n\n` +
60
62
  `Commands:\n` +
61
63
  ` init Install the AI-assistant skill (ai-skills/dreamtree-ui.md) so your\n` +
62
64
  ` coding agent knows this library's components, conventions, wiring.\n` +
63
65
  ` mcp Run the Model Context Protocol server over stdio — gives an MCP\n` +
64
66
  ` client live, queryable access to the library (list_components,\n` +
65
67
  ` get_component, search_components + docs + a compose_ui prompt).\n` +
66
- ` Wire it as: { "command": "npx", "args": ["-y", "${PKG}", "mcp"] }\n\n` +
68
+ ` Wire it as: { "command": "npx", "args": ["-y", "${PKG}", "mcp"] }\n` +
69
+ ` brand Your product's brand lives in your repo as dreamtree.brand.json.\n` +
70
+ ` init write a starter file (--id <slug> --name <Name>)\n` +
71
+ ` validate check it, with a fix for every problem\n` +
72
+ ` build write the CSS to load after twreact-ui's CSS\n` +
73
+ ` (--out app/brand.css, --scope <selector>, --check for CI)\n\n` +
67
74
  `init options:\n` +
68
75
  ` --ai <provider> Target assistant: ${providers} (default: claude)\n` +
69
76
  ` --out <path> Write to this path instead of the provider default\n` +
@@ -74,12 +81,16 @@ function printHelp() {
74
81
  }
75
82
 
76
83
  function parseArgs(argv) {
77
- const out = { _: [], ai: undefined, out: undefined, force: false, help: false, version: false };
84
+ const out = { _: [], ai: undefined, out: undefined, force: false, help: false, version: false, scope: undefined, check: false, id: undefined, name: undefined };
78
85
  for (let i = 0; i < argv.length; i++) {
79
86
  const a = argv[i];
80
87
  if (a === "--ai") out.ai = argv[++i];
81
88
  else if (a === "--out") out.out = argv[++i];
82
89
  else if (a === "--force") out.force = true;
90
+ else if (a === "--scope") out.scope = argv[++i];
91
+ else if (a === "--check") out.check = true;
92
+ else if (a === "--id") out.id = argv[++i];
93
+ else if (a === "--name") out.name = argv[++i];
83
94
  else if (a === "-h" || a === "--help") out.help = true;
84
95
  else if (a === "-v" || a === "--version") out.version = true;
85
96
  else out._.push(a);
@@ -117,6 +128,62 @@ function runInit(args) {
117
128
  );
118
129
  }
119
130
 
131
+ // `brand` — a product keeps its brand in its own repo (dreamtree.brand.json); these commands turn it into CSS.
132
+ // tokens/lib.cjs is CommonJS and dependency-free, so this stays a zero-dependency path.
133
+ const BRAND_FILE = "dreamtree.brand.json";
134
+
135
+ function runBrand(args) {
136
+ const lib = createRequire(import.meta.url)("../tokens/lib.cjs");
137
+ const sub = args._[1];
138
+ const file = resolve(process.cwd(), args._[2] || BRAND_FILE);
139
+ const shown = relative(process.cwd(), file) || BRAND_FILE;
140
+
141
+ if (sub === "init") {
142
+ if (existsSync(file) && !args.force) fail(`${shown} already exists. Re-run with --force to overwrite.`);
143
+ const id = (args.id || "my-product").toLowerCase();
144
+ const base = lib.scales(lib.loadTokens()).primary;
145
+ const starter = {
146
+ $schema: "https://unpkg.com/@dreamtree-org/twreact-ui/tokens/brand.schema.json",
147
+ id,
148
+ name: args.name || id,
149
+ color: { primary: base },
150
+ role: {},
151
+ marketing: {},
152
+ };
153
+ writeFileSync(file, JSON.stringify(starter, null, 2) + "\n");
154
+ process.stdout.write(`✓ wrote ${shown}. Change color.primary to your product's scale, then run: npx ${PKG} brand build\n`);
155
+ return;
156
+ }
157
+
158
+ if (sub === "validate" || sub === "build") {
159
+ let brand;
160
+ try {
161
+ brand = lib.loadBrand(file);
162
+ } catch (err) {
163
+ fail(err.message);
164
+ }
165
+ if (sub === "validate") {
166
+ process.stdout.write(`✓ ${shown} is a valid brand ("${lib.brandId(brand)}").\n`);
167
+ return;
168
+ }
169
+ const css = lib.brandCss(lib.loadTokens(), brand, { scope: args.scope });
170
+ const outFile = resolve(process.cwd(), args.out || "brand.css");
171
+ const outShown = relative(process.cwd(), outFile);
172
+ if (args.check) {
173
+ const current = existsSync(outFile) ? readFileSync(outFile, "utf8") : "";
174
+ if (current !== css) fail(`${outShown} is out of date with ${shown}. Run: npx ${PKG} brand build ${shown} --out ${outShown}${args.scope ? ` --scope "${args.scope}"` : ""}`);
175
+ process.stdout.write(`✓ ${outShown} matches ${shown}.\n`);
176
+ return;
177
+ }
178
+ mkdirSync(dirname(outFile), { recursive: true });
179
+ writeFileSync(outFile, css);
180
+ process.stdout.write(`✓ wrote ${outShown} from ${shown}. Import it after "${PKG}/dist/index.css".\n`);
181
+ return;
182
+ }
183
+
184
+ fail(`unknown brand command "${sub || ""}". Use: brand init | brand validate | brand build.`);
185
+ }
186
+
120
187
  // Boot the bundled MCP server over stdio. The SDK + server are lazy-imported
121
188
  // here so plain `init` carries no dependency cost. stdout is the MCP protocol
122
189
  // channel, so this path must only ever log to stderr. The shipped server reads
@@ -140,6 +207,7 @@ async function main() {
140
207
  const cmd = args._[0];
141
208
  if (cmd === "init") return runInit(args);
142
209
  if (cmd === "mcp") return runMcp();
210
+ if (cmd === "brand") return runBrand(args);
143
211
 
144
212
  fail(`unknown command "${cmd}". Run \`npx ${PKG} --help\`.`);
145
213
  }
@@ -0,0 +1,5 @@
1
+ {
2
+ "main": "../dist/charts/index.js",
3
+ "module": "../dist/esm/charts/index.js",
4
+ "types": "../dist/charts/index.d.ts"
5
+ }
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ var clsx = require('clsx');
4
+ var tailwindMerge = require('tailwind-merge');
5
+
6
+ function _arrayLikeToArray(r, a) {
7
+ (null == a || a > r.length) && (a = r.length);
8
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
9
+ return n;
10
+ }
11
+ function _arrayWithHoles(r) {
12
+ if (Array.isArray(r)) return r;
13
+ }
14
+ function _arrayWithoutHoles(r) {
15
+ if (Array.isArray(r)) return _arrayLikeToArray(r);
16
+ }
17
+ function _defineProperty(e, r, t) {
18
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
19
+ value: t,
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true
23
+ }) : e[r] = t, e;
24
+ }
25
+ function _iterableToArray(r) {
26
+ if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
27
+ }
28
+ function _iterableToArrayLimit(r, l) {
29
+ var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
30
+ if (null != t) {
31
+ var e,
32
+ n,
33
+ i,
34
+ u,
35
+ a = [],
36
+ f = true,
37
+ o = false;
38
+ try {
39
+ if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
40
+ } catch (r) {
41
+ o = true, n = r;
42
+ } finally {
43
+ try {
44
+ if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
45
+ } finally {
46
+ if (o) throw n;
47
+ }
48
+ }
49
+ return a;
50
+ }
51
+ }
52
+ function _nonIterableRest() {
53
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
54
+ }
55
+ function _nonIterableSpread() {
56
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
57
+ }
58
+ function ownKeys(e, r) {
59
+ var t = Object.keys(e);
60
+ if (Object.getOwnPropertySymbols) {
61
+ var o = Object.getOwnPropertySymbols(e);
62
+ r && (o = o.filter(function (r) {
63
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
64
+ })), t.push.apply(t, o);
65
+ }
66
+ return t;
67
+ }
68
+ function _objectSpread2(e) {
69
+ for (var r = 1; r < arguments.length; r++) {
70
+ var t = null != arguments[r] ? arguments[r] : {};
71
+ r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
72
+ _defineProperty(e, r, t[r]);
73
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
74
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
75
+ });
76
+ }
77
+ return e;
78
+ }
79
+ function _objectWithoutProperties(e, t) {
80
+ if (null == e) return {};
81
+ var o,
82
+ r,
83
+ i = _objectWithoutPropertiesLoose(e, t);
84
+ if (Object.getOwnPropertySymbols) {
85
+ var n = Object.getOwnPropertySymbols(e);
86
+ for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
87
+ }
88
+ return i;
89
+ }
90
+ function _objectWithoutPropertiesLoose(r, e) {
91
+ if (null == r) return {};
92
+ var t = {};
93
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
94
+ if (-1 !== e.indexOf(n)) continue;
95
+ t[n] = r[n];
96
+ }
97
+ return t;
98
+ }
99
+ function _slicedToArray(r, e) {
100
+ return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
101
+ }
102
+ function _toConsumableArray(r) {
103
+ return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
104
+ }
105
+ function _toPrimitive(t, r) {
106
+ if ("object" != typeof t || !t) return t;
107
+ var e = t[Symbol.toPrimitive];
108
+ if (void 0 !== e) {
109
+ var i = e.call(t, r);
110
+ if ("object" != typeof i) return i;
111
+ throw new TypeError("@@toPrimitive must return a primitive value.");
112
+ }
113
+ return ("string" === r ? String : Number)(t);
114
+ }
115
+ function _toPropertyKey(t) {
116
+ var i = _toPrimitive(t, "string");
117
+ return "symbol" == typeof i ? i : i + "";
118
+ }
119
+ function _unsupportedIterableToArray(r, a) {
120
+ if (r) {
121
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
122
+ var t = {}.toString.call(r).slice(8, -1);
123
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
124
+ }
125
+ }
126
+
127
+ function cn() {
128
+ for (var _len = arguments.length, inputs = new Array(_len), _key = 0; _key < _len; _key++) {
129
+ inputs[_key] = arguments[_key];
130
+ }
131
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
132
+ }
133
+
134
+ exports._objectSpread2 = _objectSpread2;
135
+ exports._objectWithoutProperties = _objectWithoutProperties;
136
+ exports._slicedToArray = _slicedToArray;
137
+ exports._toConsumableArray = _toConsumableArray;
138
+ exports.cn = cn;