@tokyo3rdhq/magi-design-system 0.3.0 → 0.4.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
@@ -5,7 +5,8 @@ Shared visual foundation for the [MAGI](https://magi.website) product family —
5
5
  - **React 18** + **TypeScript** (strict)
6
6
  - **Plain CSS** with `magi-` prefixed classes — works in any React app, no Tailwind, no Next, no Vite, no Cloudflare coupling
7
7
  - **CSS custom properties** for every token — readable from any stylesheet
8
- - **~16 kB** stylesheet (gzip ~3 kB), **~4 kB** JS (gzip ~1 kB)
8
+ - **CSS `@layer` cascade** — consumer unlayered styles always win (declared architecture, not accidental)
9
+ - **~24 kB** stylesheet (gzip ~4 kB), **~9 kB** JS (gzip ~3 kB)
9
10
 
10
11
  ## Install
11
12
 
@@ -24,17 +25,21 @@ import '@tokyo3rdhq/magi-design-system/styles.css';
24
25
  <div id="root"></div>
25
26
  </body>
26
27
 
27
- // 3. Wrap your app in <ProductTheme> to optionally override the accent.
28
- import { ProductTheme } from '@tokyo3rdhq/magi-design-system';
28
+ // 3. Wrap your app in <AppTheme> to optionally override the accent.
29
+ // 0.3.0+: AppTheme is a context provider + sets accent CSS
30
+ // variables on <html>. No DOM wrapper.
31
+ import { AppTheme } from '@tokyo3rdhq/magi-design-system';
29
32
 
30
- <ProductTheme accent="green">
33
+ <AppTheme accent="green" name="magi-portal">
31
34
  <App />
32
- </ProductTheme>
35
+ </AppTheme>
33
36
  ```
34
37
 
35
38
  ## API
36
39
 
37
- ### `<Container>` — centers and constrains max-width
40
+ ### Layout primitives
41
+
42
+ #### `<Container>` — centers and constrains max-width
38
43
 
39
44
  ```tsx
40
45
  import { Container } from '@tokyo3rdhq/magi-design-system';
@@ -53,7 +58,7 @@ import { Container } from '@tokyo3rdhq/magi-design-system';
53
58
  | `wide` | 1400 px |
54
59
  | `full` | 100 % |
55
60
 
56
- ### `<Section>` — page-level vertical rhythm
61
+ #### `<Section>` — page-level vertical rhythm
57
62
 
58
63
  ```tsx
59
64
  import { Section } from '@tokyo3rdhq/magi-design-system';
@@ -67,7 +72,7 @@ import { Section } from '@tokyo3rdhq/magi-design-system';
67
72
 
68
73
  `spacing` maps to `--magi-space-12` / `20` / `32` / `40` (xl jumps to `40` on ≥ 768 px viewports).
69
74
 
70
- ### `<Stack>` — flex primitive with semantic gap tokens
75
+ #### `<Stack>` — flex primitive with semantic gap tokens
71
76
 
72
77
  ```tsx
73
78
  import { Stack } from '@tokyo3rdhq/magi-design-system';
@@ -82,7 +87,9 @@ import { Stack } from '@tokyo3rdhq/magi-design-system';
82
87
  // wrap: boolean (default: true)
83
88
  ```
84
89
 
85
- ### `<Button>` — pill action element
90
+ ### UI primitives
91
+
92
+ #### `<Button>` — pill action element
86
93
 
87
94
  ```tsx
88
95
  import { Button } from '@tokyo3rdhq/magi-design-system';
@@ -95,7 +102,7 @@ import { Button } from '@tokyo3rdhq/magi-design-system';
95
102
 
96
103
  All buttons include hover, active, focus-visible, disabled, and loading states. Focus ring uses `--magi-focus-ring`.
97
104
 
98
- ### `<Card>` — dark surface
105
+ #### `<Card>` — dark surface
99
106
 
100
107
  ```tsx
101
108
  import { Card } from '@tokyo3rdhq/magi-design-system';
@@ -107,7 +114,7 @@ import { Card } from '@tokyo3rdhq/magi-design-system';
107
114
 
108
115
  `interactive` adds hover (border lift + surface darken) and active (1 px lift) states.
109
116
 
110
- ### `<Badge>` — compact status / metadata label
117
+ #### `<Badge>` — compact status / metadata label
111
118
 
112
119
  ```tsx
113
120
  import { Badge } from '@tokyo3rdhq/magi-design-system';
@@ -117,22 +124,177 @@ import { Badge } from '@tokyo3rdhq/magi-design-system';
117
124
  // dot: boolean (default: false)
118
125
  ```
119
126
 
120
- ### `<ProductTheme>` — accent override
127
+ #### `<Checkbox>` — restyled native checkbox
128
+
129
+ ```tsx
130
+ import { Checkbox } from '@tokyo3rdhq/magi-design-system';
131
+
132
+ // Uncontrolled with native input attrs:
133
+ <Checkbox aria-label="Accept terms" defaultChecked />
134
+
135
+ // Controlled with label:
136
+ <Checkbox
137
+ checked={providers.includes('nvidia')}
138
+ onChange={(e) => toggle('nvidia', e.target.checked)}
139
+ >
140
+ NVIDIA
141
+ </Checkbox>
142
+ ```
143
+
144
+ Native `<input type="checkbox">` under the hood. `type` prop is locked to `'checkbox'` (not extensible to `'radio'` etc.) via `Omit<InputHTMLAttributes, 'type'>`.
145
+
146
+ #### `<Input>` — `<input>` wrapper
147
+
148
+ ```tsx
149
+ import { Input } from '@tokyo3rdhq/magi-design-system';
150
+
151
+ <Input placeholder="e.g. Coding assistant" />
152
+ <Input type="password" placeholder="••••••••" />
153
+ <Input type="search" placeholder="Search models" />
154
+ <Input invalid defaultValue="bad value" />
155
+ // inputSize: 'sm' | 'md' | 'lg' (default: 'md')
156
+ // invalid: boolean (default: false)
157
+ ```
158
+
159
+ `inputSize` (not `size`) to avoid collision with the native `<input size>` HTML attribute.
160
+
161
+ #### `<FormField>` — label + control + helper / error
162
+
163
+ ```tsx
164
+ import { FormField, Input } from '@tokyo3rdhq/magi-design-system';
165
+
166
+ <FormField label="Email" helper="We'll never share this.">
167
+ <Input type="email" placeholder="you@example.com" />
168
+ </FormField>
169
+
170
+ <FormField label="Max models" error="Pick a value between 1 and 10.">
171
+ <Segmented value="3" options={...} onChange={...} />
172
+ </FormField>
173
+ ```
174
+
175
+ `<FormField>` wires `aria-describedby` to the helper/error span, `aria-labelledby` to
176
+ the label, and `aria-invalid="true"` to the child control — automatically via
177
+ `Children.only + cloneElement`. Works with any single focusable element
178
+ (`Input`, `Select`, native `<input>`, `Segmented`, etc.). For complex
179
+ multi-element layouts, no wiring is performed — the label and helper/error
180
+ still render.
181
+
182
+ #### `<Segmented>` — single-select chip group
183
+
184
+ ```tsx
185
+ import { Segmented } from '@tokyo3rdhq/magi-design-system';
186
+
187
+ <Segmented
188
+ value={contextMin}
189
+ options={[
190
+ { value: '128k', label: '128K+' },
191
+ { value: '32k', label: '32K+' },
192
+ { value: '8k', label: '8K+' },
193
+ { value: 'any', label: 'Any' },
194
+ ]}
195
+ onChange={(v) => setContextMin(v as '128k' | '32k' | '8k' | 'any')}
196
+ />
197
+
198
+ <Segmented value={cost} options={...} onChange={...} accent />
199
+ ```
200
+
201
+ **Single-select only.** For multi-select (e.g. provider toggles, multi-tag pickers),
202
+ use a `<Checkbox>` group instead — a multi-item segmented control implies
203
+ exclusive selection and confuses the interaction model.
204
+
205
+ #### `<Banner>` — inline notice with left-border accent
206
+
207
+ ```tsx
208
+ import { Banner } from '@tokyo3rdhq/magi-design-system';
209
+
210
+ <Banner variant="info">
211
+ No requirements set yet. Go back to specify what you're building.
212
+ </Banner>
213
+ <Banner variant="warning">
214
+ This action will reset all your saved configurations.
215
+ </Banner>
216
+ <Banner variant="error">
217
+ Could not load KV catalog: {error.message}.
218
+ </Banner>
219
+ <Banner variant="success">
220
+ Saved successfully. <a href="#">View changes</a>.
221
+ </Banner>
222
+ // variant: 'warning' | 'error' | 'success' | 'info' (default: 'warning')
223
+ ```
224
+
225
+ `role="alert"` for warning/error, `role="status"` for info/success.
226
+
227
+ #### `<EmptyState>` — centered placeholder
228
+
229
+ ```tsx
230
+ import { EmptyState } from '@tokyo3rdhq/magi-design-system';
231
+
232
+ // Simple:
233
+ <EmptyState>
234
+ No models match. Try loosening the constraints — <Link to="/">edit requirements</Link>.
235
+ </EmptyState>
236
+
237
+ // Structured:
238
+ <EmptyState
239
+ title="Nothing selected"
240
+ description="Pick models on the Browse page first."
241
+ action={<Button variant="primary" onClick={goToBrowse}>Go to Browse</Button>}
242
+ />
243
+ ```
244
+
245
+ ### Theme
246
+
247
+ #### `<AppTheme>` — accent override (0.3.0+: no DOM wrapper)
121
248
 
122
249
  ```tsx
123
- import { ProductTheme } from '@tokyo3rdhq/magi-design-system';
250
+ import { AppTheme } from '@tokyo3rdhq/magi-design-system';
124
251
 
125
- <ProductTheme accent="cyan" name="token-factory">
252
+ <AppTheme accent="cyan" name="token-factory">
126
253
  <App />
127
- </ProductTheme>
128
- // accent: 'green' | 'cyan' | 'violet' | 'amber' | 'white' (default: 'green')
129
- // name: string (optional product identifier)
130
- // tokens: Partial<CSSProperties> (optional additional overrides)
254
+ </AppTheme>
255
+ // accent: 'green' | 'cyan' | 'violet' | 'amber' | 'white' (default: 'green')
256
+ // name: string (optional product identifier)
131
257
  ```
132
258
 
133
- `<ProductTheme>` renders a `<div data-magi-product="…">` and sets the four accent tokens as inline custom properties on the wrapper. Children inherit the override automatically via CSS variable resolution.
259
+ `name` sets `data-magi-product="<name>"` on `<html>` (not on a wrapper div).
260
+ This is the contract for scoped application identification.
261
+
262
+ **Removed in 0.3.0**: `tokens?: Partial<CSSProperties>` prop. Consumers can no
263
+ longer redefine arbitrary `--magi-*` variables through this component. For
264
+ subtree accent overrides, use `<div data-magi-accent="<name>">` instead.
265
+
266
+ #### Subtree accent override via `data-magi-accent`
267
+
268
+ ```tsx
269
+ // In any consumer JSX:
270
+ <div data-magi-accent="danger">
271
+ <Button variant="primary">Delete</Button>
272
+ </div>
273
+
274
+ <div data-magi-accent="warning">
275
+ <Banner variant="warning">…</Banner>
276
+ </div>
277
+ ```
278
+
279
+ Maps to:
280
+ - Accent presets: `green` / `cyan` / `violet` / `amber` / `white`
281
+ - Semantic: `danger` / `warning` / `success`
282
+
283
+ The button / banner / etc. inside reads `--magi-accent` via CSS and picks up
284
+ the override automatically. The mapping is shipped as static CSS in
285
+ `foundation/globals.css`.
134
286
 
135
- **Allowed in `tokens`**: only overrideable tokens (accent family). Refrain from setting `--magi-space-*`, `--magi-text-primary`, or base surfaces — see spec §10.
287
+ #### `useAppTheme()` hook
288
+
289
+ ```tsx
290
+ import { useAppTheme } from '@tokyo3rdhq/magi-design-system';
291
+
292
+ function MyComponent() {
293
+ const { accent } = useAppTheme();
294
+ // Returns { accent: AppAccent } or { accent: 'green' } outside
295
+ // a <AppTheme>.
296
+ }
297
+ ```
136
298
 
137
299
  #### Preset accent palettes
138
300
 
@@ -176,13 +338,33 @@ Every token is a CSS custom property on `:root`. See [`docs/tokens.md`](../../do
176
338
  }
177
339
  ```
178
340
 
341
+ ## CSS cascade model (0.3.0+)
342
+
343
+ `styles/index.css` declares layer order:
344
+
345
+ ```css
346
+ @layer magi.reset, magi.tokens, magi.foundation, magi.layout, magi.components;
347
+ ```
348
+
349
+ Consumer styles (unlayered) always win over our layered rules, regardless of
350
+ import order. This is declared architecture, not accidental source-order.
351
+
179
352
  ## Accessibility
180
353
 
181
354
  - All interactive elements have visible `:focus-visible` rings via `--magi-focus-ring`
182
355
  - `@media (prefers-reduced-motion: reduce)` collapses all motion durations to `0.01ms`
183
- - Buttons: `aria-busy` toggles with `loading`; `disabled` + `aria-disabled` covered
356
+ - `<Button>`: `aria-busy` toggles with `loading`; `disabled` + `aria-disabled` covered
357
+ - `<FormField>`: auto-wires `aria-describedby` / `aria-labelledby` / `aria-invalid` on the child control (0.3.0+)
358
+ - `<Banner>`: `role="alert"` for warning/error; `role="status"` for info/success
359
+ - `<Segmented>`: `role="radiogroup"` + `role="radio"` + `aria-checked`
184
360
  - Color tokens chosen to meet WCAG AA contrast on the dark background
185
361
 
362
+ ## Architecture decisions
363
+
364
+ See [`docs/adr/`](../../docs/adr/) for the 8 ADRs documenting key decisions:
365
+ token architecture, CSS scope, theme architecture, package boundary,
366
+ CSS bundling, React peer range, accessibility, visual regression.
367
+
186
368
  ## Build
187
369
 
188
370
  ```bash
@@ -201,7 +383,7 @@ Per spec §36 / §4:
201
383
  - Animation library
202
384
  - i18n (consumers handle it themselves)
203
385
  - Light theme (package is dark-first per spec §9)
204
- - Navbar, Footer, Tabs, Input, CodeBlock, ProductHeader — Phase 4, only after duplication is observed in 2+ products
386
+ - Navbar, Footer, Tabs, Spinner, Select, CodeBlock, ProductHeader — Phase 4+, only after duplication is observed in 2+ products
205
387
 
206
388
  ## License
207
389
 
@@ -23,6 +23,11 @@ export interface FormFieldProps {
23
23
  * ARIA attributes. Sets `aria-invalid="true"` on the child when `error` is
24
24
  * present.
25
25
  *
26
+ * **Consumer-supplied ARIA attributes are preserved (merged, not overwritten).**
27
+ * For example, if the consumer passes `aria-describedby="external-help"`,
28
+ * the resulting attribute is `"external-help <generated-helper-id>"`.
29
+ * This matches the WAI-ARIA spec for multi-value id lists.
30
+ *
26
31
  * The child MUST be a single focusable element (Input, Select, native
27
32
  * `<input>`, Segmented, etc.). If `children` is not a valid React element,
28
33
  * the ARIA wiring is skipped — the label and helper/error still render.
@@ -1 +1 @@
1
- {"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../../src/components/FormField/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAUf,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,EAAE,SAAS,CAAC;IACpB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAC/C,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,KAAK,EACL,QAAQ,EACR,KAAiB,EACjB,SAAS,EACT,QAAQ,GACT,EAAE,cAAc,+BA2ChB"}
1
+ {"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../../src/components/FormField/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAUf,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,QAAQ,EAAE,SAAS,CAAC;IACpB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;IAC/C,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,MAAM,EACN,KAAK,EACL,QAAQ,EACR,KAAiB,EACjB,SAAS,EACT,QAAQ,GACT,EAAE,cAAc,+BAsDhB"}
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * Container, Section, Stack,
7
7
  * Button, Card, Badge,
8
8
  * Checkbox, FormField, Input, Segmented, Banner, EmptyState,
9
- * ProductTheme,
9
+ * AppTheme,
10
10
  * } from '@tokyo3rdhq/magi-design-system';
11
11
  */
12
12
  import './styles/index.css';
@@ -20,6 +20,6 @@ export * from './components/FormField/index';
20
20
  export * from './components/Segmented/index';
21
21
  export * from './components/Banner/index';
22
22
  export * from './components/EmptyState/index';
23
- export { ProductTheme } from './theme';
24
- export type { ProductThemeProps, ProductAccent } from './theme';
23
+ export { AppTheme } from './theme';
24
+ export type { AppThemeProps, AppAccent } from './theme';
25
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,oBAAoB,CAAC;AAE5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,oBAAoB,CAAC;AAE5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,+BAA+B,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,299 +1,316 @@
1
- import { jsx as o, jsxs as b, Fragment as v } from "react/jsx-runtime";
2
- import { forwardRef as p, useId as y, isValidElement as N, cloneElement as $, useMemo as x, useEffect as k, createContext as C } from "react";
3
- function m(...n) {
1
+ import { jsx as o, jsxs as p, Fragment as y } from "react/jsx-runtime";
2
+ import { forwardRef as v, useId as N, isValidElement as P, cloneElement as $, useMemo as x, useInsertionEffect as C, createContext as k } from "react";
3
+ function l(...n) {
4
4
  return n.filter(Boolean).join(" ");
5
5
  }
6
- function I({
6
+ function E({
7
7
  size: n = "xl",
8
- as: e,
8
+ as: t,
9
9
  className: a,
10
- children: t,
11
- ...i
10
+ children: c,
11
+ ...s
12
12
  }) {
13
- const c = e ?? "div", s = m("magi-container", `magi-container--${n}`, a);
14
- return /* @__PURE__ */ o(c, { className: s, ...i, children: t });
13
+ const e = t ?? "div", i = l("magi-container", `magi-container--${n}`, a);
14
+ return /* @__PURE__ */ o(e, { className: i, ...s, children: c });
15
15
  }
16
- function S({
16
+ function j({
17
17
  spacing: n = "lg",
18
- surface: e = "default",
18
+ surface: t = "default",
19
19
  fullWidth: a = !1,
20
- as: t,
21
- className: i,
22
- children: c,
23
- ...s
20
+ as: c,
21
+ className: s,
22
+ children: e,
23
+ ...i
24
24
  }) {
25
- const r = t ?? "section", d = m(
25
+ const r = c ?? "section", m = l(
26
26
  "magi-section",
27
27
  `magi-section--${n}`,
28
- `magi-section--${e}`,
28
+ `magi-section--${t}`,
29
29
  a && "magi-section--fullWidth",
30
- i
30
+ s
31
31
  );
32
- return /* @__PURE__ */ o(r, { className: d, ...s, children: c });
32
+ return /* @__PURE__ */ o(r, { className: m, ...i, children: e });
33
33
  }
34
34
  function T({
35
35
  direction: n = "column",
36
- gap: e = "4",
36
+ gap: t = "4",
37
37
  align: a = "stretch",
38
- wrap: t = !0,
39
- as: i,
40
- className: c,
41
- style: s,
38
+ wrap: c = !0,
39
+ as: s,
40
+ className: e,
41
+ style: i,
42
42
  children: r,
43
- ...d
43
+ ...m
44
44
  }) {
45
- const g = i ?? "div", l = n === "row" && !t ? { flexWrap: "nowrap" } : void 0, u = m(
45
+ const g = s ?? "div", d = n === "row" && !c ? { flexWrap: "nowrap" } : void 0, u = l(
46
46
  "magi-stack",
47
47
  n === "row" && "magi-stack--row",
48
- `magi-stack--gap-${e}`,
48
+ `magi-stack--gap-${t}`,
49
49
  `magi-stack--align-${a}`,
50
- c
50
+ e
51
51
  );
52
- return /* @__PURE__ */ o(g, { className: u, style: { ...l, ...s }, ...d, children: r });
52
+ return /* @__PURE__ */ o(g, { className: u, style: { ...d, ...i }, ...m, children: r });
53
53
  }
54
- const j = p(
54
+ const V = v(
55
55
  function({
56
- variant: e = "primary",
56
+ variant: t = "primary",
57
57
  size: a = "md",
58
- loading: t = !1,
59
- disabled: i,
60
- className: c,
61
- children: s,
58
+ loading: c = !1,
59
+ disabled: s,
60
+ className: e,
61
+ children: i,
62
62
  type: r,
63
- ...d
63
+ ...m
64
64
  }, g) {
65
- const l = m(
65
+ const d = l(
66
66
  "magi-button",
67
- `magi-button--${e}`,
67
+ `magi-button--${t}`,
68
68
  `magi-button--${a}`,
69
- t && "magi-button--loading",
70
- c
69
+ c && "magi-button--loading",
70
+ e
71
71
  );
72
72
  return /* @__PURE__ */ o(
73
73
  "button",
74
74
  {
75
75
  ref: g,
76
- className: l,
76
+ className: d,
77
77
  type: r ?? "button",
78
- disabled: i || t,
79
- "aria-busy": t || void 0,
80
- ...d,
81
- children: s
78
+ disabled: s || c,
79
+ "aria-busy": c || void 0,
80
+ ...m,
81
+ children: i
82
82
  }
83
83
  );
84
84
  }
85
- ), F = p(function({ variant: e = "default", padding: a = "md", className: t, children: i, ...c }, s) {
86
- const r = m(
85
+ ), A = v(function({ variant: t = "default", padding: a = "md", className: c, children: s, ...e }, i) {
86
+ const r = l(
87
87
  "magi-card",
88
- `magi-card--${e}`,
88
+ `magi-card--${t}`,
89
89
  `magi-card--padding-${a}`,
90
- t
90
+ c
91
91
  );
92
- return /* @__PURE__ */ o("div", { ref: s, className: r, ...c, children: i });
93
- }), _ = p(function({ variant: e = "neutral", dot: a = !1, className: t, children: i, ...c }, s) {
94
- const r = m(
92
+ return /* @__PURE__ */ o("div", { ref: i, className: r, ...e, children: s });
93
+ }), F = v(function({ variant: t = "neutral", dot: a = !1, className: c, children: s, ...e }, i) {
94
+ const r = l(
95
95
  "magi-badge",
96
- `magi-badge--${e}`,
97
- t
96
+ `magi-badge--${t}`,
97
+ c
98
98
  );
99
- return /* @__PURE__ */ b("span", { ref: s, className: r, ...c, children: [
99
+ return /* @__PURE__ */ p("span", { ref: i, className: r, ...e, children: [
100
100
  a ? /* @__PURE__ */ o("span", { className: "magi-badge__dot", "aria-hidden": "true" }) : null,
101
- i
101
+ s
102
102
  ] });
103
- }), M = p(
103
+ }), R = v(
104
104
  function({
105
- children: e,
105
+ children: t,
106
106
  labelPosition: a = "right",
107
- className: t,
108
- disabled: i,
109
- ...c
110
- }, s) {
107
+ className: c,
108
+ disabled: s,
109
+ ...e
110
+ }, i) {
111
111
  const r = /* @__PURE__ */ o(
112
112
  "input",
113
113
  {
114
- ref: s,
114
+ ref: i,
115
115
  type: "checkbox",
116
- className: m("magi-checkbox", t),
117
- disabled: i,
118
- ...c
116
+ className: l("magi-checkbox", c),
117
+ disabled: s,
118
+ ...e
119
119
  }
120
120
  );
121
- return e == null ? r : /* @__PURE__ */ o(
121
+ return t == null ? r : /* @__PURE__ */ o(
122
122
  "label",
123
123
  {
124
- className: m(
124
+ className: l(
125
125
  "magi-checkbox-label",
126
- i && "magi-checkbox-label--disabled"
126
+ s && "magi-checkbox-label--disabled"
127
127
  ),
128
- children: a === "left" ? /* @__PURE__ */ b(v, { children: [
129
- /* @__PURE__ */ o("span", { children: e }),
128
+ children: a === "left" ? /* @__PURE__ */ p(y, { children: [
129
+ /* @__PURE__ */ o("span", { children: t }),
130
130
  r
131
- ] }) : /* @__PURE__ */ b(v, { children: [
131
+ ] }) : /* @__PURE__ */ p(y, { children: [
132
132
  r,
133
- /* @__PURE__ */ o("span", { children: e })
133
+ /* @__PURE__ */ o("span", { children: t })
134
134
  ] })
135
135
  }
136
136
  );
137
137
  }
138
- ), R = p(function({ inputSize: e = "md", invalid: a = !1, className: t, ...i }, c) {
139
- const s = m(
138
+ ), _ = v(function({ inputSize: t = "md", invalid: a = !1, className: c, ...s }, e) {
139
+ const i = l(
140
140
  "magi-input",
141
- `magi-input--${e}`,
141
+ `magi-input--${t}`,
142
142
  a && "magi-input--invalid",
143
- t
143
+ c
144
144
  );
145
145
  return /* @__PURE__ */ o(
146
146
  "input",
147
147
  {
148
- ref: c,
149
- className: s,
148
+ ref: e,
149
+ className: i,
150
150
  "aria-invalid": a || void 0,
151
- ...i
151
+ ...s
152
152
  }
153
153
  );
154
154
  });
155
- function W({
155
+ function h(...n) {
156
+ const t = n.filter(
157
+ (a) => typeof a == "string" && a.length > 0
158
+ );
159
+ return t.length > 0 ? t.join(" ") : void 0;
160
+ }
161
+ function H({
156
162
  label: n,
157
- helper: e,
163
+ helper: t,
158
164
  error: a,
159
- children: t,
160
- align: i = "stretch",
161
- className: c,
162
- helperId: s
165
+ children: c,
166
+ align: s = "stretch",
167
+ className: e,
168
+ helperId: i
163
169
  }) {
164
- const d = `magi-field-${y()}`, g = `${d}-label`, l = s ?? `${d}-helper`, u = !!(a || e);
165
- let f = t;
166
- if (N(t)) {
167
- const h = t.props;
168
- f = $(t, {
169
- id: h.id ?? d,
170
- "aria-describedby": u ? l : void 0,
171
- "aria-invalid": a ? !0 : void 0,
172
- "aria-labelledby": g
170
+ const m = `magi-field-${N()}`, g = `${m}-label`, d = i ?? `${m}-helper`, u = !!(a || t);
171
+ let b = c;
172
+ if (P(c)) {
173
+ const f = c.props;
174
+ b = $(c, {
175
+ id: f.id ?? m,
176
+ // MERGE with consumer-supplied IDs. The order is: consumer's first,
177
+ // then FormField's. This matches ARIA's "consumer-supplied IDs win
178
+ // for ordering" intuition — FormField's helpers are appended.
179
+ "aria-describedby": h(
180
+ f["aria-describedby"],
181
+ u ? d : void 0
182
+ ),
183
+ "aria-labelledby": h(
184
+ f["aria-labelledby"],
185
+ g
186
+ ),
187
+ // aria-invalid: when FormField has an error, force true (overriding
188
+ // consumer). When no error, preserve consumer's setting.
189
+ "aria-invalid": a ? !0 : f["aria-invalid"] ?? void 0
173
190
  });
174
191
  }
175
- return /* @__PURE__ */ b(
192
+ return /* @__PURE__ */ p(
176
193
  "div",
177
194
  {
178
- className: m(
195
+ className: l(
179
196
  "magi-field",
180
- `magi-field--align-${i}`,
181
- c
197
+ `magi-field--align-${s}`,
198
+ e
182
199
  ),
183
200
  children: [
184
201
  /* @__PURE__ */ o("label", { id: g, className: "magi-field-label", children: n }),
185
- f,
202
+ b,
186
203
  u && /* @__PURE__ */ o(
187
204
  "span",
188
205
  {
189
- id: l,
190
- className: m(
206
+ id: d,
207
+ className: l(
191
208
  "magi-field-helper",
192
209
  a && "magi-field-helper--error"
193
210
  ),
194
- children: a || e
211
+ children: a || t
195
212
  }
196
213
  )
197
214
  ]
198
215
  }
199
216
  );
200
217
  }
201
- function A({
218
+ function M({
202
219
  value: n,
203
- options: e,
220
+ options: t,
204
221
  onChange: a,
205
- accent: t = !1,
206
- size: i = "md",
207
- disabled: c = !1,
208
- id: s,
222
+ accent: c = !1,
223
+ size: s = "md",
224
+ disabled: e = !1,
225
+ id: i,
209
226
  className: r,
210
- "aria-label": d,
227
+ "aria-label": m,
211
228
  children: g
212
229
  }) {
213
230
  return /* @__PURE__ */ o(
214
231
  "div",
215
232
  {
216
233
  role: "radiogroup",
217
- id: s,
218
- "aria-label": d,
219
- className: m(
234
+ id: i,
235
+ "aria-label": m,
236
+ className: l(
220
237
  "magi-segmented",
221
- `magi-segmented--${i}`,
222
- c && "magi-segmented--disabled",
238
+ `magi-segmented--${s}`,
239
+ e && "magi-segmented--disabled",
223
240
  r
224
241
  ),
225
- children: e.map((l) => {
226
- const u = l.value === n, f = !!(c || l.disabled), h = () => {
227
- f || u || a(l.value);
242
+ children: t.map((d) => {
243
+ const u = d.value === n, b = !!(e || d.disabled), f = () => {
244
+ b || u || a(d.value);
228
245
  };
229
- return g ? /* @__PURE__ */ o("div", { children: g(l, { active: u, disabled: f, onSelect: h }) }, l.value) : /* @__PURE__ */ o(
246
+ return g ? /* @__PURE__ */ o("div", { children: g(d, { active: u, disabled: b, onSelect: f }) }, d.value) : /* @__PURE__ */ o(
230
247
  "button",
231
248
  {
232
249
  type: "button",
233
250
  role: "radio",
234
251
  "aria-checked": u,
235
- disabled: f,
236
- onClick: h,
237
- className: m(
252
+ disabled: b,
253
+ onClick: f,
254
+ className: l(
238
255
  "magi-segmented-item",
239
256
  u && "magi-segmented-item--active",
240
- t && "magi-segmented-item--accent"
257
+ c && "magi-segmented-item--accent"
241
258
  ),
242
- children: l.label
259
+ children: d.label
243
260
  },
244
- l.value
261
+ d.value
245
262
  );
246
263
  })
247
264
  }
248
265
  );
249
266
  }
250
- function D({
267
+ function W({
251
268
  variant: n = "warning",
252
- className: e,
269
+ className: t,
253
270
  children: a,
254
- ...t
271
+ ...c
255
272
  }) {
256
273
  return /* @__PURE__ */ o(
257
274
  "div",
258
275
  {
259
276
  role: n === "error" || n === "warning" ? "alert" : "status",
260
- className: m(
277
+ className: l(
261
278
  "magi-banner",
262
279
  `magi-banner--${n}`,
263
- e
280
+ t
264
281
  ),
265
- ...t,
282
+ ...c,
266
283
  children: a
267
284
  }
268
285
  );
269
286
  }
270
- function V({
287
+ function D({
271
288
  title: n,
272
- description: e,
289
+ description: t,
273
290
  action: a,
274
- children: t,
275
- className: i,
276
- ...c
291
+ children: c,
292
+ className: s,
293
+ ...e
277
294
  }) {
278
- const s = !!(n || e || a), r = e ?? t;
295
+ const i = !!(n || t || a), r = t ?? c;
279
296
  return /* @__PURE__ */ o(
280
297
  "div",
281
298
  {
282
- className: m(
299
+ className: l(
283
300
  "magi-empty",
284
- s && "magi-empty--structured",
285
- i
301
+ i && "magi-empty--structured",
302
+ s
286
303
  ),
287
- ...c,
288
- children: s ? /* @__PURE__ */ b(v, { children: [
304
+ ...e,
305
+ children: i ? /* @__PURE__ */ p(y, { children: [
289
306
  n ? /* @__PURE__ */ o("p", { className: "magi-empty-title", children: n }) : null,
290
307
  r != null ? /* @__PURE__ */ o("p", { className: "magi-empty-description", children: r }) : null,
291
308
  a ? /* @__PURE__ */ o("div", { className: "magi-empty-action", children: a }) : null
292
- ] }) : t
309
+ ] }) : c
293
310
  }
294
311
  );
295
312
  }
296
- const P = {
313
+ const I = {
297
314
  green: {
298
315
  "--magi-accent": "#00c853",
299
316
  "--magi-accent-hover": "#00e676",
@@ -324,32 +341,43 @@ const P = {
324
341
  "--magi-accent-soft": "rgba(255, 255, 255, 0.08)",
325
342
  "--magi-accent-contrast": "#050505"
326
343
  }
327
- }, w = C(null);
328
- function q({
344
+ }, S = k(null);
345
+ function O({
329
346
  accent: n = "green",
330
- name: e,
347
+ name: t,
331
348
  children: a
332
349
  }) {
333
- const t = x(() => ({ accent: n }), [n]);
334
- return k(() => {
350
+ const c = x(() => ({ accent: n }), [n]);
351
+ return C(() => {
335
352
  if (typeof document > "u") return;
336
- const i = P[n], c = document.documentElement;
337
- c.style.setProperty("--magi-accent", i["--magi-accent"]), c.style.setProperty("--magi-accent-hover", i["--magi-accent-hover"]), c.style.setProperty("--magi-accent-soft", i["--magi-accent-soft"]), c.style.setProperty("--magi-accent-contrast", i["--magi-accent-contrast"]), e ? c.dataset.magiProduct = e : delete c.dataset.magiProduct;
338
- }, [n, e]), /* @__PURE__ */ o(w.Provider, { value: t, children: a });
353
+ const s = I[n], e = document.documentElement, i = {
354
+ accent: e.style.getPropertyValue("--magi-accent"),
355
+ accentHover: e.style.getPropertyValue("--magi-accent-hover"),
356
+ accentSoft: e.style.getPropertyValue("--magi-accent-soft"),
357
+ accentContrast: e.style.getPropertyValue("--magi-accent-contrast"),
358
+ product: e.dataset.magiProduct
359
+ };
360
+ return e.style.setProperty("--magi-accent", s["--magi-accent"]), e.style.setProperty("--magi-accent-hover", s["--magi-accent-hover"]), e.style.setProperty("--magi-accent-soft", s["--magi-accent-soft"]), e.style.setProperty("--magi-accent-contrast", s["--magi-accent-contrast"]), t ? e.dataset.magiProduct = t : delete e.dataset.magiProduct, () => {
361
+ const r = (m, g) => {
362
+ g ? e.style.setProperty(m, g) : e.style.removeProperty(m);
363
+ };
364
+ r("--magi-accent", i.accent), r("--magi-accent-hover", i.accentHover), r("--magi-accent-soft", i.accentSoft), r("--magi-accent-contrast", i.accentContrast), i.product !== void 0 ? e.dataset.magiProduct = i.product : e.dataset.magiProduct !== void 0 && delete e.dataset.magiProduct;
365
+ };
366
+ }, [n, t]), /* @__PURE__ */ o(S.Provider, { value: c, children: a });
339
367
  }
340
368
  export {
341
- _ as Badge,
342
- D as Banner,
343
- j as Button,
344
- F as Card,
345
- M as Checkbox,
346
- I as Container,
347
- V as EmptyState,
348
- W as FormField,
349
- R as Input,
350
- q as ProductTheme,
351
- S as Section,
352
- A as Segmented,
369
+ O as AppTheme,
370
+ F as Badge,
371
+ W as Banner,
372
+ V as Button,
373
+ A as Card,
374
+ R as Checkbox,
375
+ E as Container,
376
+ D as EmptyState,
377
+ H as FormField,
378
+ _ as Input,
379
+ j as Section,
380
+ M as Segmented,
353
381
  T as Stack
354
382
  };
355
383
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/utils/classnames.ts","../src/layout/Container.tsx","../src/layout/Section.tsx","../src/layout/Stack.tsx","../src/components/Button/Button.tsx","../src/components/Card/Card.tsx","../src/components/Badge/Badge.tsx","../src/components/Checkbox/Checkbox.tsx","../src/components/Input/Input.tsx","../src/components/FormField/FormField.tsx","../src/components/Segmented/Segmented.tsx","../src/components/Banner/Banner.tsx","../src/components/EmptyState/EmptyState.tsx","../src/theme.tsx"],"sourcesContent":["/** Join truthy class strings with a space. */\nexport function cx(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(' ');\n}","import type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../utils/classnames';\n\nexport type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'wide' | 'full';\n\nexport interface ContainerProps extends HTMLAttributes<HTMLElement> {\n /** Maximum width at desktop. Default: `xl` (1200px). */\n size?: ContainerSize;\n /** Override the rendered tag. Default: `div`. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Container — centers content and constrains max-width.\n *\n * @example\n * <Container size=\"lg\">...</Container>\n */\nexport function Container({\n size = 'xl',\n as,\n className,\n children,\n ...rest\n}: ContainerProps) {\n const Component = (as ?? 'div') as ElementType;\n const cls = cx('magi-container', `magi-container--${size}`, className);\n return (\n <Component className={cls} {...rest}>\n {children}\n </Component>\n );\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../utils/classnames';\n\nexport type SectionSpacing = 'sm' | 'md' | 'lg' | 'xl';\nexport type SectionSurface = 'default' | 'raised' | 'elevated';\n\nexport interface SectionProps extends HTMLAttributes<HTMLElement> {\n /** Vertical padding scale. Default: `lg`. */\n spacing?: SectionSpacing;\n /** Background surface. Default: `default` (transparent). */\n surface?: SectionSurface;\n /** Drop the inner Container padding. Useful for full-bleed content. */\n fullWidth?: boolean;\n /** Override the rendered tag. Default: `section`. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Section — page-level vertical rhythm primitive.\n *\n * @example\n * <Section spacing=\"xl\" surface=\"default\">...</Section>\n */\nexport function Section({\n spacing = 'lg',\n surface = 'default',\n fullWidth = false,\n as,\n className,\n children,\n ...rest\n}: SectionProps) {\n const Component = (as ?? 'section') as ElementType;\n const cls = cx(\n 'magi-section',\n `magi-section--${spacing}`,\n `magi-section--${surface}`,\n fullWidth && 'magi-section--fullWidth',\n className,\n );\n return (\n <Component className={cls} {...rest}>\n {children}\n </Component>\n );\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../utils/classnames';\n\nexport type StackGap =\n | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '8' | '10' | '12' | '16';\nexport type StackAlign = 'start' | 'center' | 'end' | 'stretch';\n\nexport interface StackProps extends HTMLAttributes<HTMLElement> {\n /** Layout direction. Default: vertical (column). */\n direction?: 'row' | 'column';\n /** Gap token. Default: `4` (16px). */\n gap?: StackGap;\n /** Cross-axis alignment. Default: `stretch`. */\n align?: StackAlign;\n /** Allow wrapping when `direction=\"row\"`. Default: true. */\n wrap?: boolean;\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Stack — vertical or horizontal flex primitive with semantic gap tokens.\n *\n * @example\n * <Stack gap=\"6\" align=\"center\">...</Stack>\n * <Stack direction=\"row\" gap=\"3\">...</Stack>\n */\nexport function Stack({\n direction = 'column',\n gap = '4',\n align = 'stretch',\n wrap = true,\n as,\n className,\n style,\n children,\n ...rest\n}: StackProps) {\n const Component = (as ?? 'div') as ElementType;\n const wrapStyle =\n direction === 'row' && !wrap ? { flexWrap: 'nowrap' as const } : undefined;\n const cls = cx(\n 'magi-stack',\n direction === 'row' && 'magi-stack--row',\n `magi-stack--gap-${gap}`,\n `magi-stack--align-${align}`,\n className,\n );\n\n return (\n <Component className={cls} style={{ ...wrapStyle, ...style }} {...rest}>\n {children}\n </Component>\n );\n}\n","import { forwardRef, type ButtonHTMLAttributes } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';\nexport type ButtonSize = 'sm' | 'md' | 'lg';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style. Default: `primary`. */\n variant?: ButtonVariant;\n /** Height + padding tier. Default: `md`. */\n size?: ButtonSize;\n /** Show a spinner and disable interaction. */\n loading?: boolean;\n}\n\n/**\n * Button — the canonical MAGI action element.\n *\n * Pill-shaped, four variants (primary/secondary/ghost/danger), three sizes.\n * Use `primary` for the dominant action on a page; `secondary` for supporting\n * actions; `ghost` for inline/tertiary; `danger` for destructive intent.\n *\n * @example\n * <Button variant=\"primary\" onClick={...}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n function Button(\n {\n variant = 'primary',\n size = 'md',\n loading = false,\n disabled,\n className,\n children,\n type,\n ...rest\n },\n ref,\n ) {\n const cls = cx(\n 'magi-button',\n `magi-button--${variant}`,\n `magi-button--${size}`,\n loading && 'magi-button--loading',\n className,\n );\n\n return (\n <button\n ref={ref}\n className={cls}\n type={type ?? 'button'}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...rest}\n >\n {children}\n </button>\n );\n },\n);\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type CardVariant = 'default' | 'elevated' | 'interactive';\nexport type CardPadding = 'none' | 'sm' | 'md' | 'lg';\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n /** Visual emphasis. Default: `default`. `interactive` adds hover state. */\n variant?: CardVariant;\n /** Inner padding. Default: `md`. */\n padding?: CardPadding;\n children?: ReactNode;\n}\n\n/**\n * Card — minimal dark surface with subtle border. Three variants:\n * default (flat), elevated (more contrast), interactive (hover state).\n *\n * @example\n * <Card variant=\"elevated\">...</Card>\n * <Card variant=\"interactive\" onClick={...}>...</Card>\n */\nexport const Card = forwardRef<HTMLDivElement, CardProps>(function Card(\n { variant = 'default', padding = 'md', className, children, ...rest },\n ref,\n) {\n const cls = cx(\n 'magi-card',\n `magi-card--${variant}`,\n `magi-card--padding-${padding}`,\n className,\n );\n return (\n <div ref={ref} className={cls} {...rest}>\n {children}\n </div>\n );\n});\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type BadgeVariant = 'neutral' | 'accent' | 'success' | 'warning' | 'error';\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Color/meaning. Default: `neutral`. */\n variant?: BadgeVariant;\n /** Show a leading colored dot. */\n dot?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Badge — compact status / metadata label.\n *\n * @example\n * <Badge variant=\"accent\">128K context</Badge>\n * <Badge variant=\"success\" dot>Online</Badge>\n */\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { variant = 'neutral', dot = false, className, children, ...rest },\n ref,\n) {\n const cls = cx(\n 'magi-badge',\n `magi-badge--${variant}`,\n className,\n );\n return (\n <span ref={ref} className={cls} {...rest}>\n {dot ? <span className=\"magi-badge__dot\" aria-hidden=\"true\" /> : null}\n {children}\n </span>\n );\n});\n","import {\n forwardRef,\n type InputHTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type CheckboxLabelPosition = 'right' | 'left';\n\n/**\n * Omit `type` from native input attrs — this component is always\n * `type=\"checkbox\"` under the hood. Letting `type` pass through would\n * let consumers render e.g. `type=\"radio\"` with checkbox styles.\n */\nexport interface CheckboxProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {\n /** Optional label rendered next to the checkbox. */\n children?: ReactNode;\n /** Position of the label relative to the checkbox. Default: 'right'. */\n labelPosition?: CheckboxLabelPosition;\n}\n\n/**\n * Checkbox — restyled native checkbox for the MAGI dark theme.\n *\n * Uses a native `<input type=\"checkbox\">` under the hood for full form\n * integration and screen-reader compatibility. The CSS only restyles\n * the visible state.\n *\n * @example\n * // Uncontrolled:\n * <Checkbox aria-label=\"Accept terms\" defaultChecked />\n *\n * // Controlled with label:\n * <Checkbox\n * checked={providers.includes('nvidia')}\n * onChange={(e) => toggle('nvidia', e.target.checked)}\n * >\n * NVIDIA\n * </Checkbox>\n */\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(\n function Checkbox(\n {\n children,\n labelPosition = 'right',\n className,\n disabled,\n ...rest\n },\n ref,\n ) {\n const input = (\n <input\n ref={ref}\n type=\"checkbox\"\n className={cx('magi-checkbox', className)}\n disabled={disabled}\n {...rest}\n />\n );\n\n if (children === undefined || children === null) {\n return input;\n }\n\n return (\n <label\n className={cx(\n 'magi-checkbox-label',\n disabled && 'magi-checkbox-label--disabled',\n )}\n >\n {labelPosition === 'left' ? (\n <>\n <span>{children}</span>\n {input}\n </>\n ) : (\n <>\n {input}\n <span>{children}</span>\n </>\n )}\n </label>\n );\n },\n);","import { forwardRef, type InputHTMLAttributes } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type InputSize = 'sm' | 'md' | 'lg';\n\nexport interface InputProps extends InputHTMLAttributes<HTMLInputElement> {\n /** Visual size. Default: `md`. */\n inputSize?: InputSize;\n /** Error state — adds error border + aria-invalid. */\n invalid?: boolean;\n}\n\n/**\n * Input — `<input>` styled for the MAGI dark theme.\n *\n * Wraps the native element directly. No custom state management — value /\n * onChange / defaultValue / ref all pass through.\n *\n * @example\n * <Input placeholder=\"e.g. Coding assistant\" />\n * <Input type=\"password\" />\n * <Input invalid defaultValue=\"bad value\" />\n */\nexport const Input = forwardRef<HTMLInputElement, InputProps>(function Input(\n { inputSize = 'md', invalid = false, className, ...rest },\n ref,\n) {\n const cls = cx(\n 'magi-input',\n `magi-input--${inputSize}`,\n invalid && 'magi-input--invalid',\n className,\n );\n\n return (\n <input\n ref={ref}\n className={cls}\n aria-invalid={invalid || undefined}\n {...rest}\n />\n );\n});","import {\n Children,\n cloneElement,\n isValidElement,\n useId,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { cx } from '../../utils/classnames';\n\ntype ChildProps = {\n id?: string;\n 'aria-describedby'?: string;\n 'aria-invalid'?: boolean | 'grammar' | 'spelling' | 'false' | 'true';\n 'aria-labelledby'?: string;\n};\n\nexport interface FormFieldProps {\n /** Visible label above the control. Required. */\n label: string;\n /** Optional helper text below the control. */\n helper?: string;\n /** Error message — overrides helper styling + sets aria-invalid + aria-describedby. */\n error?: string;\n /** The control itself (Input, Select, Segmented, etc.). */\n children: ReactNode;\n /** Stack alignment override. Default: stretch. */\n align?: 'start' | 'center' | 'end' | 'stretch';\n /** Extra className for the wrapper. */\n className?: string;\n /** Optional id for the helper/error element (overrides the auto-generated one). */\n helperId?: string;\n}\n\n/**\n * FormField — label + control + optional helper/error wrapper.\n *\n * Wires `aria-describedby` to the helper/error span and `aria-labelledby`\n * to the label, by cloning the single child element with the appropriate\n * ARIA attributes. Sets `aria-invalid=\"true\"` on the child when `error` is\n * present.\n *\n * The child MUST be a single focusable element (Input, Select, native\n * `<input>`, Segmented, etc.). If `children` is not a valid React element,\n * the ARIA wiring is skipped — the label and helper/error still render.\n *\n * @example\n * <FormField label=\"Email\" helper=\"We'll never share this.\">\n * <Input type=\"email\" placeholder=\"you@example.com\" />\n * </FormField>\n *\n * <FormField label=\"Max models\" error=\"Pick a value between 1 and 10.\">\n * <Segmented value=\"3\" options={...} onChange={...} />\n * </FormField>\n */\nexport function FormField({\n label,\n helper,\n error,\n children,\n align = 'stretch',\n className,\n helperId,\n}: FormFieldProps) {\n const reactId = useId();\n const fieldId = `magi-field-${reactId}`;\n const labelId = `${fieldId}-label`;\n const describedById = helperId ?? `${fieldId}-helper`;\n const hasMessage = Boolean(error || helper);\n\n let enhanced: ReactNode = children;\n if (isValidElement<ChildProps>(children)) {\n const existing = children.props;\n enhanced = cloneElement(children, {\n id: existing.id ?? fieldId,\n 'aria-describedby': hasMessage ? describedById : undefined,\n 'aria-invalid': error ? true : undefined,\n 'aria-labelledby': labelId,\n });\n }\n\n return (\n <div\n className={cx(\n 'magi-field',\n `magi-field--align-${align}`,\n className,\n )}\n >\n <label id={labelId} className=\"magi-field-label\">\n {label}\n </label>\n {enhanced}\n {hasMessage && (\n <span\n id={describedById}\n className={cx(\n 'magi-field-helper',\n error && 'magi-field-helper--error',\n )}\n >\n {error || helper}\n </span>\n )}\n </div>\n );\n}","import type { ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type SegmentedSize = 'sm' | 'md' | 'lg';\n\nexport interface SegmentedOption<V extends string = string> {\n value: V;\n label: string;\n /** Optional: disable this individual option. */\n disabled?: boolean;\n}\n\nexport interface SegmentedProps<V extends string = string> {\n /** Currently selected option value. */\n value: V;\n /** Options to display. */\n options: SegmentedOption<V>[];\n /** Called when the user picks a different option. */\n onChange: (value: V) => void;\n /** Use accent color for the active state (cyan for tfi, etc.). Default: false. */\n accent?: boolean;\n /** Visual size. Default: `md`. */\n size?: SegmentedSize;\n /** Disable the whole control. */\n disabled?: boolean;\n /** Group label for screen readers. */\n 'aria-label'?: string;\n /** Optional id for the group. */\n id?: string;\n /** Extra className for the wrapper. */\n className?: string;\n /** Render-prop variant — render function receives each option and its state. */\n children?: (option: SegmentedOption<V>, state: {\n active: boolean;\n disabled: boolean;\n onSelect: () => void;\n }) => ReactNode;\n}\n\n/**\n * Segmented — pill-shaped single-select chip group.\n *\n * **SINGLE-SELECT ONLY.** For multi-select (e.g. provider toggles, multi-tag\n * pickers), use a `Checkbox` group instead — a multi-item segmented control\n * implies exclusive selection and confuses the interaction model.\n *\n * @example\n * <Segmented\n * value={contextMin}\n * options={[\n * { value: '128k', label: '128K+' },\n * { value: '32k', label: '32K+' },\n * { value: '8k', label: '8K+' },\n * { value: 'any', label: 'Any' },\n * ]}\n * onChange={(v) => setContextMin(v as ContextMin)}\n * />\n *\n * <Segmented value={cost} options={[...] onChange={...} accent />\n */\nexport function Segmented<V extends string = string>({\n value,\n options,\n onChange,\n accent = false,\n size = 'md',\n disabled = false,\n id,\n className,\n 'aria-label': ariaLabel,\n children,\n}: SegmentedProps<V>) {\n return (\n <div\n role=\"radiogroup\"\n id={id}\n aria-label={ariaLabel}\n className={cx(\n 'magi-segmented',\n `magi-segmented--${size}`,\n disabled && 'magi-segmented--disabled',\n className,\n )}\n >\n {options.map((opt) => {\n const active = opt.value === value;\n const isDisabled = !!(disabled || opt.disabled);\n const onSelect = () => {\n if (isDisabled || active) return;\n onChange(opt.value);\n };\n\n if (children) {\n return (\n <div key={opt.value}>\n {children(opt, { active, disabled: isDisabled, onSelect })}\n </div>\n );\n }\n\n return (\n <button\n key={opt.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n disabled={isDisabled}\n onClick={onSelect}\n className={cx(\n 'magi-segmented-item',\n active && 'magi-segmented-item--active',\n accent && 'magi-segmented-item--accent',\n )}\n >\n {opt.label}\n </button>\n );\n })}\n </div>\n );\n}","import type { HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type BannerVariant = 'warning' | 'error' | 'success' | 'info';\n\nexport interface BannerProps extends HTMLAttributes<HTMLDivElement> {\n /** Semantic variant. Default: `warning`. */\n variant?: BannerVariant;\n /** Banner content. Inline elements (links, code) are allowed. */\n children: ReactNode;\n}\n\n/**\n * Banner — inline notice with a left-border accent.\n *\n * Use for hints, warnings, errors, and confirmations. Persists until the\n * condition resolves; no dismiss by default.\n *\n * - `warning` (default) — yellow accent, actionable risk\n * - `error` — red accent, blocker\n * - `success` — green accent, confirmation\n * - `info` — cyan accent, neutral hint (uses product accent)\n *\n * @example\n * <Banner variant=\"info\">No requirements set. <a href=\"/\">Go back</a>.</Banner>\n * <Banner variant=\"error\">Could not load KV catalog: {error.message}.</Banner>\n * <Banner variant=\"success\">Saved.</Banner>\n */\nexport function Banner({\n variant = 'warning',\n className,\n children,\n ...rest\n}: BannerProps) {\n return (\n <div\n role={variant === 'error' || variant === 'warning' ? 'alert' : 'status'}\n className={cx(\n 'magi-banner',\n `magi-banner--${variant}`,\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n );\n}","import type { HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> {\n /** Optional bold title above the description (structured mode). */\n title?: string;\n /** Optional description text — used in structured mode. Defaults to children. */\n description?: ReactNode;\n /** Optional primary call-to-action (e.g. a Button or Link). */\n action?: ReactNode;\n /** Body content. Required in simple mode (when no `description`).\n * In structured mode it is shown only if `description` is not provided. */\n children?: ReactNode;\n}\n\n/**\n * EmptyState — centered placeholder for \"no data yet\" cases.\n *\n * Two patterns:\n *\n * @example Simple (string + inline elements):\n * <EmptyState>\n * No models match. Try loosening the constraints — <Link to=\"/\">edit requirements</Link>.\n * </EmptyState>\n *\n * @example Structured (title + description + action):\n * <EmptyState\n * title=\"Nothing selected\"\n * description=\"Pick models on the Browse page first.\"\n * action={<Button variant=\"primary\">Go to Browse</Button>}\n * />\n */\nexport function EmptyState({\n title,\n description,\n action,\n children,\n className,\n ...rest\n}: EmptyStateProps) {\n const structured = Boolean(title || description || action);\n const body = description ?? children;\n\n return (\n <div\n className={cx(\n 'magi-empty',\n structured && 'magi-empty--structured',\n className,\n )}\n {...rest}\n >\n {structured ? (\n <>\n {title ? <p className=\"magi-empty-title\">{title}</p> : null}\n {body !== undefined && body !== null ? (\n <p className=\"magi-empty-description\">{body}</p>\n ) : null}\n {action ? <div className=\"magi-empty-action\">{action}</div> : null}\n </>\n ) : (\n children\n )}\n </div>\n );\n}","import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n type ReactNode,\n} from 'react';\n\nexport type ProductAccent =\n | 'green' // default — MAGI core\n | 'cyan' // Token Factory Initializr\n | 'violet' // API product\n | 'amber' // Agent product\n | 'white'; // monochrome MAGI\n\ninterface AccentTokens {\n '--magi-accent': string;\n '--magi-accent-hover': string;\n '--magi-accent-soft': string;\n '--magi-accent-contrast': string;\n}\n\nconst ACCENT_PRESETS: Record<ProductAccent, AccentTokens> = {\n green: {\n '--magi-accent': '#00c853',\n '--magi-accent-hover': '#00e676',\n '--magi-accent-soft': 'rgba(0, 200, 83, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n cyan: {\n '--magi-accent': '#38bdf8',\n '--magi-accent-hover': '#7dd3fc',\n '--magi-accent-soft': 'rgba(56, 189, 248, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n violet: {\n '--magi-accent': '#8b5cf6',\n '--magi-accent-hover': '#a78bfa',\n '--magi-accent-soft': 'rgba(139, 92, 246, 0.08)',\n '--magi-accent-contrast': '#f5f5f7',\n },\n amber: {\n '--magi-accent': '#f59e0b',\n '--magi-accent-hover': '#fbbf24',\n '--magi-accent-soft': 'rgba(245, 158, 11, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n white: {\n '--magi-accent': '#ffffff',\n '--magi-accent-hover': '#f5f5f7',\n '--magi-accent-soft': 'rgba(255, 255, 255, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n};\n\ninterface ProductThemeContextValue {\n accent: ProductAccent;\n}\n\nconst ProductThemeContext = createContext<ProductThemeContextValue | null>(null);\n\n/**\n * Hook for any JS-aware consumer that needs the current accent.\n * Returns `{ accent: 'green' }` (the default) when used outside a `<ProductTheme>`.\n */\nexport function useProductTheme(): ProductThemeContextValue {\n const ctx = useContext(ProductThemeContext);\n return ctx ?? { accent: 'green' };\n}\n\nexport interface ProductThemeProps {\n /** Accent preset. Default: `green`. */\n accent?: ProductAccent;\n /** Optional product identifier. Sets `data-magi-product=\"<name>\"` on `<html>`. */\n name?: string;\n children?: ReactNode;\n}\n\n/**\n * ProductTheme — overrides the MAGI accent for a subtree.\n *\n * Renders only a React context provider (no DOM wrapper). On mount, sets\n * the four accent CSS variables on `<html>` so all descendants inherit via\n * CSS cascade. For scoped overrides within a subtree, consumers can use:\n *\n * <div data-magi-accent=\"danger\">\n * <Button variant=\"primary\">Delete</Button>\n * </div>\n *\n * The shorthand `data-magi-accent=\"<name>\"` maps to the matching accent\n * preset (green / cyan / violet / amber / white) or semantic variant\n * (danger / warning / success) — see foundation/globals.css.\n *\n * Note: the initial render uses the default accent (green). The accent\n * variables are applied on the next effect tick. There is no FOUC if the\n * default matches the consumer's `<ProductTheme accent>`. On accent\n * transitions the variables are overwritten directly (no temporary unset\n * to default), so there is no accent flash on change.\n *\n * @example\n * function App() {\n * return (\n * <ProductTheme accent=\"cyan\" name=\"token-factory\">\n * <Routes />\n * </ProductTheme>\n * );\n * }\n */\nexport function ProductTheme({\n accent = 'green',\n name,\n children,\n}: ProductThemeProps) {\n const value = useMemo(() => ({ accent }), [accent]);\n\n useEffect(() => {\n if (typeof document === 'undefined') return;\n const tokens = ACCENT_PRESETS[accent];\n const root = document.documentElement;\n\n root.style.setProperty('--magi-accent', tokens['--magi-accent']);\n root.style.setProperty('--magi-accent-hover', tokens['--magi-accent-hover']);\n root.style.setProperty('--magi-accent-soft', tokens['--magi-accent-soft']);\n root.style.setProperty('--magi-accent-contrast', tokens['--magi-accent-contrast']);\n\n if (name) {\n root.dataset.magiProduct = name;\n } else {\n delete root.dataset.magiProduct;\n }\n }, [accent, name]);\n\n return (\n <ProductThemeContext.Provider value={value}>\n {children}\n </ProductThemeContext.Provider>\n );\n}"],"names":["cx","parts","Container","size","as","className","children","rest","Component","cls","Section","spacing","surface","fullWidth","Stack","direction","gap","align","wrap","style","wrapStyle","jsx","Button","forwardRef","variant","loading","disabled","type","ref","Card","padding","Badge","dot","Checkbox","labelPosition","input","jsxs","Fragment","Input","inputSize","invalid","FormField","label","helper","error","helperId","fieldId","useId","labelId","describedById","hasMessage","enhanced","isValidElement","existing","cloneElement","Segmented","value","options","onChange","accent","id","ariaLabel","opt","active","isDisabled","onSelect","Banner","EmptyState","title","description","action","structured","body","ACCENT_PRESETS","ProductThemeContext","createContext","ProductTheme","name","useMemo","useEffect","tokens","root"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/utils/classnames.ts","../src/layout/Container.tsx","../src/layout/Section.tsx","../src/layout/Stack.tsx","../src/components/Button/Button.tsx","../src/components/Card/Card.tsx","../src/components/Badge/Badge.tsx","../src/components/Checkbox/Checkbox.tsx","../src/components/Input/Input.tsx","../src/components/FormField/FormField.tsx","../src/components/Segmented/Segmented.tsx","../src/components/Banner/Banner.tsx","../src/components/EmptyState/EmptyState.tsx","../src/theme.tsx"],"sourcesContent":["/** Join truthy class strings with a space. */\nexport function cx(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(' ');\n}","import type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../utils/classnames';\n\nexport type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'wide' | 'full';\n\nexport interface ContainerProps extends HTMLAttributes<HTMLElement> {\n /** Maximum width at desktop. Default: `xl` (1200px). */\n size?: ContainerSize;\n /** Override the rendered tag. Default: `div`. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Container — centers content and constrains max-width.\n *\n * @example\n * <Container size=\"lg\">...</Container>\n */\nexport function Container({\n size = 'xl',\n as,\n className,\n children,\n ...rest\n}: ContainerProps) {\n const Component = (as ?? 'div') as ElementType;\n const cls = cx('magi-container', `magi-container--${size}`, className);\n return (\n <Component className={cls} {...rest}>\n {children}\n </Component>\n );\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../utils/classnames';\n\nexport type SectionSpacing = 'sm' | 'md' | 'lg' | 'xl';\nexport type SectionSurface = 'default' | 'raised' | 'elevated';\n\nexport interface SectionProps extends HTMLAttributes<HTMLElement> {\n /** Vertical padding scale. Default: `lg`. */\n spacing?: SectionSpacing;\n /** Background surface. Default: `default` (transparent). */\n surface?: SectionSurface;\n /** Drop the inner Container padding. Useful for full-bleed content. */\n fullWidth?: boolean;\n /** Override the rendered tag. Default: `section`. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Section — page-level vertical rhythm primitive.\n *\n * @example\n * <Section spacing=\"xl\" surface=\"default\">...</Section>\n */\nexport function Section({\n spacing = 'lg',\n surface = 'default',\n fullWidth = false,\n as,\n className,\n children,\n ...rest\n}: SectionProps) {\n const Component = (as ?? 'section') as ElementType;\n const cls = cx(\n 'magi-section',\n `magi-section--${spacing}`,\n `magi-section--${surface}`,\n fullWidth && 'magi-section--fullWidth',\n className,\n );\n return (\n <Component className={cls} {...rest}>\n {children}\n </Component>\n );\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../utils/classnames';\n\nexport type StackGap =\n | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '8' | '10' | '12' | '16';\nexport type StackAlign = 'start' | 'center' | 'end' | 'stretch';\n\nexport interface StackProps extends HTMLAttributes<HTMLElement> {\n /** Layout direction. Default: vertical (column). */\n direction?: 'row' | 'column';\n /** Gap token. Default: `4` (16px). */\n gap?: StackGap;\n /** Cross-axis alignment. Default: `stretch`. */\n align?: StackAlign;\n /** Allow wrapping when `direction=\"row\"`. Default: true. */\n wrap?: boolean;\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Stack — vertical or horizontal flex primitive with semantic gap tokens.\n *\n * @example\n * <Stack gap=\"6\" align=\"center\">...</Stack>\n * <Stack direction=\"row\" gap=\"3\">...</Stack>\n */\nexport function Stack({\n direction = 'column',\n gap = '4',\n align = 'stretch',\n wrap = true,\n as,\n className,\n style,\n children,\n ...rest\n}: StackProps) {\n const Component = (as ?? 'div') as ElementType;\n const wrapStyle =\n direction === 'row' && !wrap ? { flexWrap: 'nowrap' as const } : undefined;\n const cls = cx(\n 'magi-stack',\n direction === 'row' && 'magi-stack--row',\n `magi-stack--gap-${gap}`,\n `magi-stack--align-${align}`,\n className,\n );\n\n return (\n <Component className={cls} style={{ ...wrapStyle, ...style }} {...rest}>\n {children}\n </Component>\n );\n}\n","import { forwardRef, type ButtonHTMLAttributes } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';\nexport type ButtonSize = 'sm' | 'md' | 'lg';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style. Default: `primary`. */\n variant?: ButtonVariant;\n /** Height + padding tier. Default: `md`. */\n size?: ButtonSize;\n /** Show a spinner and disable interaction. */\n loading?: boolean;\n}\n\n/**\n * Button — the canonical MAGI action element.\n *\n * Pill-shaped, four variants (primary/secondary/ghost/danger), three sizes.\n * Use `primary` for the dominant action on a page; `secondary` for supporting\n * actions; `ghost` for inline/tertiary; `danger` for destructive intent.\n *\n * @example\n * <Button variant=\"primary\" onClick={...}>Save</Button>\n * <Button variant=\"secondary\">Cancel</Button>\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n function Button(\n {\n variant = 'primary',\n size = 'md',\n loading = false,\n disabled,\n className,\n children,\n type,\n ...rest\n },\n ref,\n ) {\n const cls = cx(\n 'magi-button',\n `magi-button--${variant}`,\n `magi-button--${size}`,\n loading && 'magi-button--loading',\n className,\n );\n\n return (\n <button\n ref={ref}\n className={cls}\n type={type ?? 'button'}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...rest}\n >\n {children}\n </button>\n );\n },\n);\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type CardVariant = 'default' | 'elevated' | 'interactive';\nexport type CardPadding = 'none' | 'sm' | 'md' | 'lg';\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n /** Visual emphasis. Default: `default`. `interactive` adds hover state. */\n variant?: CardVariant;\n /** Inner padding. Default: `md`. */\n padding?: CardPadding;\n children?: ReactNode;\n}\n\n/**\n * Card — minimal dark surface with subtle border. Three variants:\n * default (flat), elevated (more contrast), interactive (hover state).\n *\n * @example\n * <Card variant=\"elevated\">...</Card>\n * <Card variant=\"interactive\" onClick={...}>...</Card>\n */\nexport const Card = forwardRef<HTMLDivElement, CardProps>(function Card(\n { variant = 'default', padding = 'md', className, children, ...rest },\n ref,\n) {\n const cls = cx(\n 'magi-card',\n `magi-card--${variant}`,\n `magi-card--padding-${padding}`,\n className,\n );\n return (\n <div ref={ref} className={cls} {...rest}>\n {children}\n </div>\n );\n});\n","import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type BadgeVariant = 'neutral' | 'accent' | 'success' | 'warning' | 'error';\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Color/meaning. Default: `neutral`. */\n variant?: BadgeVariant;\n /** Show a leading colored dot. */\n dot?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Badge — compact status / metadata label.\n *\n * @example\n * <Badge variant=\"accent\">128K context</Badge>\n * <Badge variant=\"success\" dot>Online</Badge>\n */\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { variant = 'neutral', dot = false, className, children, ...rest },\n ref,\n) {\n const cls = cx(\n 'magi-badge',\n `magi-badge--${variant}`,\n className,\n );\n return (\n <span ref={ref} className={cls} {...rest}>\n {dot ? <span className=\"magi-badge__dot\" aria-hidden=\"true\" /> : null}\n {children}\n </span>\n );\n});\n","import {\n forwardRef,\n type InputHTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type CheckboxLabelPosition = 'right' | 'left';\n\n/**\n * Omit `type` from native input attrs — this component is always\n * `type=\"checkbox\"` under the hood. Letting `type` pass through would\n * let consumers render e.g. `type=\"radio\"` with checkbox styles.\n */\nexport interface CheckboxProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {\n /** Optional label rendered next to the checkbox. */\n children?: ReactNode;\n /** Position of the label relative to the checkbox. Default: 'right'. */\n labelPosition?: CheckboxLabelPosition;\n}\n\n/**\n * Checkbox — restyled native checkbox for the MAGI dark theme.\n *\n * Uses a native `<input type=\"checkbox\">` under the hood for full form\n * integration and screen-reader compatibility. The CSS only restyles\n * the visible state.\n *\n * @example\n * // Uncontrolled:\n * <Checkbox aria-label=\"Accept terms\" defaultChecked />\n *\n * // Controlled with label:\n * <Checkbox\n * checked={providers.includes('nvidia')}\n * onChange={(e) => toggle('nvidia', e.target.checked)}\n * >\n * NVIDIA\n * </Checkbox>\n */\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(\n function Checkbox(\n {\n children,\n labelPosition = 'right',\n className,\n disabled,\n ...rest\n },\n ref,\n ) {\n const input = (\n <input\n ref={ref}\n type=\"checkbox\"\n className={cx('magi-checkbox', className)}\n disabled={disabled}\n {...rest}\n />\n );\n\n if (children === undefined || children === null) {\n return input;\n }\n\n return (\n <label\n className={cx(\n 'magi-checkbox-label',\n disabled && 'magi-checkbox-label--disabled',\n )}\n >\n {labelPosition === 'left' ? (\n <>\n <span>{children}</span>\n {input}\n </>\n ) : (\n <>\n {input}\n <span>{children}</span>\n </>\n )}\n </label>\n );\n },\n);","import { forwardRef, type InputHTMLAttributes } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type InputSize = 'sm' | 'md' | 'lg';\n\nexport interface InputProps extends InputHTMLAttributes<HTMLInputElement> {\n /** Visual size. Default: `md`. */\n inputSize?: InputSize;\n /** Error state — adds error border + aria-invalid. */\n invalid?: boolean;\n}\n\n/**\n * Input — `<input>` styled for the MAGI dark theme.\n *\n * Wraps the native element directly. No custom state management — value /\n * onChange / defaultValue / ref all pass through.\n *\n * @example\n * <Input placeholder=\"e.g. Coding assistant\" />\n * <Input type=\"password\" />\n * <Input invalid defaultValue=\"bad value\" />\n */\nexport const Input = forwardRef<HTMLInputElement, InputProps>(function Input(\n { inputSize = 'md', invalid = false, className, ...rest },\n ref,\n) {\n const cls = cx(\n 'magi-input',\n `magi-input--${inputSize}`,\n invalid && 'magi-input--invalid',\n className,\n );\n\n return (\n <input\n ref={ref}\n className={cls}\n aria-invalid={invalid || undefined}\n {...rest}\n />\n );\n});","import {\n Children,\n cloneElement,\n isValidElement,\n useId,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport { cx } from '../../utils/classnames';\n\ntype ChildProps = {\n id?: string;\n 'aria-describedby'?: string;\n 'aria-invalid'?: boolean | 'grammar' | 'spelling' | 'false' | 'true';\n 'aria-labelledby'?: string;\n};\n\nexport interface FormFieldProps {\n /** Visible label above the control. Required. */\n label: string;\n /** Optional helper text below the control. */\n helper?: string;\n /** Error message — overrides helper styling + sets aria-invalid + aria-describedby. */\n error?: string;\n /** The control itself (Input, Select, Segmented, etc.). */\n children: ReactNode;\n /** Stack alignment override. Default: stretch. */\n align?: 'start' | 'center' | 'end' | 'stretch';\n /** Extra className for the wrapper. */\n className?: string;\n /** Optional id for the helper/error element (overrides the auto-generated one). */\n helperId?: string;\n}\n\n/**\n * Merge ARIA id-list attributes (space-separated per WAI-ARIA spec).\n * Empty / null / undefined entries are dropped.\n */\nfunction mergeIds(\n ...ids: Array<string | undefined | null>\n): string | undefined {\n const filtered = ids.filter(\n (id): id is string => typeof id === 'string' && id.length > 0,\n );\n return filtered.length > 0 ? filtered.join(' ') : undefined;\n}\n\n/**\n * FormField — label + control + optional helper/error wrapper.\n *\n * Wires `aria-describedby` to the helper/error span and `aria-labelledby`\n * to the label, by cloning the single child element with the appropriate\n * ARIA attributes. Sets `aria-invalid=\"true\"` on the child when `error` is\n * present.\n *\n * **Consumer-supplied ARIA attributes are preserved (merged, not overwritten).**\n * For example, if the consumer passes `aria-describedby=\"external-help\"`,\n * the resulting attribute is `\"external-help <generated-helper-id>\"`.\n * This matches the WAI-ARIA spec for multi-value id lists.\n *\n * The child MUST be a single focusable element (Input, Select, native\n * `<input>`, Segmented, etc.). If `children` is not a valid React element,\n * the ARIA wiring is skipped — the label and helper/error still render.\n *\n * @example\n * <FormField label=\"Email\" helper=\"We'll never share this.\">\n * <Input type=\"email\" placeholder=\"you@example.com\" />\n * </FormField>\n *\n * <FormField label=\"Max models\" error=\"Pick a value between 1 and 10.\">\n * <Segmented value=\"3\" options={...} onChange={...} />\n * </FormField>\n */\nexport function FormField({\n label,\n helper,\n error,\n children,\n align = 'stretch',\n className,\n helperId,\n}: FormFieldProps) {\n const reactId = useId();\n const fieldId = `magi-field-${reactId}`;\n const labelId = `${fieldId}-label`;\n const describedById = helperId ?? `${fieldId}-helper`;\n const hasMessage = Boolean(error || helper);\n\n let enhanced: ReactNode = children;\n if (isValidElement<ChildProps>(children)) {\n const existing = children.props;\n enhanced = cloneElement(children, {\n id: existing.id ?? fieldId,\n // MERGE with consumer-supplied IDs. The order is: consumer's first,\n // then FormField's. This matches ARIA's \"consumer-supplied IDs win\n // for ordering\" intuition — FormField's helpers are appended.\n 'aria-describedby': mergeIds(\n existing['aria-describedby'],\n hasMessage ? describedById : undefined,\n ),\n 'aria-labelledby': mergeIds(\n existing['aria-labelledby'],\n labelId,\n ),\n // aria-invalid: when FormField has an error, force true (overriding\n // consumer). When no error, preserve consumer's setting.\n 'aria-invalid': error ? true : (existing['aria-invalid'] ?? undefined),\n });\n }\n\n return (\n <div\n className={cx(\n 'magi-field',\n `magi-field--align-${align}`,\n className,\n )}\n >\n <label id={labelId} className=\"magi-field-label\">\n {label}\n </label>\n {enhanced}\n {hasMessage && (\n <span\n id={describedById}\n className={cx(\n 'magi-field-helper',\n error && 'magi-field-helper--error',\n )}\n >\n {error || helper}\n </span>\n )}\n </div>\n );\n}","import type { ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type SegmentedSize = 'sm' | 'md' | 'lg';\n\nexport interface SegmentedOption<V extends string = string> {\n value: V;\n label: string;\n /** Optional: disable this individual option. */\n disabled?: boolean;\n}\n\nexport interface SegmentedProps<V extends string = string> {\n /** Currently selected option value. */\n value: V;\n /** Options to display. */\n options: SegmentedOption<V>[];\n /** Called when the user picks a different option. */\n onChange: (value: V) => void;\n /** Use accent color for the active state (cyan for tfi, etc.). Default: false. */\n accent?: boolean;\n /** Visual size. Default: `md`. */\n size?: SegmentedSize;\n /** Disable the whole control. */\n disabled?: boolean;\n /** Group label for screen readers. */\n 'aria-label'?: string;\n /** Optional id for the group. */\n id?: string;\n /** Extra className for the wrapper. */\n className?: string;\n /** Render-prop variant — render function receives each option and its state. */\n children?: (option: SegmentedOption<V>, state: {\n active: boolean;\n disabled: boolean;\n onSelect: () => void;\n }) => ReactNode;\n}\n\n/**\n * Segmented — pill-shaped single-select chip group.\n *\n * **SINGLE-SELECT ONLY.** For multi-select (e.g. provider toggles, multi-tag\n * pickers), use a `Checkbox` group instead — a multi-item segmented control\n * implies exclusive selection and confuses the interaction model.\n *\n * @example\n * <Segmented\n * value={contextMin}\n * options={[\n * { value: '128k', label: '128K+' },\n * { value: '32k', label: '32K+' },\n * { value: '8k', label: '8K+' },\n * { value: 'any', label: 'Any' },\n * ]}\n * onChange={(v) => setContextMin(v as ContextMin)}\n * />\n *\n * <Segmented value={cost} options={[...] onChange={...} accent />\n */\nexport function Segmented<V extends string = string>({\n value,\n options,\n onChange,\n accent = false,\n size = 'md',\n disabled = false,\n id,\n className,\n 'aria-label': ariaLabel,\n children,\n}: SegmentedProps<V>) {\n return (\n <div\n role=\"radiogroup\"\n id={id}\n aria-label={ariaLabel}\n className={cx(\n 'magi-segmented',\n `magi-segmented--${size}`,\n disabled && 'magi-segmented--disabled',\n className,\n )}\n >\n {options.map((opt) => {\n const active = opt.value === value;\n const isDisabled = !!(disabled || opt.disabled);\n const onSelect = () => {\n if (isDisabled || active) return;\n onChange(opt.value);\n };\n\n if (children) {\n return (\n <div key={opt.value}>\n {children(opt, { active, disabled: isDisabled, onSelect })}\n </div>\n );\n }\n\n return (\n <button\n key={opt.value}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n disabled={isDisabled}\n onClick={onSelect}\n className={cx(\n 'magi-segmented-item',\n active && 'magi-segmented-item--active',\n accent && 'magi-segmented-item--accent',\n )}\n >\n {opt.label}\n </button>\n );\n })}\n </div>\n );\n}","import type { HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport type BannerVariant = 'warning' | 'error' | 'success' | 'info';\n\nexport interface BannerProps extends HTMLAttributes<HTMLDivElement> {\n /** Semantic variant. Default: `warning`. */\n variant?: BannerVariant;\n /** Banner content. Inline elements (links, code) are allowed. */\n children: ReactNode;\n}\n\n/**\n * Banner — inline notice with a left-border accent.\n *\n * Use for hints, warnings, errors, and confirmations. Persists until the\n * condition resolves; no dismiss by default.\n *\n * - `warning` (default) — yellow accent, actionable risk\n * - `error` — red accent, blocker\n * - `success` — green accent, confirmation\n * - `info` — cyan accent, neutral hint (uses product accent)\n *\n * @example\n * <Banner variant=\"info\">No requirements set. <a href=\"/\">Go back</a>.</Banner>\n * <Banner variant=\"error\">Could not load KV catalog: {error.message}.</Banner>\n * <Banner variant=\"success\">Saved.</Banner>\n */\nexport function Banner({\n variant = 'warning',\n className,\n children,\n ...rest\n}: BannerProps) {\n return (\n <div\n role={variant === 'error' || variant === 'warning' ? 'alert' : 'status'}\n className={cx(\n 'magi-banner',\n `magi-banner--${variant}`,\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n );\n}","import type { HTMLAttributes, ReactNode } from 'react';\nimport { cx } from '../../utils/classnames';\n\nexport interface EmptyStateProps extends HTMLAttributes<HTMLDivElement> {\n /** Optional bold title above the description (structured mode). */\n title?: string;\n /** Optional description text — used in structured mode. Defaults to children. */\n description?: ReactNode;\n /** Optional primary call-to-action (e.g. a Button or Link). */\n action?: ReactNode;\n /** Body content. Required in simple mode (when no `description`).\n * In structured mode it is shown only if `description` is not provided. */\n children?: ReactNode;\n}\n\n/**\n * EmptyState — centered placeholder for \"no data yet\" cases.\n *\n * Two patterns:\n *\n * @example Simple (string + inline elements):\n * <EmptyState>\n * No models match. Try loosening the constraints — <Link to=\"/\">edit requirements</Link>.\n * </EmptyState>\n *\n * @example Structured (title + description + action):\n * <EmptyState\n * title=\"Nothing selected\"\n * description=\"Pick models on the Browse page first.\"\n * action={<Button variant=\"primary\">Go to Browse</Button>}\n * />\n */\nexport function EmptyState({\n title,\n description,\n action,\n children,\n className,\n ...rest\n}: EmptyStateProps) {\n const structured = Boolean(title || description || action);\n const body = description ?? children;\n\n return (\n <div\n className={cx(\n 'magi-empty',\n structured && 'magi-empty--structured',\n className,\n )}\n {...rest}\n >\n {structured ? (\n <>\n {title ? <p className=\"magi-empty-title\">{title}</p> : null}\n {body !== undefined && body !== null ? (\n <p className=\"magi-empty-description\">{body}</p>\n ) : null}\n {action ? <div className=\"magi-empty-action\">{action}</div> : null}\n </>\n ) : (\n children\n )}\n </div>\n );\n}","import {\n createContext,\n useContext,\n useInsertionEffect,\n useMemo,\n type ReactNode,\n} from 'react';\n\nexport type AppAccent =\n | 'green' // default — MAGI core\n | 'cyan' // Token Factory Initializr\n | 'violet' // API product\n | 'amber' // Agent product\n | 'white'; // monochrome MAGI\n\ninterface AccentTokens {\n '--magi-accent': string;\n '--magi-accent-hover': string;\n '--magi-accent-soft': string;\n '--magi-accent-contrast': string;\n}\n\nconst ACCENT_PRESETS: Record<AppAccent, AccentTokens> = {\n green: {\n '--magi-accent': '#00c853',\n '--magi-accent-hover': '#00e676',\n '--magi-accent-soft': 'rgba(0, 200, 83, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n cyan: {\n '--magi-accent': '#38bdf8',\n '--magi-accent-hover': '#7dd3fc',\n '--magi-accent-soft': 'rgba(56, 189, 248, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n violet: {\n '--magi-accent': '#8b5cf6',\n '--magi-accent-hover': '#a78bfa',\n '--magi-accent-soft': 'rgba(139, 92, 246, 0.08)',\n '--magi-accent-contrast': '#f5f5f7',\n },\n amber: {\n '--magi-accent': '#f59e0b',\n '--magi-accent-hover': '#fbbf24',\n '--magi-accent-soft': 'rgba(245, 158, 11, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n white: {\n '--magi-accent': '#ffffff',\n '--magi-accent-hover': '#f5f5f7',\n '--magi-accent-soft': 'rgba(255, 255, 255, 0.08)',\n '--magi-accent-contrast': '#050505',\n },\n};\n\ninterface AppThemeContextValue {\n accent: AppAccent;\n}\n\nconst AppThemeContext = createContext<AppThemeContextValue | null>(null);\n\n/**\n * Hook for any JS-aware consumer that needs the current accent.\n * Returns `{ accent: 'green' }` (the default) when used outside an `<AppTheme>`.\n */\nexport function useAppTheme(): AppThemeContextValue {\n const ctx = useContext(AppThemeContext);\n return ctx ?? { accent: 'green' };\n}\n\nexport interface AppThemeProps {\n /** Accent preset. Default: `green`. */\n accent?: AppAccent;\n /** Optional product identifier. Sets `data-magi-product=\"<name>\"` on `<html>`. */\n name?: string;\n children?: ReactNode;\n}\n\n/**\n * AppTheme — application-level accent configuration.\n *\n * Renders only a React context provider (no DOM wrapper). On mount, sets\n * the four accent CSS variables on `<html>` so all descendants inherit via\n * CSS cascade. For scoped overrides within a subtree, consumers should use\n * `data-magi-accent`:\n *\n * <div data-magi-accent=\"danger\">\n * <Button variant=\"primary\">Delete</Button>\n * </div>\n *\n * SSR: useInsertionEffect does not run on the server, so on SSR pages\n * the default theme applies during the initial render. For SSR theming\n * without FOUC, set `data-magi-accent=\"<name>\"` on `<html>` in your\n * server-side layout — the CSS rule in globals.css picks it up.\n *\n * Nested <AppTheme>: each instance snapshots the previous accent values\n * on mount and restores them on unmount, so a child mounting and\n * unmounting leaves the outer theme intact. Note that CSS variables\n * are global (set on `<html>`), so siblings outside a child subtree\n * still see the child's accent — use `<div data-magi-accent=\"...\">`\n * for true CSS subtree scope.\n *\n * @example\n * function App() {\n * return (\n * <AppTheme accent=\"cyan\" name=\"token-factory\">\n * <Routes />\n * </AppTheme>\n * );\n * }\n */\nexport function AppTheme({\n accent = 'green',\n name,\n children,\n}: AppThemeProps) {\n const value = useMemo(() => ({ accent }), [accent]);\n\n useInsertionEffect(() => {\n if (typeof document === 'undefined') return;\n const tokens = ACCENT_PRESETS[accent];\n const root = document.documentElement;\n\n // Snapshot previous values so the cleanup can restore them on unmount\n // or accent change. This makes nested themes work correctly: the inner\n // theme restores the outer theme's values when it unmounts.\n const prev = {\n accent: root.style.getPropertyValue('--magi-accent'),\n accentHover: root.style.getPropertyValue('--magi-accent-hover'),\n accentSoft: root.style.getPropertyValue('--magi-accent-soft'),\n accentContrast: root.style.getPropertyValue('--magi-accent-contrast'),\n product: root.dataset.magiProduct,\n };\n\n root.style.setProperty('--magi-accent', tokens['--magi-accent']);\n root.style.setProperty('--magi-accent-hover', tokens['--magi-accent-hover']);\n root.style.setProperty('--magi-accent-soft', tokens['--magi-accent-soft']);\n root.style.setProperty('--magi-accent-contrast', tokens['--magi-accent-contrast']);\n\n if (name) {\n root.dataset.magiProduct = name;\n } else {\n delete root.dataset.magiProduct;\n }\n\n return () => {\n const restoreOrRemove = (prop: string, prevValue: string) => {\n if (prevValue) root.style.setProperty(prop, prevValue);\n else root.style.removeProperty(prop);\n };\n restoreOrRemove('--magi-accent', prev.accent);\n restoreOrRemove('--magi-accent-hover', prev.accentHover);\n restoreOrRemove('--magi-accent-soft', prev.accentSoft);\n restoreOrRemove('--magi-accent-contrast', prev.accentContrast);\n\n if (prev.product !== undefined) {\n root.dataset.magiProduct = prev.product;\n } else if (root.dataset.magiProduct !== undefined) {\n delete root.dataset.magiProduct;\n }\n };\n }, [accent, name]);\n\n return (\n <AppThemeContext.Provider value={value}>\n {children}\n </AppThemeContext.Provider>\n );\n}"],"names":["cx","parts","Container","size","as","className","children","rest","Component","cls","Section","spacing","surface","fullWidth","Stack","direction","gap","align","wrap","style","wrapStyle","jsx","Button","forwardRef","variant","loading","disabled","type","ref","Card","padding","Badge","dot","Checkbox","labelPosition","input","jsxs","Fragment","Input","inputSize","invalid","mergeIds","ids","filtered","id","FormField","label","helper","error","helperId","fieldId","useId","labelId","describedById","hasMessage","enhanced","isValidElement","existing","cloneElement","Segmented","value","options","onChange","accent","ariaLabel","opt","active","isDisabled","onSelect","Banner","EmptyState","title","description","action","structured","body","ACCENT_PRESETS","AppThemeContext","createContext","AppTheme","name","useMemo","useInsertionEffect","tokens","root","prev","restoreOrRemove","prop","prevValue"],"mappings":"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"}
package/dist/theme.d.ts CHANGED
@@ -1,50 +1,53 @@
1
1
  import { type ReactNode } from 'react';
