@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.
- package/DESIGN.md +2 -2
- package/README.md +90 -5
- package/ai-skills/dreamtree-ui.md +43 -4
- package/bin/cli.mjs +72 -4
- package/charts/package.json +5 -0
- package/dist/_shared/cn-DPPQUJu1.js +138 -0
- package/dist/ai-schema.json +639 -1
- package/dist/charts/index.d.ts +9 -0
- package/dist/charts/index.d.ts.map +1 -0
- package/dist/charts/index.js +797 -0
- package/dist/components/charts/AreaChart.d.ts +4 -0
- package/dist/components/charts/AreaChart.d.ts.map +1 -0
- package/dist/components/charts/BarChart.d.ts +4 -0
- package/dist/components/charts/BarChart.d.ts.map +1 -0
- package/dist/components/charts/CartesianChart.d.ts +5 -0
- package/dist/components/charts/CartesianChart.d.ts.map +1 -0
- package/dist/components/charts/ChartFrame.d.ts +18 -0
- package/dist/components/charts/ChartFrame.d.ts.map +1 -0
- package/dist/components/charts/DonutChart.d.ts +4 -0
- package/dist/components/charts/DonutChart.d.ts.map +1 -0
- package/dist/components/charts/LineChart.d.ts +4 -0
- package/dist/components/charts/LineChart.d.ts.map +1 -0
- package/dist/components/charts/Sparkline.d.ts +5 -0
- package/dist/components/charts/Sparkline.d.ts.map +1 -0
- package/dist/components/charts/chartTheme.d.ts +36 -0
- package/dist/components/charts/chartTheme.d.ts.map +1 -0
- package/dist/components/core/Table/MobileCard.d.ts.map +1 -1
- package/dist/components/core/Table/MobileFilters.d.ts.map +1 -1
- package/dist/components/core/Table/Table.d.ts.map +1 -1
- package/dist/components/rich-text/RichTextEditor.d.ts +4 -0
- package/dist/components/rich-text/RichTextEditor.d.ts.map +1 -0
- package/dist/components/rich-text/RichTextToolbar.d.ts +219 -0
- package/dist/components/rich-text/RichTextToolbar.d.ts.map +1 -0
- package/dist/components/utility/Stepper.d.ts.map +1 -1
- package/dist/esm/charts/index.js +5 -0
- package/dist/esm/components/blocks/StatCard.js +1 -1
- package/dist/esm/components/charts/AreaChart.js +16 -0
- package/dist/esm/components/charts/BarChart.js +16 -0
- package/dist/esm/components/charts/CartesianChart.js +255 -0
- package/dist/esm/components/charts/ChartFrame.js +170 -0
- package/dist/esm/components/charts/DonutChart.js +122 -0
- package/dist/esm/components/charts/LineChart.js +16 -0
- package/dist/esm/components/charts/Sparkline.js +113 -0
- package/dist/esm/components/charts/chartTheme.js +126 -0
- package/dist/esm/components/core/Select.js +2 -2
- package/dist/esm/components/core/Table/MobileCard.js +5 -2
- package/dist/esm/components/core/Table/MobileFilters.js +10 -3
- package/dist/esm/components/core/Table/Table.js +10 -7
- package/dist/esm/components/rich-text/RichTextEditor.js +244 -0
- package/dist/esm/components/rich-text/RichTextToolbar.js +397 -0
- package/dist/esm/components/utility/Stepper.js +6 -4
- package/dist/esm/rich-text/index.js +1 -0
- package/dist/index.css +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +50 -22
- package/dist/index.js +50 -22
- package/dist/rich-text/index.d.ts +5 -0
- package/dist/rich-text/index.d.ts.map +1 -0
- package/dist/rich-text/index.js +654 -0
- package/dist/tokens/brands/practicepro.json +1 -0
- package/dist/tokens/brands/studio.css +14 -0
- package/dist/tokens/brands/studio.json +21 -0
- package/dist/types/charts.d.ts +97 -0
- package/dist/types/charts.d.ts.map +1 -0
- package/dist/types/rich-text.d.ts +50 -0
- package/dist/types/rich-text.d.ts.map +1 -0
- package/doc/README.md +13 -0
- package/mcp/catalog.snapshot.json +1016 -10
- package/mcp/server-core.mjs +30 -1
- package/package.json +39 -2
- package/rich-text/package.json +5 -0
- package/tokens/brand.schema.json +612 -0
- package/tokens/brands/practicepro.json +1 -0
- package/tokens/brands/studio.json +21 -0
- 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
|
-
|
|
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
|
-
-
|
|
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.
|
|
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
|
-
**
|
|
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 "
|
|
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
|
|
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
|
|
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.**
|
|
360
|
-
|
|
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
|
|
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
|
|
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,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;
|