@hideyukimori/nene2-ui 0.8.0 → 0.14.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.
Files changed (55) hide show
  1. package/README.md +96 -4
  2. package/dist/data/DataTable.js +1 -1
  3. package/dist/data/DataTable.js.map +1 -1
  4. package/dist/data/DetailList.js +1 -1
  5. package/dist/data/DetailList.js.map +1 -1
  6. package/dist/data/Pagination.js +1 -1
  7. package/dist/data/Pagination.js.map +1 -1
  8. package/dist/feedback/Badge.js +3 -3
  9. package/dist/feedback/Badge.js.map +1 -1
  10. package/dist/feedback/InlineAlert.d.ts +13 -4
  11. package/dist/feedback/InlineAlert.js +5 -5
  12. package/dist/feedback/InlineAlert.js.map +1 -1
  13. package/dist/feedback/ToastProvider.js +2 -2
  14. package/dist/feedback/ToastProvider.js.map +1 -1
  15. package/dist/forms/FormField.d.ts +11 -1
  16. package/dist/forms/FormField.js +7 -3
  17. package/dist/forms/FormField.js.map +1 -1
  18. package/dist/forms/field-context.d.ts +13 -0
  19. package/dist/forms/field-context.js +6 -1
  20. package/dist/forms/field-context.js.map +1 -1
  21. package/dist/layout/Card.js +1 -1
  22. package/dist/layout/Card.js.map +1 -1
  23. package/dist/layout/PageHeader.js +1 -1
  24. package/dist/layout/PageHeader.js.map +1 -1
  25. package/dist/lib/states.d.ts +33 -3
  26. package/dist/lib/states.js +37 -2
  27. package/dist/lib/states.js.map +1 -1
  28. package/dist/overlay/Modal.js +1 -1
  29. package/dist/overlay/Modal.js.map +1 -1
  30. package/dist/primitives/Button.js +32 -8
  31. package/dist/primitives/Button.js.map +1 -1
  32. package/dist/primitives/Checkbox.d.ts +21 -6
  33. package/dist/primitives/Checkbox.js +20 -10
  34. package/dist/primitives/Checkbox.js.map +1 -1
  35. package/dist/primitives/Input.js +2 -2
  36. package/dist/primitives/Input.js.map +1 -1
  37. package/dist/primitives/Radio.d.ts +20 -6
  38. package/dist/primitives/Radio.js +19 -10
  39. package/dist/primitives/Radio.js.map +1 -1
  40. package/dist/primitives/Select.js +2 -2
  41. package/dist/primitives/Select.js.map +1 -1
  42. package/dist/primitives/Spinner.js +1 -1
  43. package/dist/primitives/Spinner.js.map +1 -1
  44. package/dist/primitives/Switch.js +4 -2
  45. package/dist/primitives/Switch.js.map +1 -1
  46. package/dist/primitives/Text.js +1 -1
  47. package/dist/primitives/Text.js.map +1 -1
  48. package/dist/primitives/Textarea.js +2 -2
  49. package/dist/primitives/Textarea.js.map +1 -1
  50. package/dist/states/EmptyState.js +1 -1
  51. package/dist/states/EmptyState.js.map +1 -1
  52. package/dist/states/ErrorState.js +1 -1
  53. package/dist/states/ErrorState.js.map +1 -1
  54. package/package.json +1 -1
  55. package/themes/default.css +213 -2
@@ -19,6 +19,30 @@
19
19
  --color-on-accent: oklch(99% 0 0);
20
20
  --color-danger: oklch(55% 0.2 25);
21
21
 
