# InputNumber fix spec — Phase X.4.2 batch C

Inputs:

- Figma cache: `figma-data/normalized/figma-mcp-cache/inputnumber.tsx`
- Canonical: `src/components/InputNumber/InputNumber.vue`
- Token aliases: `src/design-system/translation/token-aliases.ts`
- Prop aliases: `src/design-system/translation/prop-aliases.md`
- Icon aliases: `src/design-system/translation/icon-aliases.ts`
- Variables: `src/tokens/variables.css`

Figma component summary (cache):

- Single axis: `property1: 'Default' | 'Only Add' | 'Only Reduce' | 'Readonly'`
- Wrapper: `border border-[var(--color-type/line/deep-divider,#353535)]` + `rounded-[4px]` + `h-[var(--size/xxs,32px)]` + `max-w-[200px] min-w-[100px] w-[128px]`
- Two button slots flanking center text: minus icon (left) + plus icon (right), each with `aspect-[32/32]` (32px square) and `border-r` / `border-l` divider
- Icon size: `16px`
- Center text: `Helvetica Neue Regular 14px`, `leading-[16px]`, color `--text-1` (`#f8f8f8`) for active, `--text-grey-dis` (`#7b7b7b`) for Readonly

Per `prop-aliases.md` Known Entries:

| Code Prop | Figma Property | Status |
| --- | --- | --- |
| `property1` | `Property 1` | approved alias (Title Case → camelCase) |

Per `prop-aliases.md` Vue ecosystem:

- `modelValue`, `disabled`, `readonly` are runtime additions

Per icon-aliases (existing entries) — `icon/Edit/Add 1` and `icon/Edit/Minus` map to `<Icon name="add">` / `<Icon name="minus">`. Canonical uses these names ✅.

---

## Mismatches

### a. Spacing

| Aspect | Figma 真源 | Canonical 现状 | Fix |
| --- | --- | --- | --- |
| Wrapper height | `--size/xxs` figma var = `32px` (matches `--sp-xl` value-wise but semantically a control-size token, not a spacing one) | `height: 32px` literal | propose new token `--control-height-xxs: 32px` (rationale: figma `size/xxs` is a distinct **control-height** scale, not spacing — see "New tokens" below) |
| Wrapper width range | `min-w-[100px] w-[128px] max-w-[200px]` | `width: 128px; min-width: 100px; max-width: 200px` literals | propose `--input-number-min-width: 100px`, `--input-number-default-width: 128px`, `--input-number-max-width: 200px` (rationale: explicit anchors; figma 真源) |
| Button width (each) | `aspect-[32/32]` = `32×32px` | `width: 32px; height: 32px` literals | repoint to `var(--control-height-xxs)` for both width and height (square button) |
| Border-radius | `rounded-[4px]` | `border-radius: 4px` literal | replace with `border-radius: var(--r-s)` |
| Center text padding | figma: text fills flex-1, no explicit padding visible (relies on flex centering) | `padding: 0 8px` literal | replace with `padding: 0 var(--sp-xs)` |
| Center text `min-w-[40px]` | `min-w-[40px]` | `min-width: 0` | repoint canonical to `min-width: 40px` to match figma 真源 (or new token `--input-number-text-min-width: 40px`) |
| Inner divider thickness | `border-r` / `border-l` (1px) | `border-right: 1px / border-left: 1px` literals | OK as 1px is web standard |
| Icon size | `16px` | `width: 16px; height: 16px` literals | extract token only if shared (canonical Icon already supports `size` prop; just pass `:size="16"` — currently uses class). |

### b. Color

| Element | Figma 真源 token | Canonical 现状 | Fix |
| --- | --- | --- | --- |
| Wrapper border | `Color Type/Line/Deep Divider` → `--line-deep` | `border: 1px solid var(--line-deep)` | OK |
| Inner button divider | `--line-deep` (same as wrapper) | `border-right: 1px solid var(--line-deep)` / `border-left: 1px solid var(--line-deep)` | OK |
| Center text — Default/Only Add/Only Reduce | `Color Type/Text/Text_1` → `--text-body` | `color: var(--text-body)` | OK |
| Center text — Readonly | `Color Type/Text/Disable Grey Button` → `--text-grey-dis` (`#7b7b7b`) | `color: var(--text-disabled)` (= `#595959` dark / `#cccccc` light) | **mismatch**: canonical uses `--text-disabled` but figma uses `--text-grey-dis` — different token semantics. Fix: change `.inputnum--readonly .inputnum-input { color: var(--text-grey-dis); }` |
| Button icon — Default/active | figma uses default icon color (likely `--icon-default` `#9e9e9e`) | `color: var(--text-body)` | mismatch — repoint to `var(--icon-default)` (matches figma neutral icon intent) |
| Button icon — disabled state | figma uses `--icon-disabled` (`#595959`) | `color: var(--text-disabled)` | repoint to `var(--icon-disabled)` |
| Button icon — Only Add (minus disabled) | per cache: `img2` (disabled minus) vs `img` (active minus) — distinct asset | implicitly via `:disabled` button attr | OK (canonical disables button) |
| Button icon — Only Reduce (add disabled) | per cache: `img3` (disabled add) vs `img1` (active add) | implicitly via `:disabled` | OK |
| Button hover bg | not in figma cache (no hover state captured) | `background: var(--bg-layer4)` | acceptable runtime addition; document |
| Wrapper bg | transparent | `background: transparent` | OK |

### c. Typography

