srcdev-nuxt-components 9.3.11 → 9.3.13

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 (77) hide show
  1. package/.claude/skills/component-aria-landmark.md +3 -3
  2. package/.claude/skills/components/auto-grid.md +1 -1
  3. package/.claude/skills/components/page-hero-highlights.md +1 -1
  4. package/.claude/skills/components/service-detail.md +13 -14
  5. package/.claude/skills/components/service-summary-grid.md +141 -0
  6. package/.claude/skills/components/service-summary.md +165 -0
  7. package/.claude/skills/icon-sets.md +1 -1
  8. package/.claude/skills/index.md +3 -2
  9. package/.claude/skills/theming-component-token-pattern.md +126 -0
  10. package/.vscode/srcdev-component-service-summary.code-snippets +67 -0
  11. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +10 -1
  12. package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +3 -3
  13. package/app/components/02.molecules/input-copy/InputCopy.vue +9 -3
  14. package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +3 -3
  15. package/app/components/02.molecules/pricing-card/PricingCard.vue +6 -3
  16. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +7 -8
  17. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +0 -1
  18. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +2 -2
  19. package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +116 -0
  20. package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +183 -0
  21. package/app/components/03.organisms/services/{services-section/stories/ServicesSection.stories.ts → service-summary/stories/ServiceSummary.stories.ts} +78 -148
  22. package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +210 -0
  23. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +17 -0
  24. package/app/components/03.organisms/services/services-grids/{ServicesSectionGrid.vue → ServiceSummaryGrid.vue} +8 -10
  25. package/app/components/03.organisms/services/services-grids/stories/{ServicesSectionGrid.stories.ts → ServiceSummaryGrid.stories.ts} +21 -21
  26. package/app/components/03.organisms/services/services-grids/tests/{ServicesSectionGrid.spec.ts → ServiceSummaryGrid.spec.ts} +28 -28
  27. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +45 -0
  28. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +117 -0
  29. package/app/components/05.forms/input-button/InputButtonCore.vue +83 -37
  30. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-chromium-darwin.png +0 -0
  31. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-chromium-darwin.png +0 -0
  32. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-chromium-darwin.png +0 -0
  33. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-chromium-darwin.png +0 -0
  34. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-chromium-darwin.png +0 -0
  35. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
  36. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-chromium-darwin.png +0 -0
  37. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-chromium-darwin.png +0 -0
  38. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-chromium-darwin.png +0 -0
  39. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-chromium-darwin.png +0 -0
  40. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-chromium-darwin.png +0 -0
  41. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-chromium-darwin.png +0 -0
  42. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-chromium-darwin.png +0 -0
  43. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-chromium-darwin.png +0 -0
  44. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-chromium-darwin.png +0 -0
  45. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-chromium-darwin.png +0 -0
  46. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-chromium-darwin.png +0 -0
  47. package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-chromium-darwin.png +0 -0
  48. package/app/components/05.forms/input-button/stories/InputButtonCore.stories.ts +35 -0
  49. package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +68 -0
  50. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +19 -9
  51. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue +8 -5
  52. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +93 -0
  53. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioCore.stories.ts +75 -0
  54. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +41 -0
  55. package/app/components/05.forms/input-number/InputNumberCore.vue +12 -6
  56. package/app/components/05.forms/input-select/CONSUMER-STYLING.md +35 -29
  57. package/app/components/05.forms/input-select/InputSelectCore.vue +32 -20
  58. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +69 -0
  59. package/app/components/05.forms/input-text/CONSUMER-STYLING.md +38 -48
  60. package/app/components/05.forms/input-text/InputTextCore.vue +35 -18
  61. package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +184 -0
  62. package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +125 -0
  63. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +49 -0
  64. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +23 -16
  65. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +72 -0
  66. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +42 -20
  67. package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitchCore.stories.ts +63 -0
  68. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +13 -6
  69. package/package.json +1 -1
  70. package/.claude/skills/components/services-section-grid.md +0 -130
  71. package/.claude/skills/components/services-section.md +0 -211
  72. package/.vscode/srcdev-component-services-section.code-snippets +0 -84
  73. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesSectionGrid.spec.ts.snap +0 -87
  74. package/app/components/03.organisms/services/services-section/CONSUMER-STYLING.md +0 -149
  75. package/app/components/03.organisms/services/services-section/ServicesSection.vue +0 -345
  76. package/app/components/03.organisms/services/services-section/tests/ServicesSection.spec.ts +0 -297
  77. package/app/components/03.organisms/services/services-section/tests/__snapshots__/ServicesSection.spec.ts.snap +0 -87
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
2
  import { ref } from "vue";
