@cascivo/react 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,26 +73,27 @@ RSC without any extra wrapper.
73
73
 
74
74
  > **No bundler? (CDN, import maps, plain `<link>`)** Import the aggregate sheet
75
75
  > `@cascivo/react/styles.css` — every component's CSS **plus the tokens and the
76
- > light & dark themes** in one file (~305 KB / ~40 KB gzip, not tree-shaken). It is
76
+ > light & dark themes** in one file (~328 KB / ~47 KB gzip, not tree-shaken). It is
77
77
  > self-contained: this single import gives you a fully-colored app with no separate
78
- > `@cascivo/themes` import. With a bundler (CSR) you don't need it; import it only if
79
- > you prefer one explicit stylesheet over per-component tree-shaking, or for Vite SSR
80
- > (below), where it is required.
78
+ > `@cascivo/themes` import. On **any** bundler CSR, SSR or RSC alike you don't
79
+ > need it, and importing it costs you the per-component tree-shaking; reach for it
80
+ > only when nothing walks the module graph.
81
81
 
82
82
  > **Vite SSR / TanStack Start / Remix / workerd?** The 4-line SSR checklist (full
83
83
  > recipe: [USING-WITH-VITE-SSR.md](https://github.com/cascivo/cascivo/blob/main/docs/USING-WITH-VITE-SSR.md)):
84
84
  >
85
- > 1. **`ssr: { noExternal: [/^@cascivo\//] }`** in `vite.config` (or the
86
- > `cascivoSsr()` plugin from `@cascivo/vite-plugin`). Without it a bare
87
- > server-side ESM loader can't resolve the per-component `.css` side-effect
88
- > imports and throws `Unknown file extension ".css"`.
85
+ > 1. **No Vite config needed** on 0.10+ — the `node` export condition ships a
86
+ > CSS-free server build a bare ESM loader can import. Pinned below 0.10? Add
87
+ > `ssr: { noExternal: [/^@cascivo\//] }` (or the `cascivoSsr()` plugin from
88
+ > `@cascivo/vite-plugin`), else the loader throws `Unknown file extension ".css"`.
89
89
  > 2. **`@preact/signals-react` 3.x** — on React 19 the 2.x line fails to load. The
90
90
  > peer range enforces `>=3`.
91
- > 3. **Import the CSS once** in your root entry: `@cascivo/react/styles.css` +
92
- > `@cascivo/themes/all` (+ `@cascivo/charts/styles.css` if you use charts). If you
93
- > typecheck (`tsc --noEmit`), these bare `.css` imports need ambient types — add
94
- > `src/vite-env.d.ts` with `/// <reference types="vite/client" />` or
95
- > `declare module '*.css'`, else TS2307/TS2882.
91
+ > 3. **Import a theme once** in your root entry: `@cascivo/themes/light-dark.css`
92
+ > (+ `@cascivo/charts/styles.css` if you use charts). Component CSS is automatic
93
+ > here too — don't add the aggregate. If you typecheck (`tsc --noEmit`), these
94
+ > bare `.css` imports need ambient types — add `src/vite-env.d.ts` with
95
+ > `/// <reference types="vite/client" />` or `declare module '*.css'`, else
96
+ > TS2307/TS2882.
96
97
  > 4. **Theme without a mismatch:** inline `themePreloadScript()` in `<head>` and add
97
98
  > `suppressHydrationWarning` to `<html>`, or hard-code `data-theme` for a fixed theme.
98
99
  >
@@ -21,7 +21,7 @@ function s({ items: s, maxVisible: c, className: l, ariaLabel: u }) {
21
21
  }) : e.href === void 0 ? /* @__PURE__ */ o("span", { children: e.label }) : /* @__PURE__ */ o(d, {
22
22
  href: e.href,
23
23
  children: e.label
24
- }) }, `${t}-${e.label}`)) })
24
+ }) }, e.id ?? `${t}-${e.label}`)) })
25
25
  });
26
26
  }
27
27
  //#endregion
@@ -6,7 +6,10 @@ import { cn as n, useClipboard as r, useSignals as i } from "@cascivo/core";
6
6
  import { builtin as a, t as o } from "@cascivo/i18n";
7
7
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
8
8
  //#region ../components/src/code-snippet/code-snippet.tsx