22
+ /* 🔴 Four contract colours the theme did not define. They are not new: `warn`,
23
+ * `warn-soft`, `on-warn` and `danger-soft` are in the frozen Core Token Contract v1
24
+ * (28 colours), and this theme defined 8 of them. Twenty are still missing — see the
25
+ * README — but these four are what a validity state needs.
26
+ *
27
+ * Same root cause as the radius scale: the theme was promoted verbatim from nene-payout,
28
+ * and payout has no warn. Six other ships do (nene-vault, nene-invoice, nene-field,
29
+ * nene-records, nene-deal, nene-profile), so `--color-x-slot-alert-warn-*` pointed at
30
+ * `--color-danger` — a warning that renders as an error. Promoting from the product with
31
+ * the fewest violations exported its missing distinctions along with its consistency.
32
+ *
33
+ * Values are the fleet median of the eight `--color-warn` definitions in use
34
+ * (2026-08-23; nene-field's hex converted to OKLCH to be counted).
35
+ *
36
+ * 🔴 `on-warn` is measured, not chosen. Against this `warn` fill: white is 3.48:1 and
37
+ * `text-primary` is 4.47:1 — which misses the 4.5:1 body-text threshold by 0.03, and is
38
+ * exactly the kind of near-miss that survives review by eye. The dark amber below is
39
+ * 4.97:1. The `warn` fill itself is 3.58:1 against `surface-raised`, clearing the 3:1
40
+ * that WCAG 1.4.11 asks of a non-text boundary. */
41
+ --color-warn: oklch(63% 0.135 75);
42
+ --color-warn-soft: oklch(95% 0.035 80);
43
+ --color-on-warn: oklch(21% 0.04 75);
44
+ --color-danger-soft: oklch(95% 0.03 30);
45
+
22
46
  --shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.06);
23
47
 
24
48
  /* Typography. Until 0.3.0 the kit had none of these and the components carried
@@ -54,6 +78,21 @@
54
78
  --text-x-xl: 1.5rem;
55
79
  --text-x-ios-floor: 16px;
56
80
 
81
+ /* The smallest a control may be on a touch screen. Not a design step, for the same reason
82
+ * `--text-x-ios-floor` is not: it is what the platform requires, and a product lowering it
83
+ * is not expressing a preference, it is making targets people miss.
84
+ *
85
+ * 🔴 44px, which is what WCAG 2.5.5 (AAA) and Apple's HIG both ask for — the smallest
86
+ * value that satisfies the standards rather than the fleet's average. Two ships already
87
+ * set 48px (nene-profile, nene-deal), Material's number; a product that wants it raises
88
+ * the slot. Picking the higher one here would be the kit imposing a size, and picking a
89
+ * median would make a device constraint look like a preference.
90
+ *
91
+ * ⚠️ The kit's own `sm` button computes to about 31px against a 14px body — above the
92
+ * 24px WCAG 2.5.8 (AA) asks for, and well under this. The `md` button is already ~47px,
93
+ * so this changes nothing there. */
94
+ --spacing-x-touch-floor: 44px;
95
+
57
96
  /* ─────────────────────────────────────────────────────────────────────────
58
97
  * ② SLOTS — a product may redefine these.
59
98
  *
@@ -70,6 +109,12 @@
70
109
  --spacing-x-slot-button-pad-y: var(--spacing-x-xs);
71
110
  --spacing-x-slot-button-sm-pad-x: var(--spacing-x-xs);
72
111
  --spacing-x-slot-button-sm-pad-y: var(--spacing-x-3xs);
112
+ /* 🔴 The gap between an icon and its label. It exists because the button became a flex
113
+ * container in 0.13.0, and a flex container drops the whitespace that used to separate
114
+ * them: `<Button><Icon /> Save</Button>` renders the text as an anonymous flex item, and
115
+ * leading whitespace in one is stripped. `3xs` is 0.25rem, which is about the width of
116
+ * the space that disappears — so the change of display does not also change the spacing. */
117
+ --spacing-x-slot-button-gap: var(--spacing-x-3xs);
73
118
  --spacing-x-slot-control-pad-x: var(--spacing-x-2xs);
74
119
  --spacing-x-slot-control-pad-y: var(--spacing-x-xs);
75
120
  --spacing-x-slot-field-gap: var(--spacing-x-2xs);
@@ -126,9 +171,11 @@
126
171
  --color-x-slot-alert-info-bg: var(--color-surface);
127
172
  --color-x-slot-alert-info-fg: var(--color-text-primary);
128
173
  --color-x-slot-alert-info-border: var(--color-border);
174
+ /* 🔴 Changed in 0.12.0: these pointed at `--color-danger`, so a warning rendered as an
175
+ * error. Not a shade — a different meaning wearing the wrong one. */
129
176
  --color-x-slot-alert-warn-bg: var(--color-surface);