2
- export type ProductAccent = 'green' | 'cyan' | 'violet' | 'amber' | 'white';
3
- interface ProductThemeContextValue {
4
- accent: ProductAccent;
2
+ export type AppAccent = 'green' | 'cyan' | 'violet' | 'amber' | 'white';
3
+ interface AppThemeContextValue {
4
+ accent: AppAccent;
5
5
  }
6
6
  /**
7
7
  * Hook for any JS-aware consumer that needs the current accent.
8
- * Returns `{ accent: 'green' }` (the default) when used outside a `<ProductTheme>`.
8
+ * Returns `{ accent: 'green' }` (the default) when used outside an `<AppTheme>`.
9
9
  */
10
- export declare function useProductTheme(): ProductThemeContextValue;
11
- export interface ProductThemeProps {
10
+ export declare function useAppTheme(): AppThemeContextValue;
11
+ export interface AppThemeProps {
12
12
  /** Accent preset. Default: `green`. */
13
- accent?: ProductAccent;
13
+ accent?: AppAccent;
14
14
  /** Optional product identifier. Sets `data-magi-product="<name>"` on `<html>`. */
15
15
  name?: string;
16
16
  children?: ReactNode;
17
17
  }
18
18
  /**
19
- * ProductTheme — overrides the MAGI accent for a subtree.
19
+ * AppTheme — application-level accent configuration.
20
20
  *
21
21
  * Renders only a React context provider (no DOM wrapper). On mount, sets
22
22
  * the four accent CSS variables on `<html>` so all descendants inherit via
23
- * CSS cascade. For scoped overrides within a subtree, consumers can use:
23
+ * CSS cascade. For scoped overrides within a subtree, consumers should use
24
+ * `data-magi-accent`:
24
25
  *
25
26
  * <div data-magi-accent="danger">
26
27
  * <Button variant="primary">Delete</Button>
27
28
  * </div>
28
29
  *
29
- * The shorthand `data-magi-accent="<name>"` maps to the matching accent
30
- * preset (green / cyan / violet / amber / white) or semantic variant
31
- * (danger / warning / success) — see foundation/globals.css.
30
+ * SSR: useInsertionEffect does not run on the server, so on SSR pages
31
+ * the default theme applies during the initial render. For SSR theming
32
+ * without FOUC, set `data-magi-accent="<name>"` on `<html>` in your
33
+ * server-side layout — the CSS rule in globals.css picks it up.
32
34
  *
33
- * Note: the initial render uses the default accent (green). The accent
34
- * variables are applied on the next effect tick. There is no FOUC if the
35
- * default matches the consumer's `<ProductTheme accent>`. On accent
36
- * transitions the variables are overwritten directly (no temporary unset
37
- * to default), so there is no accent flash on change.
35
+ * Nested <AppTheme>: each instance snapshots the previous accent values
36
+ * on mount and restores them on unmount, so a child mounting and
37
+ * unmounting leaves the outer theme intact. Note that CSS variables
38
+ * are global (set on `<html>`), so siblings outside a child subtree
39
+ * still see the child's accent — use `<div data-magi-accent="...">`
40
+ * for true CSS subtree scope.
38
41
  *
39
42
  * @example
40
43
  * function App() {
41
44
  * return (
42
- * <ProductTheme accent="cyan" name="token-factory">
45
+ * <AppTheme accent="cyan" name="token-factory">
43
46
  * <Routes />
44
- * </ProductTheme>
47
+ * </AppTheme>
45
48
  * );
46
49
  * }
47
50
  */
48
- export declare function ProductTheme({ accent, name, children, }: ProductThemeProps): import("react").JSX.Element;
51
+ export declare function AppTheme({ accent, name, children, }: AppThemeProps): import("react").JSX.Element;
49
52
  export {};
50
53
  //# sourceMappingURL=theme.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,MAAM,MAAM,aAAa,GACrB,OAAO,GACP,MAAM,GACN,QAAQ,GACR,OAAO,GACP,OAAO,CAAC;AA0CZ,UAAU,wBAAwB;IAChC,MAAM,EAAE,aAAa,CAAC;CACvB;AAID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,wBAAwB,CAG1D;AAED,MAAM,WAAW,iBAAiB;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAgB,EAChB,IAAI,EACJ,QAAQ,GACT,EAAE,iBAAiB,+BAyBnB"}
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,MAAM,GACN,QAAQ,GACR,OAAO,GACP,OAAO,CAAC;AA0CZ,UAAU,oBAAoB;IAC5B,MAAM,EAAE,SAAS,CAAC;CACnB;AAID;;;GAGG;AACH,wBAAgB,WAAW,IAAI,oBAAoB,CAGlD;AAED,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,QAAQ,CAAC,EACvB,MAAgB,EAChB,IAAI,EACJ,QAAQ,GACT,EAAE,aAAa,+BAqDf"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokyo3rdhq/magi-design-system",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Shared visual foundation for the MAGI product family — dark-first, near-monochrome, restrained.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",