3
3
  import StorybookComponent from "../InputButtonCore.vue";
4
+ import InputTextCore from "../../input-text/InputTextCore.vue";
4
5
 
5
6
  interface InputButtonCoreStoryArgs {
6
7
  type: "submit" | "button" | "reset";
@@ -240,7 +241,17 @@ const Template: StoryFn<InputButtonCoreStoryArgs> = (args) => ({
240
241
  },
241
242
  template: `
242
243
  <div style="display: flex; flex-direction: column; gap: 1rem; align-items: flex-start;margin: 36px;">
244
+ <!-- :key forces a remount when any slot-visibility arg changes. Verified the args
245
+ themselves ARE reactive here (a plain {{ args.useLeftSlot }} interpolation updates
246
+ live on toggle) — the bug is narrower: Vue's compiled-at-runtime string template
247
+ doesn't reliably re-evaluate a <template v-if="..."> conditionally attached to a named
248
+ slot when only the v-if's condition changes, on this component/Storybook combination.
249
+ Toggling a control changed the fallthrough attribute (confirmed via useleftslot="true"
250
+ in the rendered DOM) but never actually added/removed the slot content until a hard
251
+ refresh. Remounting sidesteps whatever the underlying slot-stability quirk is, rather
252
+ than chasing it further. -->
243
253
  <StorybookComponent
254
+ :key="[args.useLeftSlot, args.useLeftIcon, args.useRightSlot, args.useRightIcon, args.useIconOnlySlot, args.useIconOnly].join(',')"
244
255
  v-bind="args"
245
256
  @click="handleClick"
246
257
  >
@@ -302,3 +313,27 @@ NuxtIconOnlyComponent.args = {
302
313
  useIconOnly: true,
303
314
  iconOnlyName: "mdi:chevron-right-circle-outline",
304
315
  };
316
+
317
+ // Composition check, not a props demo: confirms InputButtonCore's --button-min-height (defaults
318
+ // to var(--input-min-height)) actually keeps it height-aligned with an inline text input, the
319
+ // most common "usual suspect" pairing (e.g. newsletter signup, search bar). Textareas are the
320
+ // deliberate exception to this alignment and aren't part of this story.
321
+ const InlineTemplate: StoryFn = () => ({
322
+ components: { StorybookComponent, InputTextCore },
323
+ template: `
324
+ <div style="margin: 36px; max-width: 480px;">
325
+ <div style="display: flex; align-items: flex-start; gap: 0.8rem;">
326
+ <InputTextCore
327
+ id="inline-email"
328
+ name="email"
329
+ type="email"
330
+ placeholder="you@example.com"
331
+ style="flex: 1;"
332
+ />
333
+ <StorybookComponent type="submit" variant="primary" button-text="Subscribe" />
334
+ </div>
335
+ </div>
336
+ `,
337
+ });
338
+
339
+ export const InlineWithTextInput = InlineTemplate.bind({});
@@ -0,0 +1,68 @@
1
+ # InputCheckboxRadioCore / InputCheckboxRadioButton — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ Every colour token below is `var(--input-checkbox-*, {default})` — set the `--input-checkbox-*`
6
+ name to override just this component; leave it unset and it inherits the shared `--theme-*` slot
7
+ (or `--theme-checkbox-symbol-surface`/`--theme-input-surface` family) every other themed component
8
+ also falls back to. See `theming-component-token-pattern.md` for why this two-tier shape exists.
9
+
10
+ Defaults are unchanged from before this migration — this is a token-rename pass (giving each
11
+ property its own override point), not a redesign, unlike `InputButtonCore`'s flat/darker-hover
12
+ treatment. Geometry (`--input-checked-element-size`, `--form-element-*`, etc.) is separate and
13
+ untouched — see `theming-form-geometry-tokens.md`.
14
+
15
+ ### The control itself (`InputCheckboxRadioCore` — the square/circle)
16
+
17
+ | Token | Falls back to | Controls |
18
+ |---|---|---|
19
+ | `--input-checkbox-surface` | `var(--theme-checkbox-symbol-surface)` | Background |
20
+ | `--input-checkbox-border` | `var(--theme-border)` | Border colour |
21
+ | `--input-checkbox-border-focus` | `var(--theme-border-focus)` | Outline colour on `:focus-visible` (non-button style only — see note) |
22
+ | `--input-checkbox-icon-color` | `var(--theme-text)` | Checked-state icon colour |
23
+
24
+ > The `:focus-visible` outline only applies to the plain checkbox/radio style
25
+ > (`:not(.button)`) — `InputCheckboxRadioButton`'s pill/button style has its own focus treatment,
26
+ > below, via `:has(.input-checkbox-radio-core:focus-visible)` on the outer label.
27
+
28
+ ### The pill/button style (`InputCheckboxRadioButton` — e.g. a "Services of interest" option list)
29
+
30
+ | Token | Falls back to | Controls |
31
+ |---|---|---|
32
+ | `--input-checkbox-button-surface` | `var(--theme-input-surface)` | Background (resting) |
33
+ | `--input-checkbox-button-border` | `var(--theme-border)` | Border colour (resting and hover — unchanged between the two by default) |
34
+ | `--input-checkbox-button-surface-hover` | `var(--theme-input-surface-hover)` | Background on hover |
35
+ | `--input-checkbox-button-ring-hover` | `var(--theme-ring)` | Outline colour on hover |
36
+ | `--input-checkbox-button-surface-focus` | `var(--theme-surface-subtle)` | Background when the inner control has `:focus-visible` |
37
+ | `--input-checkbox-button-ring-focus` | `var(--theme-border-focus)` | Outline colour when the inner control has `:focus-visible` |
38
+ | `--input-checkbox-button-label-color` | `var(--colour-text-default)` | Option label text colour |
39
+ | `--input-checkbox-button-icon-color` | `var(--theme-text)` | Trailing decorator icon colour (the `+` in the corner) |
40
+
41
+ ---
42
+
43
+ ## Global theming — app-level CSS file
44
+
45
+ ```css
46
+ :where(html) {
47
+ --input-checkbox-button-surface: var(--rose-09);
48
+ --input-checkbox-button-surface-hover: var(--rose-08);
49
+ --input-checkbox-button-ring-focus: var(--rose-04);
50
+ }
51
+ ```
52
+
53
+ Only declare the tokens you want to change — everything else keeps inheriting the shared
54
+ `--theme-*`/`--theme-input-*` tokens.
55
+
56
+ ---
57
+
58
+ ## Per-instance overrides
59
+
60
+ ```vue
61
+ <InputCheckboxRadioButton
62
+ id="balayage"
63
+ type="checkbox"
64
+ name="services"
65
+ label="Balayage"
66
+ style="--input-checkbox-button-surface-hover: var(--gold-08);"
67
+ />
68
+ ```
@@ -100,23 +100,28 @@ const flexDirection = ref(props.direction);
100
100
  &.inline {
101
101
  --_white-space: nowrap;
102
102
  }
103
- background-color: var(--theme-input-surface);
104
- border: var(--form-element-border-width) solid var(--theme-border);
103
+
104
+ /* Public --input-checkbox-button-* tokens, inline-fallback to the shared theme tokens (see
105
+ theming-component-token-pattern.md) — overriding one here doesn't touch every other themed
106
+ input/control that also reads --theme-input-surface/--theme-border, etc. Defaults are
107
+ unchanged from before this migration — this is a token-rename pass, not a redesign. */
108
+ background-color: var(--input-checkbox-button-surface, var(--theme-input-surface));
109
+ border: var(--form-element-border-width) solid var(--input-checkbox-button-border, var(--theme-border));
105
110
  outline: var(--form-element-outline-width) solid transparent;
106
111
 
107
112
  box-shadow: 0.1rem 0.1rem 0.8rem 0.1rem transparent;
108
113
 
109
114
  &:hover {
110
- background-color: var(--theme-input-surface-hover);
111
- border-color: var(--theme-border);
112
- outline-color: var(--theme-ring);
115
+ background-color: var(--input-checkbox-button-surface-hover, var(--theme-input-surface-hover));
116
+ border-color: var(--input-checkbox-button-border, var(--theme-border));
117
+ outline-color: var(--input-checkbox-button-ring-hover, var(--theme-ring));
113
118
  outline-offset: var(--form-element-outline-offset-focus);
114
119
  cursor: pointer;
115
120
  }
116
121
 
117
122
  &:has(.input-checkbox-radio-core:focus-visible) {
118
- background-color: var(--theme-surface-subtle);
119
- outline-color: var(--theme-border-focus);
123
+ background-color: var(--input-checkbox-button-surface-focus, var(--theme-surface-subtle));
124
+ outline-color: var(--input-checkbox-button-ring-focus, var(--theme-border-focus));
120
125
  outline-offset: var(--form-element-outline-offset-focus);
121
126
  }
122
127
 
@@ -127,12 +132,17 @@ const flexDirection = ref(props.direction);
127
132
  .input-checkbox-radio-options-button-label {
128
133
  display: flex;
129
134
  flex-grow: 1;
130
- color: var(--colour-text-default);
135
+ color: var(--input-checkbox-button-label-color, var(--colour-text-default));
131
136
  font-size: var(--input-font-size);
132
137
  width: 100%;
133
138
  min-height: var(--input-element-line-height);
134
139
  align-items: center;
135
140
  justify-content: center;
141
+ /* justify-content only centers the label as a single block — once text actually wraps
142
+ (e.g. "Half Head Highlights"), that block fills the full available width and each
143
+ individual line falls back to its default left-alignment inside it. text-align is what
144
+ centers wrapped lines relative to each other. */
145
+ text-align: center;
136
146
  padding-block: 0.8rem;
137
147
  padding-inline: 0.8rem;
138
148
  white-space: var(--_white-space);
@@ -146,7 +156,7 @@ const flexDirection = ref(props.direction);
146
156
  display: flex;
147
157
  align-items: center;
148
158
  justify-content: center;
149
- color: var(--theme-text);
159
+ color: var(--input-checkbox-button-icon-color, var(--theme-text));
150
160
 
151
161
  .icon {
152
162
  height: var(--input-checkbox-decorator-icon-size);
@@ -112,8 +112,11 @@ const inputClasses = computed(() => [
112
112
  grid-template-areas: "element-stack";
113
113
  place-content: center;
114
114
 
115
- background-color: var(--theme-checkbox-symbol-surface);
116
- border: 0.1rem solid var(--theme-border);
115
+ /* Public --input-checkbox-* tokens, inline-fallback to the shared theme tokens (see
116
+ theming-component-token-pattern.md) — overriding one here doesn't touch every other
117
+ themed input/control that also reads --theme-checkbox-symbol-surface/--theme-border. */
118
+ background-color: var(--input-checkbox-surface, var(--theme-checkbox-symbol-surface));
119
+ border: 0.1rem solid var(--input-checkbox-border, var(--theme-border));
117
120
  outline: var(--form-element-outline-width) solid transparent;
118
121
 
119
122
  height: var(--input-checked-element-size);
@@ -139,7 +142,7 @@ const inputClasses = computed(() => [
139
142
 
140
143
  &:not(:is(.button)) {
141
144
  &:has(input:focus-visible) {
142
- outline: var(--form-element-outline-width-focus) solid var(--theme-border-focus);
145
+ outline: var(--form-element-outline-width-focus) solid var(--input-checkbox-border-focus, var(--theme-border-focus));
143
146
  outline-offset: var(--form-element-outline-offset-focus);
144
147
  }
145
148
  }
@@ -150,7 +153,7 @@ const inputClasses = computed(() => [
150
153
 
151
154
  .input-checked-icon-checked,
152
155
  .icon {
153
- color: var(--theme-text);
156
+ color: var(--input-checkbox-icon-color, var(--theme-text));
154
157
  }
155
158
  }
156
159
  }
@@ -164,7 +167,7 @@ const inputClasses = computed(() => [
164
167
 
165
168
  .input-checked-icon-checked,
166
169
  .icon {
167
- color: var(--theme-text);
170
+ color: var(--input-checkbox-icon-color, var(--theme-text));
168
171
  height: var(--input-checked-icon-size);
169
172
  width: var(--input-checked-icon-size);
170
173
  box-shadow: var(--_box-shadow);
@@ -0,0 +1,93 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import { ref } from "vue";
3
+ import StorybookComponent from "../InputCheckboxRadioButton.vue";
4
+
5
+ interface InputCheckboxRadioButtonStoryArgs {
6
+ type: "checkbox" | "radio";
7
+ label: string;
8
+ isPill: boolean;
9
+ fieldHasError: boolean;
10
+ displayAsDisc: boolean;
11
+ }
12
+
13
+ export default {
14
+ title: "Components/Forms/Input Checkbox Radio/InputCheckboxRadioButton",
15
+ component: StorybookComponent,
16
+ argTypes: {
17
+ type: {
18
+ control: { type: "select" },
19
+ options: ["checkbox", "radio"],
20
+ table: { category: "Basic" },
21
+ },
22
+ label: { control: "text", table: { category: "Basic" } },
23
+ isPill: { control: "boolean", table: { category: "Styling" } },
24
+ fieldHasError: { control: "boolean", table: { category: "States" } },
25
+ displayAsDisc: { control: "boolean", table: { category: "Styling" } },
26
+ },
27
+ args: {
28
+ type: "checkbox",
29
+ label: "Balayage",
30
+ isPill: false,
31
+ fieldHasError: false,
32
+ displayAsDisc: false,
33
+ },
34
+ } as Meta<InputCheckboxRadioButtonStoryArgs>;
35
+
36
+ const Template: StoryFn<InputCheckboxRadioButtonStoryArgs> = (args) => ({
37
+ components: { StorybookComponent },
38
+ setup() {
39
+ const modelValue = ref(false);
40
+ return { args, modelValue };
41
+ },
42
+ template: `
43
+ <div style="margin: 36px; max-width: 260px;">
44
+ <StorybookComponent
45
+ id="single-option"
46
+ name="single"
47
+ :type="args.type"
48
+ :label="args.label"
49
+ :is-pill="args.isPill"
50
+ :field-has-error="args.fieldHasError"
51
+ :display-as-disc="args.displayAsDisc"
52
+ v-model="modelValue"
53
+ />
54
+ </div>
55
+ `,
56
+ });
57
+
58
+ export const Default = Template.bind({});
59
+
60
+ // "Services of interest" pattern — the actual multi-option checkbox-pill layout this component
61
+ // is normally used in (as opposed to Default's single isolated control).
62
+ const OptionGroupTemplate: StoryFn = () => ({
63
+ components: { StorybookComponent },
64
+ setup() {
65
+ const services = [
66
+ "Balayage",
67
+ "Highlights",
68
+ "Half Head Highlights",
69
+ "Full Colour",
70
+ "Lowlights",
71
+ "Toner & Gloss",
72
+ ];
73
+ const selected = ref<string[]>([]);
74
+ return { services, selected };
75
+ },
76
+ template: `
77
+ <div style="margin: 36px; max-width: 480px; display: grid; grid-template-columns: 1fr 1fr; gap: 0.8rem;">
78
+ <StorybookComponent
79
+ v-for="service in services"
80
+ :key="service"
81
+ :id="'service-' + service"
82
+ name="services"
83
+ type="checkbox"
84
+ :label="service"
85
+ :true-value="service"
86
+ :multiple-options="true"
87
+ v-model="selected"
88
+ />
89
+ </div>
90
+ `,
91
+ });
92
+
93
+ export const OptionGroup = OptionGroupTemplate.bind({});
@@ -0,0 +1,75 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import { ref } from "vue";
3
+ import StorybookComponent from "../InputCheckboxRadioCore.vue";
4
+ import type { FormUiTheme } from "~/types/forms/types.forms.d";
5
+
6
+ interface InputCheckboxRadioCoreStoryArgs {
7
+ type: "checkbox" | "radio";
8
+ id: string;
9
+ name: string;
10
+ required: boolean;
11
+ theme: FormUiTheme;
12
+ fieldHasError: boolean;
13
+ displayAsDisc: boolean;
14
+ }
15
+
16
+ export default {
17
+ title: "Components/Forms/Input Checkbox Radio/InputCheckboxRadioCore",
18
+ component: StorybookComponent,
19
+ argTypes: {
20
+ type: {
21
+ control: { type: "select" },
22
+ options: ["checkbox", "radio"],
23
+ description: "Control type",
24
+ table: { category: "Basic" },
25
+ },
26
+ id: { control: "text", table: { category: "Basic" } },
27
+ name: { control: "text", table: { category: "Basic" } },
28
+ required: { control: "boolean", table: { category: "Basic" } },
29
+ theme: {
30
+ control: { type: "select" },
31
+ options: ["default", "success", "error", "warning"],
32
+ table: { category: "Styling" },
33
+ },
34
+ fieldHasError: { control: "boolean", table: { category: "States" } },
35
+ displayAsDisc: {
36
+ control: "boolean",
37
+ description: "Checkbox only — render as a filled disc instead of a check icon",
38
+ table: { category: "Styling" },
39
+ },
40
+ },
41
+ args: {
42
+ type: "checkbox",
43
+ id: "core-checkbox",
44
+ name: "coreCheckbox",
45
+ required: false,
46
+ theme: "default",
47
+ fieldHasError: false,
48
+ displayAsDisc: false,
49
+ },
50
+ } as Meta<InputCheckboxRadioCoreStoryArgs>;
51
+
52
+ const Template: StoryFn<InputCheckboxRadioCoreStoryArgs> = (args) => ({
53
+ components: { StorybookComponent },
54
+ setup() {
55
+ const modelValue = ref(false);
56
+ return { args, modelValue };
57
+ },
58
+ template: `
59
+ <div style="margin: 36px;">
60
+ <StorybookComponent v-bind="args" v-model="modelValue" />
61
+ </div>
62
+ `,
63
+ });
64
+
65
+ export const Checkbox = Template.bind({});
66
+ Checkbox.args = { type: "checkbox" };
67
+
68
+ export const Radio = Template.bind({});
69
+ Radio.args = { type: "radio" };
70
+
71
+ export const CheckboxAsDisc = Template.bind({});
72
+ CheckboxAsDisc.args = { type: "checkbox", displayAsDisc: true };
73
+
74
+ export const ErrorState = Template.bind({});
75
+ ErrorState.args = { type: "checkbox", fieldHasError: true, theme: "error" };
@@ -0,0 +1,41 @@
1
+ # InputNumberCore — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ Every colour token below is `var(--input-number-*, {default})` — set the `--input-number-*` name
6
+ to override just this component; leave it unset and it inherits the shared `--theme-input-*`/
7
+ `--theme-*` token every other themed component also falls back to. See
8
+ `theming-component-token-pattern.md` for why this two-tier shape exists.
9
+
10
+ | Token | Falls back to | Controls |
11
+ |---|---|---|
12
+ | `--input-number-surface` | `var(--theme-input-surface)` | Wrapper and input background |
13
+ | `--input-number-border` | `var(--theme-border)` | Border colour |
14
+ | `--input-number-text-color` | `var(--theme-input-text-color-normal)` | Input text colour |
15
+ | `--input-number-divider-color` | `var(--theme-input-surface-hover)` | Divider between the input and an embedded left/right `InputButtonCore` |
16
+
17
+ Geometry (`--form-textarea-padding-*`, `--input-font-size`, `--input-element-line-height`, etc.)
18
+ is shared across every `05.forms` component and untouched by this — see
19
+ `theming-form-geometry-tokens.md`.
20
+
21
+ ---
22
+
23
+ ## Global theming — app-level CSS file
24
+
25
+ ```css
26
+ :where(html) {
27
+ --input-number-surface: var(--rose-09);
28
+ --input-number-border: var(--rose-05);
29
+ }
30
+ ```
31
+
32
+ Only declare the tokens you want to change — everything else keeps inheriting the shared
33
+ `--theme-input-*`/`--theme-*` tokens.
34
+
35
+ ---
36
+
37
+ ## Per-instance overrides
38
+
39
+ ```vue
40
+ <InputNumberCore id="quantity" name="quantity" :min="1" :max="10" style="--input-number-border: var(--gold-04);" />
41
+ ```
@@ -66,14 +66,20 @@ const minLength = computed(() => `${props.max.toString().length + 1}em`);
66
66
  --_focus-box-shadow: var(--box-shadow-off);
67
67
  --_min-width: v-bind(minLength);
68
68
 
69
+ /* Public --input-number-* tokens, inline-fallback to the shared --theme-* tokens (see
70
+ theming-component-token-pattern.md) — overriding one here doesn't touch every other themed
71
+ input/control that also reads --theme-input-surface/--theme-border. */
72
+ --_surface: var(--input-number-surface, var(--theme-input-surface));
73
+ --_border: var(--input-number-border, var(--theme-border));
74
+
69
75
  display: flex;
70
76
  align-items: center;
71
77
 
72
78
  width: fit-content;
73
79
 
74
- background-color: var(--theme-input-surface);
80
+ background-color: var(--_surface);
75
81
  border-radius: var(--form-element-border-width);
76
- border: var(--form-element-border-width) solid var(--theme-border);
82
+ border: var(--form-element-border-width) solid var(--_border);
77
83
  outline: var(--form-element-outline-width) solid transparent;
78
84
  box-shadow: var(--_focus-box-shadow);
79
85
 
@@ -106,8 +112,8 @@ const minLength = computed(() => `${props.max.toString().length + 1}em`);
106
112
  outline: none;
107
113
  box-shadow: none;
108
114
 
109
- background-color: var(--theme-input-surface);
110
- color: var(--theme-input-text-color-normal);
115
+ background-color: var(--_surface);
116
+ color: var(--input-number-text-color, var(--theme-input-text-color-normal));
111
117
  font-family: var(--font-family);
112
118
  font-size: var(--input-font-size);
113
119
  line-height: var(--input-element-line-height);
@@ -144,12 +150,12 @@ const minLength = computed(() => `${props.max.toString().length + 1}em`);
144
150
 
145
151
  .left-slot {
146
152
  margin-inline-end: 0;
147
- border-right: 2px solid var(--theme-input-surface-hover);
153
+ border-right: 2px solid var(--input-number-divider-color, var(--theme-input-surface-hover));
148
154
  }
149
155
 
150
156
  .right-slot {
151
157
  margin-inline-end: 0;
152
- border-left: 2px solid var(--theme-input-surface-hover);
158
+ border-left: 2px solid var(--input-number-divider-color, var(--theme-input-surface-hover));
153
159
  }
154
160
  }
155
161
  }
@@ -1,42 +1,48 @@
1
1
  # InputSelectCore — Consumer Styling Guide
2
2
 
3
- ## Two override paths
3
+ ## Public token API
4
+
5
+ Every colour token below is `var(--input-select-*, {default})` — set the `--input-select-*` name
6
+ to override just this component; leave it unset and it inherits the shared `--theme-input-*`/
7
+ `--theme-*` token every other themed component also falls back to. See
8
+ `theming-component-token-pattern.md` for why this two-tier shape exists.
9
+
10
+ (Before 2026-08-25 this component used a private `--_input-select-*` naming scheme presented as a
11
+ "local override path" — that's gone. A private, underscore-prefixed custom property was never a
12
+ real override point; renaming it public with a real fallback is the actual fix, not a second
13
+ documented path to reach for.)
14
+
15
+ | Token | Falls back to | Controls |
16
+ |---|---|---|
17
+ | `--input-select-surface` | `var(--theme-input-surface)` | Wrapper/select background |
18
+ | `--input-select-surface-hover` | `var(--theme-input-surface-hover)` | Option row background on hover |
19
+ | `--input-select-border` | `var(--theme-border)` | Border colour (`.normal`/`.underlined`/open picker, resting) |
20
+ | `--input-select-border-hover` | `var(--theme-border-focus)` | Outline colour on hover (mouse) |
21
+ | `--input-select-border-focus` | `var(--theme-border-focus)` | Outline colour on `:focus-visible` (keyboard/assistive) |
22
+
23
+ Geometry (`--input-padding-*`, `--input-min-height`, `--input-select-option-min-height`,
24
+ `--form-element-*`, etc.) is shared across every `05.forms` component and untouched by this — see
25
+ `theming-form-geometry-tokens.md`.
4
26
 
5
- Same public/private split as `InputTextCore` (see that component's `CONSUMER-STYLING.md` for the
6
- full rationale) — `.input-select-wrapper` now exposes local tokens, one indirection step below the
7
- global `--theme-*` ones they default from:
8
-
9
- ```css
10
- --_input-select-surface: var(--theme-input-surface);
11
- --_input-select-surface-hover: var(--theme-input-surface-hover);
12
- --_input-select-border: var(--theme-border);
13
- --_input-select-border-focus: var(--theme-border-focus);
14
- --_input-select-outline-color: var(--_input-select-border-focus);
15
- ```
27
+ ---
16
28
 
17
- - **Global override**: redeclare `--theme-input-surface` / `--theme-border` / `--theme-border-focus`
18
- / `--theme-input-surface-hover` wherever you'd normally set theme tokens.
19
- - **Local override** (this component only, guaranteed to win):
29
+ ## Global theming — app-level CSS file
20
30
 
21
31
  ```css
22
- .input-select-wrapper {
23
- --_input-select-border: var(--slate-06);
24
- --_input-select-border-focus: var(--slate-04);
25
- --_input-select-surface: var(--slate-01);
32
+ :where(html) {
33
+ --input-select-surface: var(--rose-09);
34
+ --input-select-border: var(--rose-05);
35
+ --input-select-border-focus: var(--rose-03);
26
36
  }
27
37
  ```
28
38
 
29
- ## Note on `--_input-select-outline-color`
30
-
31
- Before 2026-08-22 this was referenced (on the open-picker outline) but never declared anywhere —
32
- a dangling private token that silently resolved to the property's initial value (same failure mode
33
- as the `--theme-button-surface` typo documented in the layer's `CLAUDE.md`, pitfall list). It's now
34
- properly defined, defaulting to `--_input-select-border-focus`.
39
+ Only declare the tokens you want to change — everything else keeps inheriting the shared
40
+ `--theme-input-*`/`--theme-*` tokens.
35
41
 
36
42
  ---
37
43
 
38
- ## Other tokens
44
+ ## Per-instance overrides
39
45
 
40
- Generic form-geometry tokens (`--input-padding-inline`, `--input-min-height`, `--input-font-size`,
41
- `--form-element-border-width`, `--form-input-border-radius`, etc.) are shared across every
42
- `05.forms` component and declared in `setup/04.elements/forms/*.css` — not component-local.
46
+ ```vue
47
+ <InputSelectCore id="country" name="country" style="--input-select-border-focus: var(--gold-04);" />
48
+ ```