130
- --color-x-slot-alert-warn-fg: var(--color-danger);
131
- --color-x-slot-alert-warn-border: var(--color-danger);
177
+ --color-x-slot-alert-warn-fg: var(--color-on-warn);
178
+ --color-x-slot-alert-warn-border: var(--color-warn);
132
179
  --color-x-slot-alert-danger-bg: var(--color-surface);
133
180
  --color-x-slot-alert-danger-fg: var(--color-danger);
134
181
  --color-x-slot-alert-danger-border: var(--color-danger);
@@ -137,6 +184,10 @@
137
184
  --text-x-slot-field-hint-size: var(--text-x-2xs);
138
185
  --color-x-slot-field-error: var(--color-danger);
139
186
  --text-x-slot-field-error-size: var(--text-x-2xs);
187
+ /* 🔴 `on-warn`, not `warn`. This text sits on the page, not on a warn fill: `--color-warn`
188
+ * measures 3.58:1 against `surface-raised`, which clears the 3:1 a border needs and misses
189
+ * the 4.5:1 body text needs. The dark amber clears both. */
190
+ --color-x-slot-field-warning-fg: var(--color-on-warn);
140
191
  --text-x-slot-field-label-size: var(--text-x-xs);
141
192
  /* 🔴 `var(--font-weight-medium)`, not `500`. Identical output — Tailwind defines that
142
193
  * step as 500 — but a literal here is a weight the kit invented, and the namespace has a
@@ -156,6 +207,166 @@
156
207
  * only because the kit cannot know a product's rem base — not because it is yours to tune. */
157
208
  --text-x-slot-control-size: var(--text-x-md);
158
209
  --text-x-slot-control-touch-size: var(--text-x-ios-floor);