9
- function l({ code: n, language: r }) {
9
+ function l() {
10
+ return globalThis.process?.env?.NODE_ENV !== "production";
11
+ }
12
+ function u({ code: n, language: r }) {
10
13
  return /* @__PURE__ */ s("code", {
11
14
  className: t.code,
12
15
  children: e(n, r).map((e, t) => e.type === "text" ? /* @__PURE__ */ s("span", { children: e.value }, t) : /* @__PURE__ */ s("span", {
@@ -15,15 +18,17 @@ function l({ code: n, language: r }) {
15
18
  }, t))
16
19
  });
17
20
  }
18
- function u({ code: e, variant: u = "single", language: d, terminal: f = !1, title: p, showLineNumbers: m, showCopyButton: h, labels: g, className: _ }) {
21
+ function d({ code: e, children: d, variant: f = "single", language: p, terminal: m = !1, title: h, showLineNumbers: g, showCopyButton: _, labels: v, className: y }) {
19
22
  i();
20
- let { copied: v, copy: y } = r(), b = g?.copy ?? o(a.codeSnippet.copy), x = g?.copied ?? o(a.codeSnippet.copied), S = h ?? u !== "inline" ? /* @__PURE__ */ s("button", {
23
+ let b = e ?? d ?? "";
24
+ l() && e === void 0 && d === void 0 && console.error("CodeSnippet: pass the code either as the `code` prop or as a plain-string child. Rendering nothing.");
25
+ let { copied: x, copy: S } = r(), C = v?.copy ?? o(a.codeSnippet.copy), w = v?.copied ?? o(a.codeSnippet.copied), T = _ ?? f !== "inline" ? /* @__PURE__ */ s("button", {
21
26
  type: "button",
22
- "aria-label": v.value ? x : b,
23
- "data-state": v.value ? "copied" : "idle",
27
+ "aria-label": x.value ? w : C,
28
+ "data-state": x.value ? "copied" : "idle",
24
29
  className: t.copy,
25
- onClick: () => void y(e),
26
- children: v.value ? /* @__PURE__ */ s("svg", {
30
+ onClick: () => void S(b),
31
+ children: x.value ? /* @__PURE__ */ s("svg", {
27
32
  "aria-hidden": "true",
28
33
  viewBox: "0 0 16 16",
29
34
  className: t.icon,
@@ -56,21 +61,21 @@ function u({ code: e, variant: u = "single", language: d, terminal: f = !1, titl
56
61
  })]
57
62
  })
58
63
  }) : null;
59
- if (u === "inline") return /* @__PURE__ */ c("span", {
60
- className: n(t.root, _),
64
+ if (f === "inline") return /* @__PURE__ */ c("span", {
65
+ className: n(t.root, y),
61
66
  "data-variant": "inline",
62
67
  children: [/* @__PURE__ */ s("code", {
63
68
  className: t.inlineCode,
64
- children: e
65
- }), S]
69
+ children: b
70
+ }), T]
66
71
  });
67
- let C = e.split("\n"), w = u === "multi" && m && !d;
72
+ let E = b.split("\n"), D = f === "multi" && g && !p;
68
73
  return /* @__PURE__ */ c("div", {
69
- className: n(t.root, _),
70
- "data-variant": u,
71
- "data-terminal": f ? "" : void 0,
74
+ className: n(t.root, y),
75
+ "data-variant": f,
76
+ "data-terminal": m ? "" : void 0,
72
77
  children: [
73
- f && /* @__PURE__ */ c("div", {
78
+ m && /* @__PURE__ */ c("div", {
74
79
  className: t.bar,
75
80
  "aria-hidden": "true",
76
81
  children: [/* @__PURE__ */ c("span", {
@@ -80,16 +85,16 @@ function u({ code: e, variant: u = "single", language: d, terminal: f = !1, titl
80
85
  /* @__PURE__ */ s("span", { className: t.dot }),
81
86
  /* @__PURE__ */ s("span", { className: t.dot })
82
87
  ]
83
- }), p && /* @__PURE__ */ s("span", {
88
+ }), h && /* @__PURE__ */ s("span", {
84
89
  className: t.title,
85
- children: p
90
+ children: h
86
91
  })]
87
92
  }),
88
93
  /* @__PURE__ */ s("pre", {
89
94
  className: t.pre,
90
- children: w ? /* @__PURE__ */ s("code", {
95
+ children: D ? /* @__PURE__ */ s("code", {
91
96
  className: t.code,
92
- children: C.map((e, n) => /* @__PURE__ */ c("span", {
97
+ children: E.map((e, n) => /* @__PURE__ */ c("span", {
93
98
  className: t.line,
94
99
  children: [/* @__PURE__ */ s("span", {
95
100
  "aria-hidden": "true",
@@ -100,17 +105,17 @@ function u({ code: e, variant: u = "single", language: d, terminal: f = !1, titl
100
105
  children: e
101
106
  })]
102
107
  }, n))
103
- }) : d ? /* @__PURE__ */ s(l, {
104
- code: e,
105
- language: d
108
+ }) : p ? /* @__PURE__ */ s(u, {
109
+ code: b,
110
+ language: p
106
111
  }) : /* @__PURE__ */ s("code", {
107
112
  className: t.code,
108
- children: e
113
+ children: b
109
114
  })
110
115
  }),
111
- S
116
+ T
112
117
  ]
113
118
  });
114
119
  }
115
120
  //#endregion
116
- export { u as CodeSnippet };
121
+ export { d as CodeSnippet };
package/dist/dock/dock.js CHANGED
@@ -26,7 +26,7 @@ function s({ items: s, activeIndex: c, className: l, ariaLabel: u }) {
26
26
  className: e.label,
27
27
  children: n.label
28
28
  })]
29
- }, r);
29
+ }, n.id ?? `${r}-${n.label}`);
30
30
  })
31
31
  });
32
32
  }
package/dist/index.d.ts CHANGED
@@ -159,6 +159,11 @@ interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
159
159
  * (`neutral | info | success | warning | danger`) as well as Badge's historical spellings
160
160
  * (`default`, `destructive`, `error`) — they are aliases of the same tones, so one domain
161
161
  * enum can drive `Badge`, `Tag`, `Status` and `Notification` with no lookup table.
162
+ *
163
+ * To name this type in your own code, import it: `import type { Tone } from
164
+ * '@cascivo/react/types'` on the prebuilt path, or from `@cascivo/core` if you copied the
165
+ * source. Do not add `@cascivo/core` to a prebuilt app just for this; it is a transitive
166
+ * dependency there.
162
167
  */
163
168
  variant?: ToneInput | BadgeVariant;
164
169
  size?: 'sm' | 'md';
@@ -708,7 +713,8 @@ interface TabsTriggerProps extends HTMLAttributes<HTMLButtonElement> {
708
713
  *
709
714
  * With `asChild`, `disabled` becomes `aria-disabled`: it is not a valid attribute on an
710
715
  * anchor, and a slotted element gets no `type` either. See
711
- * [USING-WITH-A-ROUTER.md](../../../../docs/USING-WITH-A-ROUTER.md) for the full recipe.
716
+ * https://cascivo.com/docs/using-with-a-router.md for the full recipe — offline:
717
+ * `npx -y @cascivo/docs guide using-with-a-router`.
712
718
  */
713
719
  asChild?: boolean;
714
720
  }
@@ -839,7 +845,8 @@ interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
839
845
  * styling lands on your router's `Link`. **This is the supported way to style an
840
846
  * in-content router link** — `setLinkComponent` only covers the config-driven nav
841
847
  * components (SideNav, ShellHeader, Breadcrumb, …), not links you write in page
842
- * content. See `docs/USING-WITH-A-ROUTER.md`.
848
+ * content. See https://cascivo.com/docs/using-with-a-router.md — offline:
849
+ * `npx -y @cascivo/docs guide using-with-a-router`.
843
850
  *
844
851
  * ```tsx
845
852
  * <Link asChild><RouterLink to="/projects/a">Project A</RouterLink></Link>
@@ -855,6 +862,17 @@ interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
855
862
  */
856
863
  declare const Link: import("react").ForwardRefExoticComponent<LinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
857
864
  interface BreadcrumbItem {
865
+ /**
866
+ * Stable React key. Provide when `label`/`href` may repeat.
867
+ *
868
+ * A real breadcrumb repeats hrefs — "Overview" and "Projects" both pointing at `/` is the
869
+ * normal shape of a console trail — and without an `id` the crumbs key on their index, so
870
+ * reordering or truncating the trail re-keys every row after the change.
871
+ * `SideNavItem`, `ShellHeaderNavLink`, `ShellHeaderNavMenuItem`, `HeaderLink`,
872
+ * `CommandItem` and `SwitcherLink` all carry this field; `BreadcrumbItem` was missed by
873
+ * both earlier sweeps (2026-08-14 report §9).
874
+ */
875
+ id?: string;
858
876
  label: string;
859
877
  href?: string;
860
878
  }
@@ -2782,6 +2800,16 @@ interface StatProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
2782
2800
  * a `Card` you control; `Kpi` always brings its own chrome, which made a `Stat` row and
2783
2801
  * a `Kpi` row on adjacent pages of one dashboard look like different products. Set this
2784
2802
  * when you are mixing the two, or use it instead of hand-wrapping every `Stat` in a `Card`.
2803
+ *
2804
+ * ⚠ **`card` matches the CHROME only — the internal layout still differs, so do not mix
2805
+ * `Stat` and `Kpi` in one app.** `Kpi` puts value and delta on one line with the sparkline
2806
+ * below; `Stat` stacks value → delta → help text and puts `visual` in a trailing slot.
2807
+ * With `card` set they share surface, border, radius and padding and still read as two
2808
+ * tile designs (2026-08-14 report §6 — the exact symptom this prop was added to cure, on
2809
+ * an overview page using `Stat card` next to an analytics page using `Kpi`).
2810
+ *
2811
+ * Pick one per app: `Kpi` when you have a numeric delta it should format and a sparkline;
2812
+ * `Stat` otherwise.
2785
2813
  */
2786
2814
  card?: boolean;
2787
2815
  /** Trailing visual, e.g. a `Sparkline` from the separate `@cascivo/charts` package (not exported from `@cascivo/react`; `pnpm add @cascivo/charts`). Purely decorative — mark it `aria-hidden` yourself if it duplicates the value/delta already announced. */
@@ -2804,6 +2832,11 @@ interface StatusProps extends HTMLAttributes<HTMLSpanElement> {
2804
2832
  * Severity tone. Accepts the catalog-wide `Tone` vocabulary
2805
2833
  * (`neutral | info | success | warning | danger`) plus Status's historical spellings
2806
2834
  * (`error`, `destructive`, `default`).
2835
+ *
2836
+ * To name this type in your own code — a `Record<DeployState, Tone>` is the usual shape —
2837
+ * import it: `import type { Tone } from '@cascivo/react/types'` on the prebuilt path, or
2838
+ * from `@cascivo/core` if you copied the source. Do not add `@cascivo/core` to a prebuilt
2839
+ * app just for this; it is a transitive dependency there.
2807
2840
  */
2808
2841
  status?: ToneInput;
2809
2842
  /**
@@ -4181,6 +4214,16 @@ declare function Resizable({
4181
4214
  label
4182
4215
  }: ResizableProps): import("react").JSX.Element;
4183
4216
  interface DockItem {
4217
+ /**
4218
+ * Stable React key. Provide when `label`/`href` may repeat.
4219
+ *
4220
+ * Without it the items key on their array index, so inserting or reordering a dock entry
4221
+ * re-keys every item after it. `SideNavItem`, `ShellHeaderNavLink`, `SwitcherLink`,
4222
+ * `BreadcrumbItem` and the rest carry this field; `Dock` was missed by all three earlier
4223
+ * sweeps and found only once the guard derived its subjects instead of listing them
4224
+ * (2026-08-14 report §9).
4225
+ */
4226
+ id?: string;
4184
4227
  label: string;
4185
4228
  icon: React.ReactNode;
4186
4229
  onClick?: () => void;
@@ -4495,8 +4538,24 @@ interface NativeSelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>
4495
4538
  declare const NativeSelect: import("react").ForwardRefExoticComponent<NativeSelectProps & import("react").RefAttributes<HTMLSelectElement>>;
4496
4539
  type CodeLang = 'bash' | 'css' | 'js' | 'ts';
4497
4540
  interface CodeSnippetProps {
4498
- /** The code to display (and copy). */
4499
- code: string;
4541
+ /**
4542
+ * The code to display (and copy). Equivalent to passing a plain string as `children`;
4543
+ * `code` wins if you pass both.
4544
+ *
4545
+ * One of the two is required. It is a **string**, not a `ReactNode`, because the content
4546
+ * is tokenized for highlighting and handed to the clipboard — neither works on arbitrary
4547
+ * JSX, so accepting nodes here would silently produce an uncopyable snippet.
4548
+ */
4549
+ code?: string;
4550
+ /**
4551
+ * The code, as children — `<CodeSnippet>npm i foo</CodeSnippet>`.
4552
+ *
4553
+ * Every other content component in the catalog takes children, so this was the natural
4554
+ * guess and the only compile error in a 2026-08-14 adopter's whole build (§8). Must be a
4555
+ * plain string for the reason on `code` above; interpolation is fine
4556
+ * (`{`npm i ${pkg}`}`), arbitrary elements are not.
4557
+ */
4558
+ children?: string;
4500
4559
  /**
4501
4560
  * inline = a <code> span; single = one-line <pre>; multi = multi-line <pre>.
4502
4561
  *
@@ -4535,6 +4594,7 @@ interface CodeSnippetProps {
4535
4594
  */
4536
4595
  declare function CodeSnippet({
4537
4596
  code,
4597
+ children,
4538
4598
  variant,
4539
4599
  language,
4540
4600
  terminal,
@@ -5037,8 +5097,27 @@ declare function AutoGrid({
5037
5097
  ...props
5038
5098
  }: AutoGridProps): import("react").JSX.Element;
5039
5099
  interface PageHeaderProps {
5040
- title: string;
5041
- description?: string;
5100
+ /**
5101
+ * The page title, rendered as the `<h1>`.
5102
+ *
5103
+ * `ReactNode`, not `string`, so a deploy console can put the production domain in the
5104
+ * heading as a link and a status badge beside the project name — the canonical shape of a
5105
+ * project header. `RECIPE-DASHBOARD.md` tells you not to hand-compose `PageHeader` out of
5106
+ * `Heading`/`Text`/`Flex`, so this has to be composable here (2026-08-14 report §7).
5107
+ *
5108
+ * ```tsx
5109
+ * <PageHeader
5110
+ * title={<>acme-web <Badge variant="success">Ready</Badge></>}
5111
+ * description={<>Deployed from <Link asChild><RouterLink to="/c/abc">abc123</RouterLink></Link></>}
5112
+ * />
5113
+ * ```
5114
+ *
5115
+ * Keep it short and heading-like: it is an `<h1>`, so anything block-level inside it is
5116
+ * invalid HTML and will fight the heading's own typography.
5117
+ */
5118
+ title: ReactNode;
5119
+ /** Supporting line under the title, rendered as a `<p>`. Accepts nodes, like `title`. */
5120
+ description?: ReactNode;
5042
5121
  breadcrumb?: ReactNode;
5043
5122
  actions?: ReactNode;
5044
5123
  className?: string;
@@ -1,5 +1,5 @@
1
1
  import e from "./large-title-header.module.js";
2
- import { cn as t } from "@cascivo/core";
2
+ import { cn as t } from "@cascivo/core/pure";
3
3
  import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  //#region ../components/src/large-title-header/large-title-header.tsx
5
5
  function i({ title: i, children: a, leading: o, actions: s, level: c = 1, collapseDistance: l = 48, className: u }) {
@@ -21,7 +21,7 @@ function s({ items: s, maxVisible: c, className: l, ariaLabel: u }) {
21
21
  }) : e.href === void 0 ? /* @__PURE__ */ o("span", { children: e.label }) : /* @__PURE__ */ o(d, {
22
22
  href: e.href,
23
23
  children: e.label
24
- }) }, `${t}-${e.label}`)) })
24
+ }) }, e.id ?? `${t}-${e.label}`)) })
25
25
  });
26
26
  }
27
27
  //#endregion
@@ -6,7 +6,10 @@ import { cn as n, useClipboard as r, useSignals as i } from "@cascivo/core";
6
6
  import { builtin as a, t as o } from "@cascivo/i18n";
7
7
  import { jsx as s, jsxs as c } from "react/jsx-runtime";
8
8
  //#region ../components/src/code-snippet/code-snippet.tsx
9
- function l({ code: n, language: r }) {
9
+ function l() {
10
+ return globalThis.process?.env?.NODE_ENV !== "production";
11
+ }
12
+ function u({ code: n, language: r }) {
10
13
  return /* @__PURE__ */ s("code", {
11
14
  className: t.code,
12
15
  children: e(n, r).map((e, t) => e.type === "text" ? /* @__PURE__ */ s("span", { children: e.value }, t) : /* @__PURE__ */ s("span", {
@@ -15,15 +18,17 @@ function l({ code: n, language: r }) {
15
18
  }, t))
16
19
  });
17
20
  }
18
- function u({ code: e, variant: u = "single", language: d, terminal: f = !1, title: p, showLineNumbers: m, showCopyButton: h, labels: g, className: _ }) {
21
+ function d({ code: e, children: d, variant: f = "single", language: p, terminal: m = !1, title: h, showLineNumbers: g, showCopyButton: _, labels: v, className: y }) {
19
22
  i();
20
- let { copied: v, copy: y } = r(), b = g?.copy ?? o(a.codeSnippet.copy), x = g?.copied ?? o(a.codeSnippet.copied), S = h ?? u !== "inline" ? /* @__PURE__ */ s("button", {
23
+ let b = e ?? d ?? "";
24
+ l() && e === void 0 && d === void 0 && console.error("CodeSnippet: pass the code either as the `code` prop or as a plain-string child. Rendering nothing.");
25
+ let { copied: x, copy: S } = r(), C = v?.copy ?? o(a.codeSnippet.copy), w = v?.copied ?? o(a.codeSnippet.copied), T = _ ?? f !== "inline" ? /* @__PURE__ */ s("button", {
21
26
  type: "button",
22
- "aria-label": v.value ? x : b,
23
- "data-state": v.value ? "copied" : "idle",
27
+ "aria-label": x.value ? w : C,
28
+ "data-state": x.value ? "copied" : "idle",
24
29
  className: t.copy,
25
- onClick: () => void y(e),
26
- children: v.value ? /* @__PURE__ */ s("svg", {
30
+ onClick: () => void S(b),
31
+ children: x.value ? /* @__PURE__ */ s("svg", {
27
32
  "aria-hidden": "true",
28
33
  viewBox: "0 0 16 16",
29
34
  className: t.icon,
@@ -56,21 +61,21 @@ function u({ code: e, variant: u = "single", language: d, terminal: f = !1, titl
56
61
  })]
57
62
  })
58
63
  }) : null;
59
- if (u === "inline") return /* @__PURE__ */ c("span", {
60
- className: n(t.root, _),
64
+ if (f === "inline") return /* @__PURE__ */ c("span", {
65
+ className: n(t.root, y),
61
66
  "data-variant": "inline",
62
67
  children: [/* @__PURE__ */ s("code", {
63
68
  className: t.inlineCode,
64
- children: e
65
- }), S]
69
+ children: b
70
+ }), T]
66
71
  });
67
- let C = e.split("\n"), w = u === "multi" && m && !d;
72
+ let E = b.split("\n"), D = f === "multi" && g && !p;
68
73
  return /* @__PURE__ */ c("div", {
69
- className: n(t.root, _),
70
- "data-variant": u,
71
- "data-terminal": f ? "" : void 0,
74
+ className: n(t.root, y),
75
+ "data-variant": f,
76
+ "data-terminal": m ? "" : void 0,
72
77
  children: [
73
- f && /* @__PURE__ */ c("div", {
78
+ m && /* @__PURE__ */ c("div", {
74
79
  className: t.bar,
75
80
  "aria-hidden": "true",
76
81
  children: [/* @__PURE__ */ c("span", {
@@ -80,16 +85,16 @@ function u({ code: e, variant: u = "single", language: d, terminal: f = !1, titl
80
85
  /* @__PURE__ */ s("span", { className: t.dot }),
81
86
  /* @__PURE__ */ s("span", { className: t.dot })
82
87
  ]
83
- }), p && /* @__PURE__ */ s("span", {
88
+ }), h && /* @__PURE__ */ s("span", {
84
89
  className: t.title,
85
- children: p
90
+ children: h
86
91
  })]
87
92
  }),
88
93
  /* @__PURE__ */ s("pre", {
89
94
  className: t.pre,
90
- children: w ? /* @__PURE__ */ s("code", {
95
+ children: D ? /* @__PURE__ */ s("code", {
91
96
  className: t.code,
92
- children: C.map((e, n) => /* @__PURE__ */ c("span", {
97
+ children: E.map((e, n) => /* @__PURE__ */ c("span", {
93
98
  className: t.line,
94
99
  children: [/* @__PURE__ */ s("span", {
95
100
  "aria-hidden": "true",
@@ -100,17 +105,17 @@ function u({ code: e, variant: u = "single", language: d, terminal: f = !1, titl
100
105
  children: e
101
106
  })]
102
107
  }, n))
103
- }) : d ? /* @__PURE__ */ s(l, {
104
- code: e,
105
- language: d
108
+ }) : p ? /* @__PURE__ */ s(u, {
109
+ code: b,
110
+ language: p
106
111
  }) : /* @__PURE__ */ s("code", {
107
112
  className: t.code,
108
- children: e
113
+ children: b
109
114
  })
110
115
  }),
111
- S
116
+ T
112
117
  ]
113
118
  });
114
119
  }
115
120
  //#endregion
116
- export { u as CodeSnippet };
121
+ export { d as CodeSnippet };
@@ -26,7 +26,7 @@ function s({ items: s, activeIndex: c, className: l, ariaLabel: u }) {
26
26
  className: e.label,
27
27
  children: n.label
28
28
  })]
29
- }, r);
29
+ }, n.id ?? `${r}-${n.label}`);
30
30
  })
31
31
  });
32
32
  }
@@ -1,5 +1,5 @@
1
1
  import e from "./large-title-header.module.js";
2
- import { cn as t } from "@cascivo/core";
2
+ import { cn as t } from "@cascivo/core/pure";
3
3
  import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  //#region ../components/src/large-title-header/large-title-header.tsx
5
5
  function i({ title: i, children: a, leading: o, actions: s, level: c = 1, collapseDistance: l = 48, className: u }) {
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import e from "./swap.module.js";
2
4
  import { useControllableSignal as t, useSignals as n } from "@cascivo/core";
3
5
  import { jsx as r, jsxs as i } from "react/jsx-runtime";
package/dist/swap/swap.js CHANGED
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import e from "./swap.module.js";
2
4
  import { useControllableSignal as t, useSignals as n } from "@cascivo/core";
3
5
  import { jsx as r, jsxs as i } from "react/jsx-runtime";
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The catalog-wide vocabulary types, importable on the prebuilt path.
3
+ *
4
+ * `Status.status` and `Badge.variant` are `ToneInput`; every layout `gap` is a
5
+ * `SpaceStep`. These declarations are the same ones the main entry's props reference, so
6
+ * a value typed here is assignable there.
7
+ *
8
+ * import type { Tone } from '@cascivo/react/types'
9
+ * const TONE: Record<DeployState, Tone> = { ready: 'success', error: 'danger' }
10
+ * <Status status={TONE[deployment.state]} />
11
+ */
12
+ export type {
13
+ Tone,
14
+ ToneAlias,
15
+ ToneInput,
16
+ Progress,
17
+ ProgressAlias,
18
+ ProgressInput,
19
+ SpaceStep,
20
+ RovingOrientation,
21
+ } from '@cascivo/core'
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cascivo/react",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "private": false,
5
5
  "description": "Prebuilt cascivo design system components — use without copying source. @cascivo/themes installs with it; pair with @cascivo/charts (data-viz) and @cascivo/icons. Docs offline: npx @cascivo/docs",
6
6
  "keywords": [
@@ -33,10 +33,16 @@
33
33
  "exports": {
34
34
  ".": {
35
35
  "types": "./dist/index.d.ts",
36
+ "react-server": "./dist/index.js",
36
37
  "node": "./dist/node/index.js",
37
38
  "import": "./dist/index.js",
38
39
  "default": "./dist/index.js"
39
40
  },
41
+ "./types": {
42
+ "types": "./dist/types.d.ts",
43
+ "import": "./dist/types.js",
44
+ "default": "./dist/types.js"
45
+ },
40
46
  "./styles.css": "./dist/styles.css",
41
47
  "./package.json": "./package.json"
42
48
  },
@@ -45,13 +51,14 @@
45
51
  "provenance": true
46
52
  },
47
53
  "dependencies": {
48
- "@cascivo/core": "^0.17.0",
49
- "@cascivo/i18n": "^0.17.0",
50
- "@cascivo/themes": "^0.4.11"
54
+ "@cascivo/core": "^0.18.0",
55
+ "@cascivo/i18n": "^0.18.0",
56
+ "@cascivo/themes": "^0.4.13"
51
57
  },
52
58
  "devDependencies": {
53
59
  "@testing-library/jest-dom": "^6.9.1",
54
60
  "@testing-library/react": "^16.3.2",
61
+ "@types/node": "^25",
55
62
  "@types/react": "^19.2.17",
56
63
  "@types/react-dom": "^19.2.3",
57
64
  "jsdom": "^29",
package/readme.body.md CHANGED
@@ -55,26 +55,27 @@ RSC without any extra wrapper.
55
55
 
56
56
  > **No bundler? (CDN, import maps, plain `<link>`)** Import the aggregate sheet
57
57
  > `@cascivo/react/styles.css` — every component's CSS **plus the tokens and the
58
- > light & dark themes** in one file (~305 KB / ~40 KB gzip, not tree-shaken). It is
58
+ > light & dark themes** in one file (~328 KB / ~47 KB gzip, not tree-shaken). It is
59
59
  > self-contained: this single import gives you a fully-colored app with no separate
60
- > `@cascivo/themes` import. With a bundler (CSR) you don't need it; import it only if
61
- > you prefer one explicit stylesheet over per-component tree-shaking, or for Vite SSR
62
- > (below), where it is required.
60
+ > `@cascivo/themes` import. On **any** bundler CSR, SSR or RSC alike you don't
61
+ > need it, and importing it costs you the per-component tree-shaking; reach for it
62
+ > only when nothing walks the module graph.
63
63
 
64
64
  > **Vite SSR / TanStack Start / Remix / workerd?** The 4-line SSR checklist (full
65
65
  > recipe: [USING-WITH-VITE-SSR.md](https://github.com/cascivo/cascivo/blob/main/docs/USING-WITH-VITE-SSR.md)):
66
66
  >
67
- > 1. **`ssr: { noExternal: [/^@cascivo\//] }`** in `vite.config` (or the
68
- > `cascivoSsr()` plugin from `@cascivo/vite-plugin`). Without it a bare
69
- > server-side ESM loader can't resolve the per-component `.css` side-effect
70
- > imports and throws `Unknown file extension ".css"`.
67
+ > 1. **No Vite config needed** on 0.10+ — the `node` export condition ships a
68
+ > CSS-free server build a bare ESM loader can import. Pinned below 0.10? Add
69
+ > `ssr: { noExternal: [/^@cascivo\//] }` (or the `cascivoSsr()` plugin from
70
+ > `@cascivo/vite-plugin`), else the loader throws `Unknown file extension ".css"`.
71
71
  > 2. **`@preact/signals-react` 3.x** — on React 19 the 2.x line fails to load. The
72
72
  > peer range enforces `>=3`.
73
- > 3. **Import the CSS once** in your root entry: `@cascivo/react/styles.css` +
74
- > `@cascivo/themes/all` (+ `@cascivo/charts/styles.css` if you use charts). If you
75
- > typecheck (`tsc --noEmit`), these bare `.css` imports need ambient types — add
76
- > `src/vite-env.d.ts` with `/// <reference types="vite/client" />` or
77
- > `declare module '*.css'`, else TS2307/TS2882.
73
+ > 3. **Import a theme once** in your root entry: `@cascivo/themes/light-dark.css`
74
+ > (+ `@cascivo/charts/styles.css` if you use charts). Component CSS is automatic
75
+ > here too — don't add the aggregate. If you typecheck (`tsc --noEmit`), these
76
+ > bare `.css` imports need ambient types — add `src/vite-env.d.ts` with
77
+ > `/// <reference types="vite/client" />` or `declare module '*.css'`, else
78
+ > TS2307/TS2882.
78
79
  > 4. **Theme without a mismatch:** inline `themePreloadScript()` in `<head>` and add
79
80
  > `suppressHydrationWarning` to `<html>`, or hard-code `data-theme` for a fixed theme.
80
81
  >