@hideyukimori/nene2-ui 0.6.0 → 0.8.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
@@ -153,7 +153,16 @@ component follows.
153
153
 
154
154
  ### ① The scale — 🔒 do not redefine
155
155
 
156
- `--spacing-x-3xs` … `--spacing-x-2xl`, and the radius and colour tokens they point at.
156
+ `--spacing-x-3xs` … `--spacing-x-2xl`, `--radius-x-none` `--radius-x-3xl` plus
157
+ `--radius-x-pill`, `--text-x-2xs` … `--text-x-xl`, and the colour palette.
158
+
159
+ 🔴 **A namespace with one step has no slots, only aliases.** Until 0.8.0 the radius
160
+ namespace held a single value and eight slots pointed at it, so a product following the
161
+ rules could render exactly one rounding — and every structural check passed, because the
162
+ slots existed and their defaults referenced the scale. The scale had been promoted verbatim
163
+ from the one product with the fewest conformance violations, which turned out to be the
164
+ product with the fewest choices. **A component implementation may come from one ship; the
165
+ set of choices may only come from the fleet's distribution.**
157
166
 
158
167
  ```css
159
168
  --spacing-x-slot-card-pad: 1.375rem; /* 🔴 no */
@@ -177,8 +186,41 @@ to match it also fits _worse_, not better:
177
186
  More exact matches, five times the worst error — a scale bent toward one histogram drops its
178
187
  own ends.
179
188
 
189
+ The radius scale was measured the same way, across the 266 radius values ten products apply
190
+ (2026-08-23). Candidates were scored on a second axis as well: whether two values that exist
191
+ **by design** land on the same step. Average error alone would have accepted folding `2px`
192
+ into `0`, because 2px is a 2px error — but nene-records ships nine themes at 0px and three at
193
+ 2–3px as separate product themes, so that error deletes a distinction rather than rounding
194
+ one.
195
+
196
+ | radius scale | exact match | within 2px | designed values collapsed |
197
+ | --------------------------- | ----------: | ---------: | ------------------------: |
198
+ | **the kit's nine + pill** | **69.9%** | **97.7%** | **3** (all 1px apart) |
199
+ | eight, without a `10px` step | 64.3% | 97.7% | 4 |
200
+ | seven, without `2px` | 43.2% | 97.7% | 4 |
201
+
202
+ 🔑 **A scale is judged by what it can still tell apart, not only by how far it moves things.**
203
+
180
204
  🔑 **The scale is the set you keep to. The slots are what your brand looks like.**
181
205
 
206
+ ### 🔴 One slot is not yours to tune
207
+
208
+ `--text-x-slot-control-touch-size` is a **device constraint, not a design choice**. iOS Safari
209
+ zooms the whole page when a focused control is under 16px, so lowering this brings that back.
210
+
211
+ It is a slot at all only because the kit cannot know your rem base — not because the value is
212
+ open. Set `--text-x-slot-control-size` to whatever suits your body text; leave the touch one
213
+ pointing at `--text-x-ios-floor`.
214
+
215
+ Until 0.7.0 the kit wrote this as `max(var(--text-x-md), var(--text-x-ios-floor))`, which is
216
+ the same idea expressed in a way that cannot work: `max()` picks the larger number and cannot
217
+ ask what kind of device it is on, so every desktop control was pushed to 16px too — visibly
218
+ larger than the text beside it in any product whose body is 14px. The two slots split that
219
+ into "the size you chose" and "the floor the device imposes", and the components apply the
220
+ second only under `@media (pointer: coarse)`.
221
+
222
+ 🔑 **Pointer, not width.** An iPad in landscape is wide and still zooms.
223
+
182
224
  ### Composing a slot
183
225
 
184
226
  A slot can hold several steps — four-sided padding is just CSS:
@@ -3,7 +3,7 @@ import { forwardRef } from 'react';
3
3
  import { cx } from '../lib/cx.js';
4
4
  import { CONTROL_CLASS } from '../lib/states.js';
5
5
  import { useFieldWiring } from '../forms/field-context.js';
6
- const BASE_CLASS = `w-full rounded-x-slot-control border border-border bg-surface-raised px-x-slot-control-pad-x py-x-slot-control-pad-y font-sans text-x-slot-control-size text-text-primary placeholder:text-text-muted ${CONTROL_CLASS}`;
6
+ const BASE_CLASS = `w-full rounded-x-slot-control border border-border bg-surface-raised px-x-slot-control-pad-x py-x-slot-control-pad-y font-sans text-x-slot-control-size pointer-coarse:text-x-slot-control-touch-size text-text-primary placeholder:text-text-muted ${CONTROL_CLASS}`;
7
7
  /**
8
8
  * Text input primitive. forwardRef so it works directly with react-hook-form `register`.
9
9
  * Visual values come from theme tokens only.
@@ -1 +1 @@
1
- {"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/primitives/Input.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA4B,MAAM,OAAO,CAAC;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAI3D,MAAM,UAAU,GAAG,yMAAyM,aAAa,EAAE,CAAC;AAE5O;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA+B,SAAS,KAAK,CAC1E,EACE,SAAS,EACT,EAAE,EACF,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAElF,OAAO,gBAAO,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,KAAM,MAAM,KAAM,IAAI,GAAI,CAAC;AACzF,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"Input.js","sourceRoot":"","sources":["../../src/primitives/Input.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA4B,MAAM,OAAO,CAAC;AAC7D,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAI3D,MAAM,UAAU,GAAG,uPAAuP,aAAa,EAAE,CAAC;AAE1R;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,CAA+B,SAAS,KAAK,CAC1E,EACE,SAAS,EACT,EAAE,EACF,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAElF,OAAO,gBAAO,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,KAAM,MAAM,KAAM,IAAI,GAAI,CAAC;AACzF,CAAC,CAAC,CAAC"}
@@ -3,7 +3,7 @@ import { forwardRef } from 'react';
3
3
  import { cx } from '../lib/cx.js';
4
4
  import { CONTROL_CLASS } from '../lib/states.js';
5
5
  import { useFieldWiring } from '../forms/field-context.js';
6
- const BASE_CLASS = `w-full rounded-x-slot-control border border-border bg-surface-raised px-x-slot-control-pad-x py-x-slot-control-pad-y font-sans text-x-slot-control-size text-text-primary ${CONTROL_CLASS}`;
6
+ const BASE_CLASS = `w-full rounded-x-slot-control border border-border bg-surface-raised px-x-slot-control-pad-x py-x-slot-control-pad-y font-sans text-x-slot-control-size pointer-coarse:text-x-slot-control-touch-size text-text-primary ${CONTROL_CLASS}`;
7
7
  /**
8
8
  * Select primitive. forwardRef so it works directly with react-hook-form `register`.
9
9
  * Visual values come from theme tokens only.
@@ -1 +1 @@
1
- {"version":3,"file":"Select.js","sourceRoot":"","sources":["../../src/primitives/Select.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA6C,MAAM,OAAO,CAAC;AAC9E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAM3D,MAAM,UAAU,GAAG,6KAA6K,aAAa,EAAE,CAAC;AAEhN;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAiC,SAAS,MAAM,CAC9E,EACE,QAAQ,EACR,SAAS,EACT,EAAE,EACF,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAElF,OAAO,CACL,iBAAQ,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,KAAM,MAAM,KAAM,IAAI,YACzE,QAAQ,GACF,CACV,CAAC;AACJ,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"Select.js","sourceRoot":"","sources":["../../src/primitives/Select.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA6C,MAAM,OAAO,CAAC;AAC9E,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAM3D,MAAM,UAAU,GAAG,2NAA2N,aAAa,EAAE,CAAC;AAE9P;;;GAGG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAiC,SAAS,MAAM,CAC9E,EACE,QAAQ,EACR,SAAS,EACT,EAAE,EACF,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAElF,OAAO,CACL,iBAAQ,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,KAAM,MAAM,KAAM,IAAI,YACzE,QAAQ,GACF,CACV,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -3,7 +3,7 @@ import { forwardRef } from 'react';
3
3
  import { cx } from '../lib/cx.js';
4
4
  import { CONTROL_CLASS } from '../lib/states.js';
5
5
  import { useFieldWiring } from '../forms/field-context.js';
6
- const BASE_CLASS = `w-full rounded-x-slot-control border border-border bg-surface-raised px-x-slot-control-pad-x py-x-slot-control-pad-y font-sans text-x-slot-control-size text-text-primary placeholder:text-text-muted ${CONTROL_CLASS}`;
6
+ const BASE_CLASS = `w-full rounded-x-slot-control border border-border bg-surface-raised px-x-slot-control-pad-x py-x-slot-control-pad-y font-sans text-x-slot-control-size pointer-coarse:text-x-slot-control-touch-size text-text-primary placeholder:text-text-muted ${CONTROL_CLASS}`;
7
7
  /**
8
8
  * Multi-line text input. Deliberately identical to `Input` apart from the element, so the
9
9
  * two never drift: five ships wrote their own `Textarea.tsx` and the kit shipped without
@@ -1 +1 @@
1
- {"version":3,"file":"Textarea.js","sourceRoot":"","sources":["../../src/primitives/Textarea.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA+B,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAI3D,MAAM,UAAU,GAAG,yMAAyM,aAAa,EAAE,CAAC;AAE5O;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAqC,SAAS,QAAQ,CACtF,EACE,SAAS,EACT,EAAE,EACF,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAElF,OAAO,mBAAU,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,KAAM,MAAM,KAAM,IAAI,GAAI,CAAC;AAC5F,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"Textarea.js","sourceRoot":"","sources":["../../src/primitives/Textarea.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAA+B,MAAM,OAAO,CAAC;AAChE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAI3D,MAAM,UAAU,GAAG,uPAAuP,aAAa,EAAE,CAAC;AAE1R;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAqC,SAAS,QAAQ,CACtF,EACE,SAAS,EACT,EAAE,EACF,cAAc,EAAE,WAAW,EAC3B,kBAAkB,EAAE,eAAe,EACnC,eAAe,EAAE,YAAY,EAC7B,GAAG,IAAI,EACR,EACD,GAAG;IAEH,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC;IAElF,OAAO,mBAAU,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC,KAAM,MAAM,KAAM,IAAI,GAAI,CAAC;AAC5F,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hideyukimori/nene2-ui",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "NeNe fleet shared React UI kit — token-driven primitives on Tailwind v4 @theme. Components carry no design of their own; themes do.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -138,8 +138,24 @@
138
138
  --color-x-slot-field-error: var(--color-danger);
139
139
  --text-x-slot-field-error-size: var(--text-x-2xs);
140
140
  --text-x-slot-field-label-size: var(--text-x-xs);
141
- --font-weight-x-slot-field-label: 500;
142
- --text-x-slot-control-size: max(var(--text-x-md), var(--text-x-ios-floor));
141
+ /* 🔴 `var(--font-weight-medium)`, not `500`. Identical output — Tailwind defines that
142
+ * step as 500 — but a literal here is a weight the kit invented, and the namespace has a
143
+ * nine-step scale already (thin…black). This one escaped the "slot defaults are scale
144
+ * references only" check because that check listed the namespaces it knew about; found
145
+ * while adding the radius scale (#348). */
146
+ --font-weight-x-slot-field-label: var(--font-weight-medium);
147
+ /* Control text. Two slots, because one value cannot answer "how is this being pointed at".
148
+ *
149
+ * 0.6.0 used `max(var(--text-x-md), var(--text-x-ios-floor))`, which raised every control
150
+ * to 16px — including on a desktop whose body text is 14px, where the inputs then look
151
+ * oversized next to everything else (found by hide on a real screen, 2026-08-23). `max()`
152
+ * cannot ask what kind of device it is on; it only ever picks the larger number.
153
+ *
154
+ * 🔴 The touch slot is a device constraint, not a design choice. iOS Safari zooms the whole
155
+ * page when a focused control is under 16px, so lowering it re-introduces that. It is a slot
156
+ * only because the kit cannot know a product's rem base — not because it is yours to tune. */
157
+ --text-x-slot-control-size: var(--text-x-md);
158
+ --text-x-slot-control-touch-size: var(--text-x-ios-floor);
143
159
 
144
160
  /* Sentinel for the consumer's build (#316). Zero-width, so applying it does nothing.
145
161
  * Its only job is to exist: `p-x-source-probe` appears nowhere except this kit's `dist`,
@@ -152,7 +168,50 @@
152
168
  * gave them nothing (50 x10, 40 x4, 30 x6, measured 2026-08-23 across 20 uses). */
153
169
  --opacity-x-disabled: 50%;
154
170
 
171
+ /* The radius scale. Nine steps plus pill, chosen the same way the spacing scale was —
172
+ * by measuring the fleet, not by taste. Snapping the 266 radius values the fleet actually
173
+ * applies (`border-radius` declarations and `--radius-*` theme tokens across ten products,
174
+ * 2026-08-23, #348) onto these nine puts 69.9% exactly on a step and 97.7% within 2px.
175
+ *
176
+ * 🔴 This replaces a scale that had ONE step. Eight slots all pointed at `--radius-x-md`,
177
+ * so a product following the rules could only ever render 8px — and a slot whose value set
178
+ * has one member is not a slot. The cause is recorded because it generalizes: the kit's
179
+ * theme was promoted verbatim from nene-payout, and payout is the fleet's only product
180
+ * with a single radius. Promoting from the product with the fewest conformance violations
181
+ * exported its poverty as the norm — having no choices is not the same as choosing well.
182
+ * A component implementation may come from one ship; the SET OF CHOICES may not.
183
+ *
184
+ * 🔴 `2px` is its own step because it is the single most common radius in the fleet
185
+ * (56 of 266). nene-records ships 9 themes at 0px and 3 at 2-3px as separate product
186
+ * themes — so folding 2px into 0 would not be a rounding error, it would delete a
187
+ * distinction somebody drew on purpose. Candidate scales were therefore scored on a
188
+ * second axis besides average error: whether two values that exist by design collapse
189
+ * onto one step (vault's proposal). The steps kept apart here are 0/2, 6/8 and 10/12;
190
+ * what still collapses (3-4, 5-6, 9-10) is 1px apart, below a scale's resolution.
191
+ *
192
+ * `--radius-x-md` keeps the value it had. Every slot points at it, so changing it would
193
+ * move every product's appearance silently — the scale gains steps, the default does not
194
+ * move. */
195
+ --radius-x-none: 0;
196
+ --radius-x-2xs: 2px;
197
+ --radius-x-xs: 4px;
198
+ --radius-x-sm: 6px;
155
199
  --radius-x-md: 0.5rem;
200
+ --radius-x-lg: 10px;
201
+ --radius-x-xl: 12px;
202
+ --radius-x-2xl: 16px;
203
+ --radius-x-3xl: 24px;
204
+
205
+ /* Fully round. A step, not a footnote: `rounded-x-pill` is the most-used radius in
206
+ * nene-field (48 uses) and appears in five products (64 uses total).
207
+ *
208
+ * 🔴 A product whose design language has no rounding does NOT redefine this step — it
209
+ * points its slots at `--radius-x-none`. nene-records reaches the same end today by
210
+ * setting its own `--radius-full: 0px`, which works only because its components hard-code
211
+ * `rounded-full`. Overriding a scale step changes what a name means fleet-wide; overriding
212
+ * a slot changes one product's choice. The second is the mechanism; the first is the drift
213
+ * the scale exists to prevent. */
214
+ --radius-x-pill: 9999px;
156
215
 
157
216
  /* The spacing scale. Nine steps, chosen by measurement rather than taste: snapping the
158
217
  * 944 spacing utilities the fleet writes by hand onto these nine moves 98.2% of them by