| Element | Figma 真源 | Canonical | Fix |
| --- | --- | --- | --- |
| Center text | `Helvetica Neue Regular 14px / 16px line-height` | `font-size: 14px; line-height: 16px; font-weight: 400; font-family: inherit` | figma uses `Helvetica Neue` but `--text-style-body` is `Roboto`-family. The cache shows `font-["Helvetica_Neue:Regular",sans-serif]` — this is **inconsistent with figma 真源 typography library** (Roboto only). Likely figma legacy / mistake. **Recommend**: use `--text-style-body` (Roboto) for canonical alignment with the rest of the system; flag the `Helvetica Neue` figma value as a figma cleanup item. Replace inline font with `font: var(--text-style-body);` (note: `--text-style-body` line-height is `21px`, figma cache is `16px`. Atomic fix: `font-size: var(--font-size-body); line-height: 16px; font-weight: var(--font-weight-regular);`) |

Decision: **prefer atomic** because figma 真源 uses tighter `line-height: 16px` (matches button row height for vertical centering). Add `--input-number-text-line-height: 16px` token (rationale: tighter line-height aligns input-number text with control row height).

### d. Effects

No drop shadow / mask. N/A.

### e. Composition

| Aspect | Figma 真源 | Canonical | Fix |
| --- | --- | --- | --- |
| Wrapper | flex row container | `<div class="inputnum">` | OK |
| Buttons | figma renders icon as raster `<img>` | canonical uses `<Icon name="minus|add">` (per `icon-aliases.ts`) | OK — uses canonical Icon component ✅ |
| Center display | figma renders `<p>` (read-only display) | canonical uses `<input type="number">` (editable) | acceptable: canonical extends figma static preview into interactive runtime input |
| Button `tabindex="-1"` | n/a in figma | canonical sets `tabindex="-1"` | runtime UX (number input handles keyboard) — document |

### f. Variant coverage

| Figma variant | Canonical handled? | Notes |
| --- | --- | --- |
| `property1=Default` | ✅ via `canDecrement`/`canIncrement` (both true) | OK |
| `property1=Only Add` | ✅ via `canDecrement = false` when `property1 === 'Only Add'` | OK |
| `property1=Only Reduce` | ✅ via `canIncrement = false` when `property1 === 'Only Reduce'` | OK |
| `property1=Readonly` | ✅ via `isReadonly` (disables both buttons + input) | OK after color repoint |

Runtime additions:

- `modelValue` + `update:modelValue` — Vue ecosystem
- `min`, `max`, `step` — runtime additions (n/a in figma)
- `disabled` — Vue/Web form
- `readonly` — runtime addition
- `figmaDefaultValues` initial values map (`Default=6`, `Only Add=0`, `Only Reduce=10`, `Readonly=6`) — matches figma cache literals `6 / 0 / 10 / 6` ✅

---

## New domain tokens proposed

| Token | Value | Rationale |
| --- | --- | --- |
| `--control-height-xxs` | `32px` | Figma `--size/xxs` is a **control-height** scale (separate from spacing); used by InputNumber wrapper, button squares, also matches Input filled `M` size, FormItem `1 line` row. Worth promoting to a shared token. |
| `--input-number-min-width` | `100px` | Figma 真源 wrapper min-width |
| `--input-number-default-width` | `128px` | Figma 真源 wrapper default width |
| `--input-number-max-width` | `200px` | Figma 真源 wrapper max-width |
| `--input-number-text-min-width` | `40px` | Figma 真源 center text min-width |
| `--input-number-text-line-height` | `16px` | Figma 真源 line-height (tighter than `--line-height-body: 21px` for control alignment) |

Add `--control-height-xxs` to `:root { ... Spacing/sizing scale ... }` section in `variables.css`. Add `--input-number-*` tokens to "Derived aliases for component compatibility" section.

---

## Token repoint summary

| Current literal | Proposed token | Location |
| --- | --- | --- |
| `width: 128px` | `width: var(--input-number-default-width)` | `.inputnum` |
| `min-width: 100px` | `min-width: var(--input-number-min-width)` | `.inputnum` |
| `max-width: 200px` | `max-width: var(--input-number-max-width)` | `.inputnum` |
| `height: 32px` | `height: var(--control-height-xxs)` | `.inputnum` |
| `width: 32px; height: 32px` | `width: var(--control-height-xxs); height: var(--control-height-xxs)` | `.inputnum-btn` |
| `border-radius: 4px` | `border-radius: var(--r-s)` | `.inputnum` |
| `padding: 0 8px` | `padding: 0 var(--sp-xs)` | `.inputnum-input` |
| `min-width: 0` | `min-width: var(--input-number-text-min-width)` | `.inputnum-input` (figma 真源 40px) |
| `font-size: 14px` | `font-size: var(--font-size-body)` | `.inputnum-input` |
| `font-weight: 400` | `font-weight: var(--font-weight-regular)` | `.inputnum-input` |
| `line-height: 16px` | `line-height: var(--input-number-text-line-height)` | `.inputnum-input` |
| `color: var(--text-disabled)` (readonly) | `color: var(--text-grey-dis)` | `.inputnum--readonly .inputnum-input` |
| `color: var(--text-body)` (button icon) | `color: var(--icon-default)` | `.inputnum-btn` |
| `color: var(--text-disabled)` (button disabled) | `color: var(--icon-disabled)` | `.inputnum-btn:disabled` |
| `width: 16px; height: 16px` (`.inputnum-icon`) | replace with `<Icon :size="16">` prop on the actual Icon component | template |

---

## Open items

- Confirm whether `Helvetica Neue` in figma cache is intentional or a figma-side mistake (rest of design system uses Roboto only). Decision affects whether canonical should follow Roboto (recommended) or replicate Helvetica Neue (figma-literal).
- `min` / `max` / `step` runtime props are reasonable additions but not registered in `prop-aliases.md` Vue-ecosystem table for InputNumber. Recommend adding row: `InputNumber | min, max, step | (no Figma counterpart) | runtime addition | numeric clamping/stepping API`.
