srcdev-nuxt-components 9.3.12 → 9.4.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/.claude/skills/components/cookie-consent-banner.md +88 -0
- package/.claude/skills/composable-cookie-consent.md +78 -0
- package/.claude/skills/composable-google-analytics.md +87 -0
- package/.claude/skills/index.md +5 -1
- package/.claude/skills/theming-component-token-pattern.md +126 -0
- package/app/app.config.ts +10 -1
- package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +10 -1
- package/app/components/01.atoms/cookie-consent-banner/CookieConsentBanner.vue +148 -0
- package/app/components/01.atoms/cookie-consent-banner/stories/CookieConsentBanner.stories.ts +93 -0
- package/app/components/01.atoms/cookie-consent-banner/tests/CookieConsentBanner.spec.ts +118 -0
- package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +3 -3
- package/app/components/02.molecules/input-copy/InputCopy.vue +9 -3
- package/app/components/02.molecules/pricing-card/CONSUMER-STYLING.md +3 -3
- package/app/components/02.molecules/pricing-card/PricingCard.vue +6 -3
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +117 -0
- package/app/components/05.forms/input-button/InputButtonCore.vue +83 -37
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-chromium-darwin.png +0 -0
- package/app/components/05.forms/input-button/stories/InputButtonCore.stories.ts +35 -0
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +68 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +19 -9
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioCore.vue +8 -5
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +93 -0
- package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioCore.stories.ts +75 -0
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +41 -0
- package/app/components/05.forms/input-number/InputNumberCore.vue +12 -6
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +35 -29
- package/app/components/05.forms/input-select/InputSelectCore.vue +32 -20
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +69 -0
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +38 -48
- package/app/components/05.forms/input-text/InputTextCore.vue +35 -18
- package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +184 -0
- package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +125 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +49 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +23 -16
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +72 -0
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +42 -20
- package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitchCore.stories.ts +63 -0
- package/app/components/display-theme-switch/DisplayThemeSwitch.vue +13 -6
- package/app/composables/tests/useCookieConsent.spec.ts +87 -0
- package/app/composables/useCookieConsent.ts +54 -0
- package/app/composables/useGoogleAnalytics.ts +26 -0
- package/app/types/components/cookie-consent-banner.d.ts +8 -0
- package/app/types/components/index.ts +1 -0
- package/nuxt.config.ts +6 -0
- package/package.json +2 -1
|
@@ -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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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);
|
package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts
ADDED
|
@@ -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({});
|
package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioCore.stories.ts
ADDED
|
@@ -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(--
|
|
80
|
+
background-color: var(--_surface);
|
|
75
81
|
border-radius: var(--form-element-border-width);
|
|
76
|
-
border: var(--form-element-border-width) solid var(--
|
|
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(--
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
--
|
|
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
|
-
|
|
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
|
-
##
|
|
44
|
+
## Per-instance overrides
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
```vue
|
|
47
|
+
<InputSelectCore id="country" name="country" style="--input-select-border-focus: var(--gold-04);" />
|
|
48
|
+
```
|
|
@@ -73,17 +73,24 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
73
73
|
<style lang="css">
|
|
74
74
|
@layer components {
|
|
75
75
|
.input-select-wrapper {
|
|
76
|
-
/*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
--
|
|
84
|
-
--
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
/* Public --input-select-* tokens, inline-fallback to the shared --theme-* tokens (see
|
|
77
|
+
theming-component-token-pattern.md) — overriding one here doesn't touch every other themed
|
|
78
|
+
input/control that also reads --theme-input-surface/--theme-border. --_surface etc. are
|
|
79
|
+
private locals purely so the rest of this rule can reuse the resolved value without
|
|
80
|
+
repeating the fallback chain at every property — they are not themselves an override point,
|
|
81
|
+
see CONSUMER-STYLING.md. --_outline-color stays private/computed: it's always just an alias
|
|
82
|
+
for --_border-focus with no independent meaning of its own. */
|
|
83
|
+
--_surface: var(--input-select-surface, var(--theme-input-surface));
|
|
84
|
+
--_surface-hover: var(--input-select-surface-hover, var(--theme-input-surface-hover));
|
|
85
|
+
--_border: var(--input-select-border, var(--theme-border));
|
|
86
|
+
/* Split so hover (mouse) and :focus-visible (keyboard/assistive) can diverge — both default to
|
|
87
|
+
the same --theme-border-focus today, identical appearance to before this token existed, but
|
|
88
|
+
each now has its own override point. */
|
|
89
|
+
--_border-hover: var(--input-select-border-hover, var(--theme-border-focus));
|
|
90
|
+
--_border-focus: var(--input-select-border-focus, var(--theme-border-focus));
|
|
91
|
+
--_outline-color: var(--_border-focus);
|
|
92
|
+
|
|
93
|
+
background-color: var(--_surface);
|
|
87
94
|
overflow: hidden;
|
|
88
95
|
|
|
89
96
|
z-index: 2;
|
|
@@ -91,18 +98,23 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
91
98
|
transition: all var(--theme-form-transition-duration) ease-in-out;
|
|
92
99
|
|
|
93
100
|
&.normal {
|
|
94
|
-
border: var(--form-element-border-width) solid var(--
|
|
101
|
+
border: var(--form-element-border-width) solid var(--_border);
|
|
95
102
|
border-radius: var(--form-input-border-radius);
|
|
96
103
|
outline: var(--form-element-outline-width) solid transparent;
|
|
97
104
|
}
|
|
98
105
|
|
|
99
106
|
&.underlined {
|
|
100
|
-
border-bottom: var(--form-element-border-bottom-width-underlined) solid var(--
|
|
101
|
-
background-color: var(--
|
|
107
|
+
border-bottom: var(--form-element-border-bottom-width-underlined) solid var(--_border);
|
|
108
|
+
background-color: var(--_surface);
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
&:has(
|
|
105
|
-
outline: var(--form-element-outline-width-focus) solid var(--
|
|
111
|
+
&:has(:hover) {
|
|
112
|
+
outline: var(--form-element-outline-width-focus) solid var(--_border-hover);
|
|
113
|
+
outline-offset: var(--form-element-outline-offset-focus);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&:has(select:focus-visible) {
|
|
117
|
+
outline: var(--form-element-outline-width-focus) solid var(--_border-focus);
|
|
106
118
|
outline-offset: var(--form-element-outline-offset-focus);
|
|
107
119
|
}
|
|
108
120
|
|
|
@@ -117,7 +129,7 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
117
129
|
content: '';
|
|
118
130
|
width: 0.8em;
|
|
119
131
|
height: 0.5em;
|
|
120
|
-
background-color: var(--
|
|
132
|
+
background-color: var(--_border);
|
|
121
133
|
clip-path: polygon(100% 0%, 0 0%, 50% 100%);
|
|
122
134
|
} */
|
|
123
135
|
|
|
@@ -140,8 +152,8 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
140
152
|
|
|
141
153
|
&:open::picker(select) {
|
|
142
154
|
opacity: 1;
|
|
143
|
-
border: var(--form-element-border-width) solid var(--
|
|
144
|
-
outline: var(--form-element-outline-width) solid var(--
|
|
155
|
+
border: var(--form-element-border-width) solid var(--_border);
|
|
156
|
+
outline: var(--form-element-outline-width) solid var(--_outline-color);
|
|
145
157
|
|
|
146
158
|
@starting-style {
|
|
147
159
|
opacity: 0;
|
|
@@ -168,7 +180,7 @@ const fieldData = defineModel("fieldData") as Ref<IFormMultipleOptions>;
|
|
|
168
180
|
transition: all var(--theme-form-transition-duration) ease-in-out;
|
|
169
181
|
|
|
170
182
|
&:hover {
|
|
171
|
-
background-color: var(--
|
|
183
|
+
background-color: var(--_surface-hover);
|
|
172
184
|
}
|
|
173
185
|
|
|
174
186
|
.input-select-core-option-decorator-icon {
|