210
+ /* The other half of the same constraint: the first says a focused control must not be
211
+ * small enough to make iOS zoom, this says it must not be small enough to miss. Applied
212
+ * only under `@media (pointer: coarse)` — pointer, not width, for the reason recorded
213
+ * above (an iPad in landscape is wide and still has fingers). */
214
+ --spacing-x-slot-control-touch-min: var(--spacing-x-touch-floor);
215
+
216
+ /* ── Colour slots ─────────────────────────────────────────────────────────────
217
+ * Every colour a component paints comes from here.
218
+ *
219
+ * 🔴 Until 0.9.0 only `FormField` and `InlineAlert` had any, and the other 26 components
220
+ * reached straight into the palette (`text-text-primary`, `bg-surface-raised`) — 59 places
221
+ * in 20 files. The README said "every design value a component uses comes from a slot"
222
+ * the whole time. The check that was supposed to enforce it listed the utility prefixes
223
+ * it knew about — `p px py … rounded` — so colour and weight were never inspected at all.
224
+ *
225
+ * Found by nene-vault comparing its production screens against the migrated build in a
226
+ * real browser (Playwright, computed styles, element by element, 2026-08-23): 73 elements
227
+ * matched, 21 differed, and every difference was inside a kit component. Its own markup —
228
+ * side nav, tables, headings — matched on every property. A product cannot make a control
229
+ * darker than its body text while the kit writes the colour itself.
230
+ *
231
+ * Defaults are palette references, so nothing renders differently. What changes is that
232
+ * there is now an opening. */
233
+ --color-x-slot-focus-ring: var(--color-text-primary);
234
+
235
+ --color-x-slot-button-primary-bg: var(--color-accent);
236
+ --color-x-slot-button-primary-fg: var(--color-on-accent);
237
+ --color-x-slot-button-secondary-bg: var(--color-surface-raised);
238
+ --color-x-slot-button-secondary-fg: var(--color-text-primary);
239
+ --color-x-slot-button-secondary-border: var(--color-border);
240
+ --color-x-slot-button-danger-bg: var(--color-danger);
241
+ --color-x-slot-button-danger-fg: var(--color-on-accent);
242
+ --color-x-slot-button-ghost-fg: var(--color-text-primary);
243
+
244
+ /* Shared by Input, Select and Textarea — they are one control with three shapes, and a
245
+ * product that darkens one and not the others has a bug, not a design. */
246
+ --color-x-slot-control-fg: var(--color-text-primary);
247
+ --color-x-slot-control-bg: var(--color-surface-raised);
248
+ --color-x-slot-control-border: var(--color-border);
249
+ --color-x-slot-control-placeholder: var(--color-text-muted);
250
+
251
+ /* 🔴 The kit painted nothing for validity until 0.12.0 — an invalid control looked exactly
252
+ * like a valid one, while `aria-invalid` was set and announced. Seven of nine ships paint
253
+ * it themselves (Tailwind `aria-invalid:` variants, CSS selectors, or a class name), so a
254
+ * migration onto the kit silently removed a signal the product already had. nene-vault
255
+ * lost an amber border on retention periods under ten years, and the test that guarded it
256
+ * asserted `aria-invalid="true"` — the attribute, not the paint — so it stayed green.
257
+ *
258
+ * 🔑 A test can check a stand-in for the thing it cares about. While both live in the
259
+ * same component that is the same test; the moment the real thing moves upstream, the
260
+ * stand-in stays behind and keeps passing.
261
+ *
262
+ * Border, not a ring: `ring-*` is a box-shadow and a parent with `overflow: hidden` clips
263
+ * it — the same reason the focus indicator here is an `outline`. The background carries
264
+ * the rest, and neither competes with the focus outline. */
265
+ --color-x-slot-control-invalid-border: var(--color-danger);
266
+ --color-x-slot-control-invalid-bg: var(--color-danger-soft);
267
+ --color-x-slot-control-warn-border: var(--color-warn);
268
+ --color-x-slot-control-warn-bg: var(--color-warn-soft);
269
+
270
+ --color-x-slot-choice-fg: var(--color-text-primary);
271
+ --color-x-slot-choice-border: var(--color-border);
272
+
273
+ --color-x-slot-switch-on-bg: var(--color-accent);
274
+ --color-x-slot-switch-on-fg: var(--color-on-accent);
275
+ --color-x-slot-switch-off-bg: var(--color-surface);
276
+ --color-x-slot-switch-off-fg: var(--color-text-muted);
277
+ --color-x-slot-switch-border: var(--color-border);
278
+
279
+ --color-x-slot-badge-neutral-bg: var(--color-surface);
280
+ --color-x-slot-badge-neutral-fg: var(--color-text-muted);
281
+ --color-x-slot-badge-neutral-border: var(--color-border);
282
+ --color-x-slot-badge-accent-bg: var(--color-accent);
283
+ --color-x-slot-badge-accent-fg: var(--color-on-accent);
284
+ --color-x-slot-badge-accent-border: var(--color-accent);
285
+ --color-x-slot-badge-danger-bg: var(--color-danger);
286
+ --color-x-slot-badge-danger-fg: var(--color-on-accent);
287
+ --color-x-slot-badge-danger-border: var(--color-danger);
288
+
289
+ --color-x-slot-toast-bg: var(--color-surface-raised);
290
+ --color-x-slot-toast-fg: var(--color-text-primary);
291
+ --color-x-slot-toast-border: var(--color-border);
292
+ --color-x-slot-toast-danger-fg: var(--color-danger);
293
+ --color-x-slot-toast-danger-border: var(--color-danger);
294
+
295
+ --color-x-slot-card-bg: var(--color-surface-raised);
296
+ --color-x-slot-card-border: var(--color-border);
297
+
298
+ --color-x-slot-modal-bg: var(--color-surface-raised);
299
+ --color-x-slot-modal-fg: var(--color-text-primary);
300
+ --color-x-slot-modal-border: var(--color-border);
301
+ /* The dim behind a modal. A colour, so it belongs here — the opacity that goes with it is
302
+ * applied by the component, since it is not a palette entry. */
303
+ --color-x-slot-modal-scrim: var(--color-text-primary);
304
+
305
+ --color-x-slot-table-fg: var(--color-text-primary);
306
+ --color-x-slot-table-header-fg: var(--color-text-muted);
307
+ --color-x-slot-table-border: var(--color-border);
308
+
309
+ --color-x-slot-detail-term-fg: var(--color-text-muted);
310
+ --color-x-slot-detail-description-fg: var(--color-text-primary);
311
+ --color-x-slot-detail-border: var(--color-border);
312
+
313
+ --color-x-slot-pagination-fg: var(--color-text-muted);
314
+ --color-x-slot-page-header-fg: var(--color-text-primary);
315
+ --color-x-slot-text-fg: var(--color-text-primary);
316
+ --color-x-slot-text-muted-fg: var(--color-text-muted);
317
+ --color-x-slot-spinner-fg: var(--color-text-muted);
318
+ --color-x-slot-empty-state-fg: var(--color-text-muted);
319
+ --color-x-slot-error-state-fg: var(--color-danger);
320
+ /* 🔴 The required marker, not the error message — that one is `--color-x-slot-field-error`.
321
+ * Named `-error-fg` when the colour slots were swept in (0.9.0, same day), which reads as
322
+ * "the error message's colour" and is not. Renamed before anything consumed it. */
323
+ --color-x-slot-field-required-marker: var(--color-danger);
324
+
325
+ /* An upper bound for an `<svg>` written directly inside a `Button`.
326
+ *
327
+ * 🔴 `max-*`, deliberately not `size-*`. An `<svg>` with no width/height attributes and no
328
+ * CSS size is a replaced element with no intrinsic size, so it lays out at 300x150 — which
329
+ * is how a nene-vault upload button became a 230px-tall rectangle the moment its wrapper
330
+ * changed from a `<div>` to a `<Stack>` (2026-08-23).
331
+ *
332
+ * Sizing it outright would fix that and break something else: a class produced by an
333
+ * arbitrary variant (`[&_svg]:size-6`) outranks the plain `h-6 w-6` that `Icon` puts on
334
+ * itself, so `<Icon size="sm">` inside a button would silently render at the button's
335
+ * size. A maximum has no such conflict — it constrains nothing that is already smaller,
336
+ * and there is no specificity contest because the two properties are different.
337
+ *
338
+ * The bound is `--spacing-x-lg`, which is 1.5rem — `Icon size="lg"` exactly — so no icon
339
+ * the kit ships is clamped by it. */
340
+ --spacing-x-slot-button-icon: var(--spacing-x-lg);
341
+
342
+ /* ── Weight slots ─────────────────────────────────────────────────────────────
343
+ * `font-medium` appeared inline in four components. Same rule, same reason as colour. */
344
+ --font-weight-x-slot-button: var(--font-weight-medium);
345
+ --font-weight-x-slot-table-header: var(--font-weight-medium);
346
+ --font-weight-x-slot-detail-term: var(--font-weight-medium);
347
+ --font-weight-x-slot-page-header: var(--font-weight-medium);
348
+
349
+ /* ── Size slots that decline to choose ────────────────────────────────────────
350
+ * 🔴 `inherit`, not a step. Button and the choice controls set no font-size today, so
351
+ * they take the page's — and picking a step here would change how every product already
352
+ * renders, which is the one thing this release must not do. `inherit` is not a design
353
+ * value the kit invented; it is the kit saying it has no opinion, in a place a product
354
+ * can now answer. nene-vault's production buttons are 13px against a 14px body, which is
355
+ * exactly the distinction that had nowhere to live.
356
+ *
357
+ * ⚠️ 13px is not on the type scale (0.6875 / 0.75 / 0.875 / 1 / 1.125 / 1.5rem). The
358
+ * scale was measured from what the fleet writes in Tailwind classes, which cannot express
359
+ * a value it has no step for — so that measurement could not have seen it. Recorded here
360
+ * rather than acted on: changing the type scale is its own decision, with its own
361
+ * measurement, and it is not this release's. */
362
+ --text-x-slot-button-size: inherit;
363
+ /* 🔴 `sm` gets its own, because padding already has one and the pair was asymmetric:
364
+ * `--spacing-x-slot-button-{,sm-}pad-{x,y}` is four slots, the font was one. nene-vault
365
+ * shipped md=13px and sm=12px, and on the kit both came out at the same size — its
366
+ * pagination "Previous" measured 12px in production against 13px on the migrated build.
367
+ * Defaults to `inherit` like its sibling, so nothing renders differently today. */
368
+ --text-x-slot-button-sm-size: inherit;
369
+ --text-x-slot-choice-size: inherit;
159
370
 
160
371
  /* Sentinel for the consumer's build (#316). Zero-width, so applying it does nothing.
161
372
  * Its only job is to exist: `p-x-source-probe` appears nowhere except this kit's `dist`,