srcdev-nuxt-components 9.6.8 → 9.6.10
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/.claude/skills/component-compliance-checklist.md +28 -1
- package/.claude/skills/component-inline-action-button.md +1 -1
- package/.claude/skills/component-local-style-override.md +1 -1
- package/.claude/skills/component-naming.md +17 -12
- package/.claude/skills/components/container-glow.md +2 -2
- package/.claude/skills/components/form-field.md +2 -2
- package/.claude/skills/components/form-wrapper.md +1 -1
- package/.claude/skills/components/glowing-border.md +1 -1
- package/.claude/skills/components/input-description.md +7 -7
- package/.claude/skills/components/input-error.md +3 -3
- package/.claude/skills/components/input-label.md +7 -8
- package/.claude/skills/components/input-number.md +17 -17
- package/.claude/skills/components/input-select.md +153 -0
- package/.claude/skills/components/input-text.md +220 -0
- package/.claude/skills/components/input-textarea.md +147 -0
- package/.claude/skills/components/select-menu.md +4 -4
- package/.claude/skills/components/toggle-switch.md +143 -0
- package/.claude/skills/composable-zod-validation.md +1 -1
- package/.claude/skills/css-nesting-conventions.md +22 -1
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/icon-sizing.md +1 -1
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/storybook-add-story.md +1 -1
- package/.claude/skills/theming-component-token-pattern.md +8 -8
- package/.claude/skills/theming-form-geometry-tokens.md +1 -1
- package/.vscode/srcdev-component-input-select.code-snippets +20 -14
- package/.vscode/srcdev-component-input-text.code-snippets +46 -40
- package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
- package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
- package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
- package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
- package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
- package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
- package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
- package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +6 -6
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +7 -7
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +9 -5
- package/app/components/05.forms/input-number/InputNumber.vue +2 -2
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +80 -10
- package/app/components/05.forms/input-select/InputSelect.vue +198 -0
- package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
- package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
- package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
- package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
- package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
- package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
- package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
- package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
- package/app/components/05.forms/input-text/InputTextField.vue +168 -0
- package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
- package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
- package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
- package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
- package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
- package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
- package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
- package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
- package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
- package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
- package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
- package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
- package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
- package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
- package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +30 -32
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
- package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
- package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
- package/package.json +1 -1
- package/.claude/skills/components/input-select-core.md +0 -151
- package/.claude/skills/components/input-text-core.md +0 -204
- package/.claude/skills/components/input-textarea-core.md +0 -154
- package/.claude/skills/components/toggle-switch-core.md +0 -169
- package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
- package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
- package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
- package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
- package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
- package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
- package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
- package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
- package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
- package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
- package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
- package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
- package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
- package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
|
@@ -25,7 +25,10 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
|
25
25
|
**Existing component:** if it lives outside a tier, move it with `git mv`.
|
|
26
26
|
2. **Props pattern** — `interface Props` + `withDefaults(defineProps<Props>(), {...})`, never
|
|
27
27
|
options-style `defineProps({...})`. Include `styleClassPassthrough` and wire it through
|
|
28
|
-
`useStyleClassPassthrough()
|
|
28
|
+
`useStyleClassPassthrough()`, with a `watch` that calls `resetElementClasses` so it reacts to prop
|
|
29
|
+
changes. A Field wrapper applies its passthrough to its own root only: forwarding the same prop
|
|
30
|
+
to the inner control as well puts one consumer class on two elements (`InputNumberField`,
|
|
31
|
+
`InputRangeField`, fixed 2026-10-16). Consumer-facing data-shape types go in
|
|
29
32
|
`app/types/components/<component-name>.d.ts` and are exported from `app/types/components/index.ts`
|
|
30
33
|
(see `component-export-types.md`), not left inline in the `.vue` file.
|
|
31
34
|
3. **CSS tokens** — audit every custom property in the component's `<style>` block against the
|
|
@@ -193,9 +196,18 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
|
193
196
|
- A toast, banner or status message doesn't `.focus()` itself when it appears (rule
|
|
194
197
|
`consumer/a11y-no-focus-steal.md`).
|
|
195
198
|
- Focus is visible (`:focus-visible`, not a suppressed outline) on anything focusable.
|
|
199
|
+
- `aria-invalid` is bound as `fieldHasError ? 'true' : undefined`, never the boolean itself:
|
|
200
|
+
`:aria-invalid="fieldHasError"` renders `aria-invalid="false"` on every valid field
|
|
201
|
+
(`InputTextarea`, `ToggleSwitch`, fixed 2026-10-16). Same for an empty `aria-describedby`.
|
|
202
|
+
- A checkbox drawn as an on/off switch has `role="switch"`, and its decorative track is
|
|
203
|
+
`aria-hidden` (`ToggleSwitch`, 2026-10-16).
|
|
196
204
|
- Live-updating or auto-advancing content (carousels, marquees, auto-dismissing toasts) has a
|
|
197
205
|
way to pause it, per WCAG 2.2.2 — a visible control, or pause-on-hover/focus at minimum.
|
|
198
206
|
- `prefers-reduced-motion` is respected for any animation that isn't purely decorative.
|
|
207
|
+
- A mode that hides a native control's own indicator (a range thumb, a checkbox box, a caret)
|
|
208
|
+
draws its own visible state for the current value, and its interactive area isn't shrunk to a
|
|
209
|
+
sliver. `InputRange`'s markers mode hid the thumb with nothing in its place and left only a
|
|
210
|
+
2px line clickable, from its first commit until 2026-10-16; its 5/5 score didn't show it.
|
|
199
211
|
- A component with a `tag` prop that can render a landmark uses `useAriaLabelledById`
|
|
200
212
|
(`component-aria-landmark.md`), unless it falls back to `aria-label` when no heading is
|
|
201
213
|
given: the composable can't do that and warns whenever a landmark has no heading. Then label
|
|
@@ -268,6 +280,21 @@ The ledger can't see items 6c, 9, 10, 11 and 14, so check those by hand.
|
|
|
268
280
|
defaults to its min-content width and overflows a narrow column (`MultipleCheckboxes`,
|
|
269
281
|
2026-10-16).
|
|
270
282
|
- Element ids come from `useId()`, never from data values (rule `consumer/ids-from-useid.md`).
|
|
283
|
+
- A wrapper passes its child only props the child declares. Anything else falls through as an
|
|
284
|
+
HTML attribute on the child's root: `InputTextField` put `label="…"` and
|
|
285
|
+
`error-message="…"` on `.input-text-wrapper` (fixed 2026-10-16). Check the child root's
|
|
286
|
+
attributes in a test.
|
|
287
|
+
- A control styled with `all: unset` restores the UA defaults it depends on. On a `<textarea>`
|
|
288
|
+
that reset `white-space: pre-wrap` (inherited, so it took the page's `normal`); set
|
|
289
|
+
`white-space: pre-wrap` and `overflow-wrap: anywhere` explicitly (`InputTextarea`,
|
|
290
|
+
2026-10-16).
|
|
291
|
+
- A state rule (`[data-checked]`) written at the root level loses to deeper nested base rules
|
|
292
|
+
for the same element. Nest it in the element's own block as `.root[data-x] & { … }`
|
|
293
|
+
(`ToggleSwitch`, 2026-10-16; `css-nesting-conventions.md`).
|
|
294
|
+
- Step buttons and any other script that changes a number round to the step's precision
|
|
295
|
+
(`toFixed`), clamp to `min`/`max`, and compare limits with `<=`/`>=`, never `===` (0.1 steps
|
|
296
|
+
drift to 0.30000000000000004, and an overshoot past an `===` check never disables the button).
|
|
297
|
+
Reference: `stepBy()` in `InputNumberField`.
|
|
271
298
|
- Layout measured in script (connector lines, indicators) re-measures when any measured item
|
|
272
299
|
resizes or changes class, not only when the container resizes (`measured-layout-observers.md`,
|
|
273
300
|
pitfall #28).
|
|
@@ -105,6 +105,6 @@ changed visible label ("Copied!") isn't announced, because the aria-label wins;
|
|
|
105
105
|
|
|
106
106
|
## Real examples
|
|
107
107
|
|
|
108
|
-
- `
|
|
108
|
+
- `InputPasswordField`: `app/components/05.forms/input-text/InputPasswordField.vue` (show/hide toggle attached to the input)
|
|
109
109
|
- `CookieConsentBanner`: `app/components/02.molecules/cookie-consent-banner/CookieConsentBanner.vue`
|
|
110
110
|
(standalone Accept/Reject actions, not attached to an input)
|
|
@@ -165,7 +165,7 @@ Use when the same component appears multiple times on a page and you need to tar
|
|
|
165
165
|
instance.
|
|
166
166
|
|
|
167
167
|
If the component you're rendering is a **wrapper** that doesn't forward `styleClassPassthrough` to
|
|
168
|
-
the inner component you want to style (e.g. `InputDescription` inside `
|
|
168
|
+
the inner component you want to style (e.g. `InputDescription` inside `InputTextField`), put a
|
|
169
169
|
plain `class` on the wrapper instead. Vue falls it through to the wrapper's root element, which is
|
|
170
170
|
an ancestor of the inner component, so tokens set there inherit down. This needs the wrapper to
|
|
171
171
|
have a single root element and not set `inheritAttrs: false`.
|
|
@@ -60,7 +60,12 @@ stripping `Core`.
|
|
|
60
60
|
control name).
|
|
61
61
|
2. `sed` the old names across `app/`, `.claude/`, `.vscode/`, `MIGRATION.md` and `CLAUDE.md`:
|
|
62
62
|
component names, `import` paths, story titles/`*StoryArgs` interfaces, `describe()` names, and the
|
|
63
|
-
root/base DOM class (update `CONSUMER-STYLING.md` and test selectors that use it).
|
|
63
|
+
root/base DOM class (update `CONSUMER-STYLING.md` and test selectors that use it). Match on word
|
|
64
|
+
boundaries (`\bToggleSwitchCore\b`): a bare `ToggleSwitchCore` also matched inside
|
|
65
|
+
`TripleToggleSwitchCore` and rewrote its history notes (2026-10-16). Leave out the gitignored
|
|
66
|
+
`.claude/settings.json` (its old allowlist entries match too) and any dated "Renamed"/"Changed"
|
|
67
|
+
notes, and rename a removed wrapper by hand before the bulk pass, or `XWithLabel` → `XField`
|
|
68
|
+
turns `XWithLabelInline` into a name that never existed.
|
|
64
69
|
3. Add a "Renamed YYYY-MM-DD" note at the top of the skill doc listing old → new names and classes.
|
|
65
70
|
4. Grep consumer repos (`/check-component-usage`) and **list** the files/lines that use the old
|
|
66
71
|
names (in the private consumer notes, never in this repo). Don't edit the consumers yet: they pin
|
|
@@ -79,19 +84,19 @@ external consumer, free to rename. Run `/check-component-usage` for the current
|
|
|
79
84
|
|---------|----------|-----------|
|
|
80
85
|
| ~~InputNumberCore~~ | ✅ `InputNumber` (2026-09-25) | - |
|
|
81
86
|
| ~~InputNumberDefault~~ | ✅ `InputNumberField` (2026-09-25) | - |
|
|
82
|
-
| InputTextCore | `InputText` | yes |
|
|
83
|
-
| InputTextWithLabel | `InputTextField` | yes |
|
|
84
|
-
| InputPasswordWithLabel | `InputPasswordField` | yes |
|
|
85
|
-
| InputTextAsNumberWithLabel | `InputTextAsNumberField` | - |
|
|
87
|
+
| ~~InputTextCore~~ | ✅ `InputText` (2026-10-16) | yes |
|
|
88
|
+
| ~~InputTextWithLabel~~ | ✅ `InputTextField` (2026-10-16) | yes |
|
|
89
|
+
| ~~InputPasswordWithLabel~~ | ✅ `InputPasswordField` (2026-10-16) | yes |
|
|
90
|
+
| ~~InputTextAsNumberWithLabel~~ | ✅ `InputTextAsNumberField` (2026-10-16) | - |
|
|
86
91
|
| ~~InputRangeCore~~ | ✅ `InputRange` (2026-10-16) | - |
|
|
87
92
|
| ~~InputRangeDefault~~ | ✅ `InputRangeField` (2026-10-16) | - |
|
|
88
|
-
| InputTextareaCore | `InputTextarea` | - |
|
|
89
|
-
| InputTextareaWithLabel | `InputTextareaField` | yes |
|
|
90
|
-
| InputSelectCore | `InputSelect` | yes |
|
|
91
|
-
| InputSelectWithLabel | `InputSelectField` | yes |
|
|
92
|
-
| ToggleSwitchCore | `ToggleSwitch` | yes |
|
|
93
|
-
| ToggleSwitchWithLabel | `ToggleSwitchField` | yes |
|
|
94
|
-
| ToggleSwitchWithLabelInline | `layout`
|
|
93
|
+
| ~~InputTextareaCore~~ | ✅ `InputTextarea` (2026-10-16) | - |
|
|
94
|
+
| ~~InputTextareaWithLabel~~ | ✅ `InputTextareaField` (2026-10-16) | yes |
|
|
95
|
+
| ~~InputSelectCore~~ | ✅ `InputSelect` (2026-10-16) | yes |
|
|
96
|
+
| ~~InputSelectWithLabel~~ | ✅ `InputSelectField` (2026-10-16) | yes |
|
|
97
|
+
| ~~ToggleSwitchCore~~ | ✅ `ToggleSwitch` (2026-10-16) | yes |
|
|
98
|
+
| ~~ToggleSwitchWithLabel~~ | ✅ `ToggleSwitchField` (2026-10-16) | yes |
|
|
99
|
+
| ~~ToggleSwitchWithLabelInline~~ | ✅ `layout="inline"` on `ToggleSwitchField` (2026-10-16) | - |
|
|
95
100
|
| ~~TripleToggleSwitchCore~~ | ✅ `TripleToggleSwitch` (2026-09-27) | - |
|
|
96
101
|
| ~~InputButtonCore~~ | ✅ `InputButton` (2026-09-28) | yes |
|
|
97
102
|
| ~~InputCheckboxRadioCore~~ | ✅ `InputCheckboxRadio` (2026-09-27) | - |
|
|
@@ -96,8 +96,8 @@ apply instantly instead of fading).
|
|
|
96
96
|
## Notes
|
|
97
97
|
|
|
98
98
|
- 2026-09-20 migration: renamed from `ContainerGlowCore` to `ContainerGlow` — the `Core` suffix in
|
|
99
|
-
this library denotes a primitive with a non-Core wrapper built on top of it (`InputTextCore` →
|
|
100
|
-
`
|
|
99
|
+
this library denotes a primitive with a non-Core wrapper built on top of it (`InputTextCore` → `InputTextWithLabel`, since renamed;
|
|
100
|
+
`AlertMaskCore` → `AlertMaskedContent`), and this component has no such
|
|
101
101
|
sibling. Checked all consumer repos first; found one real usage in a defunct
|
|
102
102
|
demo app, left as a follow-up for that repo rather than edited here.
|
|
103
103
|
- Moved from `app/components/container-glow/` (unplaced) into
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
field's width (`narrow`/`medium`/`wide`), centres it with `margin-inline: auto`, optionally insets
|
|
7
7
|
it from its container edges with a gutter, and applies the shared vertical field spacing
|
|
8
8
|
(`--field-margin-block`). It renders no label, input or error text itself: put an `*Field`
|
|
9
|
-
component (`
|
|
9
|
+
component (`InputTextField`, `InputNumberField`, `InputSelectField`, ...) in its default
|
|
10
10
|
slot.
|
|
11
11
|
|
|
12
12
|
Typical use inside `FormWrapper` / a pattern form: `<FormField width="wide" :has-gutter="false">`
|
|
@@ -37,7 +37,7 @@ around each control, so the form's own container controls the width.
|
|
|
37
37
|
|
|
38
38
|
```vue
|
|
39
39
|
<FormField width="wide" :has-gutter="false" :field-has-error="!!errors.email">
|
|
40
|
-
<
|
|
40
|
+
<InputTextField
|
|
41
41
|
v-model="state.email"
|
|
42
42
|
name="email"
|
|
43
43
|
label="Email"
|
|
@@ -50,7 +50,7 @@ stops are public CSS custom properties — see `CONSUMER-STYLING.md` for the ful
|
|
|
50
50
|
|
|
51
51
|
## Highlighting a single control
|
|
52
52
|
|
|
53
|
-
Wrap just an `InputButton` or `
|
|
53
|
+
Wrap just an `InputButton` or `InputText` so the glow becomes the control's border (a design
|
|
54
54
|
call-out for something "special"). Set `--glowing-border-radius` to the control's radius plus the
|
|
55
55
|
glow width, `--glowing-border-surface: transparent`, `--glowing-border-overflow: visible` (or the
|
|
56
56
|
control's focus ring is clipped), shrink-to-fit display, and `--input-text-border: transparent` for
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
5
|
`InputDescription` renders a form field's help text. Every `*Field` wrapper in `05.forms`
|
|
6
|
-
(`
|
|
7
|
-
`InputRangeField`, `
|
|
6
|
+
(`InputTextField`, `InputNumberField`, `InputSelectField`, `InputTextareaField`,
|
|
7
|
+
`InputRangeField`, `InputTextAsNumberField`, `SingleCheckbox`, `MultipleCheckboxes`,
|
|
8
8
|
`MultipleRadiobuttons`) already renders one and forwards its own `descriptionText` /
|
|
9
9
|
`descriptionHtml` slots into it, so consumers normally just pass those slots to the wrapper:
|
|
10
10
|
|
|
11
11
|
```vue
|
|
12
|
-
<
|
|
12
|
+
<InputTextField v-model="email" name="email" label="Email" :field-has-error :error-message>
|
|
13
13
|
<template #descriptionText>We'll only use this for your booking confirmation</template>
|
|
14
|
-
</
|
|
14
|
+
</InputTextField>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Use `InputDescription` directly only when building a custom field wrapper.
|
|
@@ -54,7 +54,7 @@ otherwise `InputDescription` sees an (empty) slot and renders an empty element:
|
|
|
54
54
|
|
|
55
55
|
- It's a plain description, not a live region: link it with `aria-describedby` on the control
|
|
56
56
|
(`useAriaDescribedById` does this in every wrapper).
|
|
57
|
-
- `
|
|
57
|
+
- `InputTextField`, `InputSelectField` and `InputTextareaField` render it **after**
|
|
58
58
|
the control for the `outlined` variant and before it otherwise; both positions carry the same id.
|
|
59
59
|
|
|
60
60
|
---
|
|
@@ -81,9 +81,9 @@ look, zero the slot margins and space it with the root margins (example in CONSU
|
|
|
81
81
|
- Wrappers forwarded both description slots unconditionally, so every field rendered an empty
|
|
82
82
|
description element (with its id) even when no description was given. Each wrapper now forwards
|
|
83
83
|
a slot only when its own consumer supplied it.
|
|
84
|
-
- `
|
|
84
|
+
- `InputTextField` and `InputSelectField` rendered the description **twice** for the
|
|
85
85
|
`outlined` variant (before and after the control); the first copy now has
|
|
86
|
-
`v-if="inputVariant !== 'outlined'"`, matching `
|
|
86
|
+
`v-if="inputVariant !== 'outlined'"`, matching `InputTextareaField`.
|
|
87
87
|
- The outlined-variant copies had no `description-id`, so the control's `aria-describedby`
|
|
88
88
|
pointed at an id that didn't exist. They now pass it.
|
|
89
89
|
- Removed props that were never read: `id` (a leftover from a commented-out id computation) and
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
5
|
`InputError` renders the red error message strip under a form control. Every `*Field` wrapper in
|
|
6
|
-
`05.forms` (`
|
|
7
|
-
`InputRangeField`, `
|
|
8
|
-
`MultipleRadiobuttons`, `
|
|
6
|
+
`05.forms` (`InputTextField`, `InputNumberField`, `InputSelectField`, `InputTextareaField`,
|
|
7
|
+
`InputRangeField`, `ToggleSwitchField`, `SingleCheckbox`, `MultipleCheckboxes`,
|
|
8
|
+
`MultipleRadiobuttons`, `InputTextAsNumberField`) already renders one, so consumers rarely use
|
|
9
9
|
it directly. Use it yourself only when building a custom field wrapper.
|
|
10
10
|
|
|
11
11
|
It is designed to sit in **grid row 2** of the wrapper, directly under the control in row 1: it is
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
5
|
`InputLabel` renders a form field's `<label>`. Every labelled wrapper in `05.forms`
|
|
6
|
-
(`
|
|
7
|
-
`
|
|
8
|
-
`ToggleSwitchWithLabelInline`) already renders one from its `label` prop, so consumers normally
|
|
6
|
+
(`InputTextField`, `InputTextAsNumberField`, `InputNumberField`, `InputSelectField`,
|
|
7
|
+
`InputTextareaField`, `InputRangeField`, `ToggleSwitchField`) already renders one from its `label` prop, so consumers normally
|
|
9
8
|
never use it directly.
|
|
10
9
|
|
|
11
10
|
Use `InputLabel` directly only when building a custom field wrapper:
|
|
@@ -24,7 +23,7 @@ Use `InputLabel` directly only when building a custom field wrapper:
|
|
|
24
23
|
|
|
25
24
|
| Prop (template form) | Type | Default | Notes |
|
|
26
25
|
|------|------|---------|-------|
|
|
27
|
-
| `:id` | `string` | required | The **control's** id, rendered as the label's `for`. The label itself gets no id. Must match the id actually on the `<input>`/`<select>`/`<textarea>` (e.g. `
|
|
26
|
+
| `:id` | `string` | required | The **control's** id, rendered as the label's `for`. The label itself gets no id. Must match the id actually on the `<input>`/`<select>`/`<textarea>` (e.g. `ToggleSwitch` prefixes its input id with `toggle-switch-`, so its wrappers pass `toggleSwitchId`). |
|
|
28
27
|
| `:required` | `boolean` | `false` | Whether the field is required. Only decides which fields the indicator marks; it doesn't set `required` on the control. Wrappers forward their own `required`. |
|
|
29
28
|
| `:indicator` | `"none" \| "required" \| "optional"` | `undefined` → app.config → `"none"` | Which fields get a marker. |
|
|
30
29
|
| `:required-text` | `string` | `undefined` → app.config → `"*"` | Required marker text. |
|
|
@@ -61,9 +60,9 @@ export default defineAppConfig({
|
|
|
61
60
|
|
|
62
61
|
- `"required"` mode: a marker on fields with `required`; `"optional"` mode: a marker on fields
|
|
63
62
|
without it; `"none"`: nothing (default, so existing sites are unchanged).
|
|
64
|
-
- `
|
|
65
|
-
`
|
|
66
|
-
`
|
|
63
|
+
- `InputTextField`, `InputTextAsNumberField`, `InputNumberField`, `InputSelectField`,
|
|
64
|
+
`InputTextareaField` and `InputRangeField` forward `:required`.
|
|
65
|
+
`ToggleSwitchField` pass `indicator="none"`: a switch always
|
|
67
66
|
has a value, so neither marker means anything there.
|
|
68
67
|
- The wrappers don't expose the indicator props per field; use `app.config`.
|
|
69
68
|
- Text props are the i18n hook: pass translated `optionalText`/`requiredText` (or set them in
|
|
@@ -101,7 +100,7 @@ Hooks: `data-input-variant`, `data-invalid`, `.input-label-indicator[data-indica
|
|
|
101
100
|
- Removed props that were never read: `name` and `theme`. Wrappers stopped passing them.
|
|
102
101
|
- Wrappers also passed a non-prop `:for`, which fell through and overrode the `for` built from
|
|
103
102
|
`id`. For the text/number/select/textarea/range wrappers it was the same value (dropped). For
|
|
104
|
-
`
|
|
103
|
+
`ToggleSwitchField` it was the only thing pointing the label
|
|
105
104
|
at the real input (`toggle-switch-<id>`); they now pass `:id="toggleSwitchId"` explicitly.
|
|
106
105
|
- `inputVariant` moved from a bare class (`.normal`/`.underlined`, whose rules were duplicates of
|
|
107
106
|
the base rule) to `data-input-variant`. `fieldHasError` (accepted but unused) now renders
|
|
@@ -30,7 +30,7 @@ Most consumers should reach for **InputNumberField** rather than `InputNumber` d
|
|
|
30
30
|
| `:required` | `boolean` | `false` | Native `required`. |
|
|
31
31
|
| `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
|
|
32
32
|
| `:weight` | `FormWeight` | `"normal"` | Adds an `input-number--{weight}` class to the input, for prop-shape consistency with sibling `05.forms` components. **No CSS in the library currently styles this class — it's a no-op today**, same convention as `InputRange`'s `weight` prop. |
|
|
33
|
-
| `:input-variant` | `InputUiVariant` | `"normal"` | Rendered as `data-input-variant` on the wrapper (a bare class until 2026-10-16) — `"normal"` (bordered box) and `"underlined"` (bottom-border only) have CSS; `"outlined"` doesn't (see CONSUMER-STYLING.md). Same prop/behaviour as `
|
|
33
|
+
| `:input-variant` | `InputUiVariant` | `"normal"` | Rendered as `data-input-variant` on the wrapper (a bare class until 2026-10-16) — `"normal"` (bordered box) and `"underlined"` (bottom-border only) have CSS; `"outlined"` doesn't (see CONSUMER-STYLING.md). Same prop/behaviour as `InputText`. |
|
|
34
34
|
| `:field-has-error` | `boolean` | `false` | Drives `data-invalid` on the wrapper. |
|
|
35
35
|
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the input element. Reacts to changes. |
|
|
36
36
|
| `:aria-describedby` | `string` | `""` | Forwarded to the native input. |
|
|
@@ -57,7 +57,7 @@ slot is present (`.input-number-slot-left .input-button { border-right: ... }` a
|
|
|
57
57
|
right-hand mirror), so it sits flush against the button's own edge rather than around the
|
|
58
58
|
containing `.input-number-slot` div. This makes step buttons read as embedded segments of the same control
|
|
59
59
|
rather than separate floating buttons. The divider reuses the wrapper's own border colour token
|
|
60
|
-
(no separate divider token), same as `
|
|
60
|
+
(no separate divider token), same as `InputText`/`InputTextAsNumberField`.
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
@@ -117,7 +117,7 @@ score-1/5 state. Several latent bugs were fixed alongside the standard checklist
|
|
|
117
117
|
|
|
118
118
|
---
|
|
119
119
|
|
|
120
|
-
## Fixed 2026-09-22 (follow-up): rewritten to visually mirror
|
|
120
|
+
## Fixed 2026-09-22 (follow-up): rewritten to visually mirror InputText
|
|
121
121
|
|
|
122
122
|
The first migration pass above only made `InputNumber`'s *own* (pre-existing, previously dead)
|
|
123
123
|
CSS active — it didn't reconsider whether that CSS was actually the right design. Once live, it
|
|
@@ -125,21 +125,21 @@ was visually inconsistent with the rest of `05.forms`: no border-radius, no hove
|
|
|
125
125
|
state, wrong padding-token family (`--form-textarea-padding-*` instead of the shared input
|
|
126
126
|
padding tokens), and a box-shadow-toggle focus mechanism instead of the `:has()`-based outline
|
|
127
127
|
approach every other input in this library uses. `InputNumber` is meant to be a native
|
|
128
|
-
`<input type="number">` sibling to the text-based `
|
|
129
|
-
`input-text
|
|
130
|
-
to mirror `
|
|
128
|
+
`<input type="number">` sibling to the text-based `InputTextAsNumberField` variant (see
|
|
129
|
+
`input-text.md`'s Variants section) — both should look and behave the same. The CSS was rewritten
|
|
130
|
+
to mirror `InputText` directly:
|
|
131
131
|
|
|
132
|
-
- Added `:input-variant` (`"normal"` / `"outlined"` / `"underlined"`), matching `
|
|
132
|
+
- Added `:input-variant` (`"normal"` / `"outlined"` / `"underlined"`), matching `InputText`'s
|
|
133
133
|
prop and wrapper-class shape (`InputNumberField` forwards it the same way
|
|
134
|
-
`
|
|
135
|
-
- Replaced the flat, unconditional border/radius/box-shadow with `
|
|
134
|
+
`InputTextField` forwards it to `InputText`, including to `InputLabel`).
|
|
135
|
+
- Replaced the flat, unconditional border/radius/box-shadow with `InputText`'s `.normal`
|
|
136
136
|
variant shape: border + `border-radius: var(--form-input-border-radius)` (previously this read
|
|
137
137
|
`var(--form-element-border-width)` — a pre-existing bug that used the *border-width* token as
|
|
138
138
|
the *radius*, coincidentally invisible while the block was dead), plus `:has(input:hover)` /
|
|
139
139
|
`:has(input:focus-visible)` outline states instead of the old `--_focus-box-shadow` custom
|
|
140
140
|
property toggle.
|
|
141
141
|
- `--input-number-border-hover` and `--input-number-border-focus` are new public tokens for
|
|
142
|
-
these two states (both fall back to `--theme-border-focus`, same as `
|
|
142
|
+
these two states (both fall back to `--theme-border-focus`, same as `InputText`).
|
|
143
143
|
- Padding moved from `--form-textarea-padding-*` (the wrong token family — textarea, not input)
|
|
144
144
|
to the shared `--input-padding-block`/`--input-padding-inline`/`--input-min-height` tokens every
|
|
145
145
|
other `05.forms` text-like input uses.
|
|
@@ -148,7 +148,7 @@ to mirror `InputTextCore` directly:
|
|
|
148
148
|
previously it had neither, an accessibility gap beyond the visual mismatch (checklist item 9).
|
|
149
149
|
- The divider between the input and an embedded button now reuses the border colour
|
|
150
150
|
(`--_border`) directly instead of a separate `--input-number-divider-color` token — removed,
|
|
151
|
-
since `
|
|
151
|
+
since `InputText` doesn't have an equivalent separate token either (the border colour
|
|
152
152
|
*is* the divider colour there too).
|
|
153
153
|
- `--_border-hover` and `--_surface-hover` were each used only once with no composition or
|
|
154
154
|
state-swap, so per the public-token rule they were inlined directly
|
|
@@ -158,21 +158,21 @@ to mirror `InputTextCore` directly:
|
|
|
158
158
|
wrapper's own focus outline *and* the embedded button's focus outline both read it).
|
|
159
159
|
- Fixed 2026-10-04: the wrapper's hover and focus outlines only matched `input:hover` /
|
|
160
160
|
`input:focus-visible`, so hovering or tabbing to a step button showed no outer ring, unlike
|
|
161
|
-
`
|
|
162
|
-
selectors are now `:has(input:…, button:…)`, mirroring `
|
|
161
|
+
`InputText` (and so `InputTextAsNumberField`), which matches `button` too. Both
|
|
162
|
+
selectors are now `:has(input:…, button:…)`, mirroring `InputText`.
|
|
163
163
|
|
|
164
164
|
**Separately found in the same investigation** (not an `InputNumber` bug, but what the visual
|
|
165
|
-
comparison was originally checking against): `
|
|
166
|
-
unrelated pre-existing bug — see `input-text
|
|
165
|
+
comparison was originally checking against): `InputTextAsNumberField.vue` had its own,
|
|
166
|
+
unrelated pre-existing bug — see `input-text.md`'s Variants section for the fix.
|
|
167
167
|
|
|
168
168
|
**Correction, same day**: the first version of this rewrite still didn't visually match — the
|
|
169
169
|
divider border and button sizing (`aspect-ratio: 1`, `width: var(--input-min-height)`) had been
|
|
170
170
|
put on the `.left-slot`/`.right-slot` *wrapper divs*, not on `.input-button` itself, unlike
|
|
171
|
-
`
|
|
171
|
+
`InputText`'s equivalent block. That left an unstyled gap between the button and the border
|
|
172
172
|
line instead of a flush edge, and the buttons weren't sized to match the input's height. Fixed by
|
|
173
173
|
moving the divider border and all button sizing/colour rules onto `.input-button` directly
|
|
174
174
|
(`&.has-left-slot .left-slot .input-button { border-right: ... }`, matching
|
|
175
|
-
`
|
|
175
|
+
`InputTextAsNumberField`'s `.left-slot .input-button { border-right: ... }` exactly)
|
|
176
176
|
rather than the containing `.slot` div. When mirroring another component's CSS structure, check
|
|
177
177
|
*which element* a rule targets, not just that an equivalent rule exists somewhere in the block —
|
|
178
178
|
a border/sizing rule one level off in the DOM tree produces a visually-close-but-not-matching
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# InputSelect Component
|
|
2
|
+
|
|
3
|
+
> **Renamed 2026-10-16**: `InputSelectCore` → `InputSelect`, `InputSelectWithLabel` →
|
|
4
|
+
> `InputSelectField` (DOM classes `.input-select-core` → `.input-select`,
|
|
5
|
+
> `.input-select-with-label` → `.input-select-field`; full class list in CONSUMER-STYLING.md).
|
|
6
|
+
> Skill doc renamed from `input-select-core.md`. A consumer app uses both old names; list its edits
|
|
7
|
+
> privately and apply them when it upgrades.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`InputSelect` is the low-level native `<select>` primitive for the `05.forms` select family.
|
|
12
|
+
It renders a native select with a data-driven list of `<option>`s (each optionally decorated with
|
|
13
|
+
an icon), an optional placeholder option, and adopts the modern CSS Customizable Select API
|
|
14
|
+
(`appearance: base-select`) for a fully styleable open-picker state where supported, with a
|
|
15
|
+
graceful CSS fallback (see "Browser support" below). It has no label, description, or
|
|
16
|
+
error-message rendering of its own — that's composed by the `InputSelectField` variant below.
|
|
17
|
+
|
|
18
|
+
Most consumers should reach for **InputSelectField** rather than `InputSelect` directly.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Props reference
|
|
23
|
+
|
|
24
|
+
> **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:field-has-error`, `:style-class-passthrough`, `:aria-describedby`.
|
|
25
|
+
|
|
26
|
+
| Prop (template form) | Type | Default | Notes |
|
|
27
|
+
|------|------|---------|-------|
|
|
28
|
+
| `:id` | `string` | (required) | Applied to the native select. |
|
|
29
|
+
| `:name` | `string` | (required) | Applied to the native select. |
|
|
30
|
+
| `:placeholder` | `string` | `""` | Renders a disabled placeholder `<option>`, selected while `modelValue` is `""`, `null` or `undefined` (not `0`). No option rendered at all when unset. |
|
|
31
|
+
| `:title` | `string` | `"Please select an option"` | Native `title` attribute (tooltip). Visitor-facing copy: pass a translated string. |
|
|
32
|
+
| `:required` | `boolean` | `false` | Native `required`. |
|
|
33
|
+
| `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
|
|
34
|
+
| `:input-variant` | `InputUiVariant` | `"normal"` | Sets `data-input-variant` on the wrapper. `"normal"` (bordered box) and `"underlined"` (bottom border only) have CSS; `"outlined"` doesn't. |
|
|
35
|
+
| `:field-has-error` | `boolean` | `false` | `data-invalid` on the wrapper and `aria-invalid="true"` on the select. |
|
|
36
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the select element. Reacts to changes. |
|
|
37
|
+
| `:aria-describedby` | `string` | `""` | Forwarded to the native select; omitted when empty. |
|
|
38
|
+
|
|
39
|
+
### v-models
|
|
40
|
+
|
|
41
|
+
- `v-model` — `string | number | readonly number[]`, **required** (`defineModel({ required: true })`, satisfying `vue/require-default-prop` — see `project_ledger_eslint_issues_column` in memory).
|
|
42
|
+
- `v-model:field-data` — `IFormMultipleOptions` (`{ data: IOptionsConfig[]; total; skip; limit }`), **required**. The `data` array drives the rendered `<option>`s; only `data` is read. The template dereferences `fieldData.data` unconditionally, so a consumer that doesn't bind it crashes on render. A one-way `:field-data` works too: the component never writes it.
|
|
43
|
+
- `v-model:is-dirty` — `boolean`. Set to `true` on the native `change` event (never reset to `false` by this component). Reflected as `data-dirty` on the wrapper.
|
|
44
|
+
- `v-model:is-active` — `boolean`. Tracks native focus/blur (`focusin`/`focusout`). Reflected as `data-active` on the wrapper.
|
|
45
|
+
|
|
46
|
+
### Option icons
|
|
47
|
+
|
|
48
|
+
An `IOptionsConfig` item with `icon` renders an `<Icon class="input-select-option-icon">`
|
|
49
|
+
(`aria-hidden`) before its label. It only shows where `appearance: base-select` is supported; a
|
|
50
|
+
native dropdown renders option text only. Labels render as text, never as markup.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## CSS token API
|
|
55
|
+
|
|
56
|
+
See [CONSUMER-STYLING.md](../../app/components/05.forms/input-select/CONSUMER-STYLING.md) for the
|
|
57
|
+
full table with defaults and the state hooks.
|
|
58
|
+
|
|
59
|
+
**Common tokens:**
|
|
60
|
+
|
|
61
|
+
- `--input-select-surface` / `-surface-hover` — wrapper/select background and option-row hover background
|
|
62
|
+
- `--input-select-text-color` — select text colour
|
|
63
|
+
- `--input-select-border` / `-border-hover` / `-border-focus` — border colour resting, mouse-hover outline, and `:focus-visible` outline (also the open picker's border/outline)
|
|
64
|
+
- `--input-select-option-gap` / `-option-icon-size` — option icon spacing and size
|
|
65
|
+
|
|
66
|
+
**State hooks** (on `.input-select-wrapper`): `data-input-variant`, `data-theme`, `data-invalid`,
|
|
67
|
+
`data-dirty`, `data-active`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Browser support: `appearance: base-select`
|
|
72
|
+
|
|
73
|
+
The open-picker styling (`&::picker(select)`, `:open`/`:not(:open)` states, the transition/opacity
|
|
74
|
+
`@starting-style` reveal) relies on the CSS Customizable Select API
|
|
75
|
+
(`appearance: base-select`/`::picker(select)`), a very new feature not yet supported everywhere.
|
|
76
|
+
`appearance: none` is set first, then overridden by `appearance: base-select` only where the
|
|
77
|
+
browser recognises that value, so elsewhere (including Safari 16) the select falls back to the
|
|
78
|
+
browser's native dropdown with full functionality. This is decorative degradation, not a
|
|
79
|
+
functional break like `ExpandingPanel`'s `::details-content` case (pitfall #19), but the
|
|
80
|
+
open-picker styling should still be spot-checked on a real Safari session before treating it as
|
|
81
|
+
visually final.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Changed 2026-10-16: rename, state hooks, stress fixes
|
|
86
|
+
|
|
87
|
+
- Renamed to the Control/Field convention (see the note at the top).
|
|
88
|
+
- State classes (`.normal`/`.underlined`/`.outlined`, `.dirty`, `.active`, `.error`) became data
|
|
89
|
+
attributes, so a consumer's unlayered `.error` rule can't restyle the field.
|
|
90
|
+
- The placeholder option used `:selected="!modelValue"`, so a model value of `0` also marked the
|
|
91
|
+
placeholder selected. It now checks for an empty value only.
|
|
92
|
+
- `aria-describedby=""` was rendered when no description id was passed; it's now omitted.
|
|
93
|
+
- `styleClassPassthrough` now reacts to prop changes (it was read once at setup).
|
|
94
|
+
- `InputSelectField` applied its passthrough to the inner select; it now applies it to its own
|
|
95
|
+
root only. It also gained a `title` prop (forwarded), and its root is `.input-select-field`
|
|
96
|
+
itself rather than an unclassed outer `<div>` around it.
|
|
97
|
+
- New public tokens: `--input-select-text-color`, `--input-select-option-gap`,
|
|
98
|
+
`--input-select-option-icon-size`.
|
|
99
|
+
- Long option labels wrap in the open picker, and the Field wraps long label, description and
|
|
100
|
+
error copy. `StressTest` stories on both components.
|
|
101
|
+
|
|
102
|
+
## Fixed 2026-09-22: dead functionality and prop-shape bugs
|
|
103
|
+
|
|
104
|
+
Migrated from a `variants:true`, `eslint_issues:true`, score-2/5 state.
|
|
105
|
+
|
|
106
|
+
- `required` and `styleClassPassthrough` were declared but never bound.
|
|
107
|
+
- `isDirty`/`isActive` were read but nothing set them; `@focusin`/`@focusout`/`@change` added.
|
|
108
|
+
- The placeholder option used `readonly` (not valid on `<option>`); switched to `disabled`.
|
|
109
|
+
- `theme`/`inputVariant` moved to the shared `FormUiTheme`/`InputUiVariant` types; `fieldData`
|
|
110
|
+
moved from an `as Ref<T>` cast to `defineModel<T>("fieldData", { required: true })`.
|
|
111
|
+
- Dead commented-out CSS and single-use private tokens removed (pitfall #20).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Variants
|
|
116
|
+
|
|
117
|
+
### InputSelectField
|
|
118
|
+
|
|
119
|
+
`InputSelectField` (`InputSelectField.vue`) composes `InputSelect` with `InputLabel`,
|
|
120
|
+
`InputDescription` (before the control, or after the control and error when
|
|
121
|
+
`inputVariant === "outlined"`, matching `InputTextField`'s placement rule), and `InputError`.
|
|
122
|
+
Ids for the label, description and error come from `useAriaDescribedById`.
|
|
123
|
+
|
|
124
|
+
**Additional props over InputSelect** (it takes no `id`; the id is generated):
|
|
125
|
+
|
|
126
|
+
| Prop (template form) | Type | Default | Notes |
|
|
127
|
+
|------|------|---------|-------|
|
|
128
|
+
| `:data-testid` | `string` | `"input-select-field"` | Sets `data-testid` on the root. (Was `"input-select-with-label"` until 2026-10-16.) |
|
|
129
|
+
| `:label` | `string` | (required) | Rendered via `InputLabel`. |
|
|
130
|
+
| `:error-message` | `object \| string` | (required) | Rendered via `InputError` when `field-has-error` is true. |
|
|
131
|
+
| `:style-class-passthrough` | `string \| string[]` | `[]` | Applied to the `.input-select-field` root only, not the inner select. |
|
|
132
|
+
|
|
133
|
+
`name`, `placeholder`, `title`, `required`, `field-has-error`, `theme` and `input-variant` are
|
|
134
|
+
forwarded. While `field-has-error` is set, the inner `InputSelect` gets `theme="error"` whatever
|
|
135
|
+
the `theme` prop says; the Field root's own `data-theme` keeps the prop value.
|
|
136
|
+
|
|
137
|
+
**Slots**: `descriptionHtml`, `descriptionText` (both forwarded to `InputDescription`).
|
|
138
|
+
|
|
139
|
+
```vue
|
|
140
|
+
<InputSelectField
|
|
141
|
+
v-model="colour"
|
|
142
|
+
v-model:field-data="colourOptions"
|
|
143
|
+
name="colour"
|
|
144
|
+
label="Favourite colour"
|
|
145
|
+
placeholder="Choose a colour"
|
|
146
|
+
:error-message="errors.colour"
|
|
147
|
+
:field-has-error="!!errors.colour"
|
|
148
|
+
>
|
|
149
|
+
<template #descriptionText>Pick the colour you like best</template>
|
|
150
|
+
</InputSelectField>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`v-model` and `v-model:field-data` are both **required**, same as `InputSelect` above.
|