@tokyo3rdhq/magi-design-system 0.2.0 → 0.3.1
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 +198 -16
- package/dist/components/FormField/FormField.d.ts +16 -5
- package/dist/components/FormField/FormField.d.ts.map +1 -1
- package/dist/index.js +236 -195
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme.d.ts +39 -24
- package/dist/theme.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/styles/index.css +12 -3
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
|
-
-
|
|
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
|
|
|
@@ -25,16 +26,20 @@ import '@tokyo3rdhq/magi-design-system/styles.css';
|
|
|
25
26
|
</body>
|
|
26
27
|
|
|
27
28
|
// 3. Wrap your app in <ProductTheme> to optionally override the accent.
|
|
29
|
+
// 0.3.0+: ProductTheme is a context provider + sets accent CSS
|
|
30
|
+
// variables on <html>. No DOM wrapper.
|
|
28
31
|
import { ProductTheme } from '@tokyo3rdhq/magi-design-system';
|
|
29
32
|
|
|
30
|
-
<ProductTheme accent="green">
|
|
33
|
+
<ProductTheme accent="green" name="magi-portal">
|
|
31
34
|
<App />
|
|
32
35
|
</ProductTheme>
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
## API
|
|
36
39
|
|
|
37
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
117
|
+
#### `<Badge>` — compact status / metadata label
|
|
111
118
|
|
|
112
119
|
```tsx
|
|
113
120
|
import { Badge } from '@tokyo3rdhq/magi-design-system';
|
|
@@ -117,7 +124,127 @@ import { Badge } from '@tokyo3rdhq/magi-design-system';
|
|
|
117
124
|
// dot: boolean (default: false)
|
|
118
125
|
```
|
|
119
126
|
|
|
120
|
-
|
|
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
|
+
#### `<ProductTheme>` — accent override (0.3.0+: no DOM wrapper)
|
|
121
248
|
|
|
122
249
|
```tsx
|
|
123
250
|
import { ProductTheme } from '@tokyo3rdhq/magi-design-system';
|
|
@@ -125,14 +252,49 @@ import { ProductTheme } from '@tokyo3rdhq/magi-design-system';
|
|
|
125
252
|
<ProductTheme accent="cyan" name="token-factory">
|
|
126
253
|
<App />
|
|
127
254
|
</ProductTheme>
|
|
128
|
-
// accent: 'green' | 'cyan' | 'violet' | 'amber' | 'white'
|
|
129
|
-
// name: string
|
|
130
|
-
// tokens: Partial<CSSProperties> (optional additional overrides)
|
|
255
|
+
// accent: 'green' | 'cyan' | 'violet' | 'amber' | 'white' (default: 'green')
|
|
256
|
+
// name: string (optional product identifier)
|
|
131
257
|
```
|
|
132
258
|
|
|
133
|
-
|
|
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
|
-
|
|
287
|
+
#### `useProductTheme()` hook
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
import { useProductTheme } from '@tokyo3rdhq/magi-design-system';
|
|
291
|
+
|
|
292
|
+
function MyComponent() {
|
|
293
|
+
const { accent } = useProductTheme();
|
|
294
|
+
// Returns { accent: ProductAccent } or { accent: 'green' } outside
|
|
295
|
+
// a <ProductTheme>.
|
|
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
|
-
-
|
|
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,
|
|
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
|
|
|
@@ -4,7 +4,7 @@ export interface FormFieldProps {
|
|
|
4
4
|
label: string;
|
|
5
5
|
/** Optional helper text below the control. */
|
|
6
6
|
helper?: string;
|
|
7
|
-
/** Error message — overrides helper styling + sets aria-describedby. */
|
|
7
|
+
/** Error message — overrides helper styling + sets aria-invalid + aria-describedby. */
|
|
8
8
|
error?: string;
|
|
9
9
|
/** The control itself (Input, Select, Segmented, etc.). */
|
|
10
10
|
children: ReactNode;
|
|
@@ -12,18 +12,29 @@ export interface FormFieldProps {
|
|
|
12
12
|
align?: 'start' | 'center' | 'end' | 'stretch';
|
|
13
13
|
/** Extra className for the wrapper. */
|
|
14
14
|
className?: string;
|
|
15
|
-
/** Optional id for the helper/error element (
|
|
15
|
+
/** Optional id for the helper/error element (overrides the auto-generated one). */
|
|
16
16
|
helperId?: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* FormField — label + control + optional helper/error wrapper.
|
|
20
20
|
*
|
|
21
|
-
* Wires the helper
|
|
22
|
-
*
|
|
21
|
+
* Wires `aria-describedby` to the helper/error span and `aria-labelledby`
|
|
22
|
+
* to the label, by cloning the single child element with the appropriate
|
|
23
|
+
* ARIA attributes. Sets `aria-invalid="true"` on the child when `error` is
|
|
24
|
+
* present.
|
|
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
|
+
*
|
|
31
|
+
* The child MUST be a single focusable element (Input, Select, native
|
|
32
|
+
* `<input>`, Segmented, etc.). If `children` is not a valid React element,
|
|
33
|
+
* the ARIA wiring is skipped — the label and helper/error still render.
|
|
23
34
|
*
|
|
24
35
|
* @example
|
|
25
36
|
* <FormField label="Email" helper="We'll never share this.">
|
|
26
|
-
* <Input type="email" />
|
|
37
|
+
* <Input type="email" placeholder="you@example.com" />
|
|
27
38
|
* </FormField>
|
|
28
39
|
*
|
|
29
40
|
* <FormField label="Max models" error="Pick a value between 1 and 10.">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../../src/components/FormField/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
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"}
|