srcdev-nuxt-components 9.6.10 → 9.6.12
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/rules/consumer/ids-from-useid.md +4 -2
- package/.claude/skills/component-compliance-checklist.md +14 -3
- package/.claude/skills/component-inline-action-button.md +5 -4
- package/.claude/skills/component-naming.md +2 -1
- package/.claude/skills/components/{alert-mask-core.md → alert-mask.md} +38 -23
- package/.claude/skills/components/alert-masked-content.md +3 -3
- package/.claude/skills/components/container-glow.md +1 -1
- package/.claude/skills/components/display-theme-switch.md +8 -8
- package/.claude/skills/components/form-field.md +15 -0
- package/.claude/skills/components/form-fieldset.md +18 -2
- package/.claude/skills/components/input-button.md +26 -7
- package/.claude/skills/components/input-checkbox-radio.md +2 -0
- package/.claude/skills/components/input-description.md +5 -0
- package/.claude/skills/components/input-error.md +9 -2
- package/.claude/skills/components/input-label.md +8 -2
- package/.claude/skills/components/input-number.md +2 -0
- package/.claude/skills/components/input-otp.md +2 -0
- package/.claude/skills/components/input-range.md +2 -0
- package/.claude/skills/components/input-select.md +2 -0
- package/.claude/skills/components/input-text.md +2 -0
- package/.claude/skills/components/input-textarea.md +2 -0
- package/.claude/skills/components/marquee-scroller.md +51 -14
- package/.claude/skills/components/pending-effect.md +5 -4
- package/.claude/skills/components/{triple-toggle-switch.md → segmented-control.md} +34 -19
- package/.claude/skills/components/services-card.md +1 -1
- package/.claude/skills/components/tabbed-content.md +1 -1
- package/.claude/skills/components/toggle-switch.md +2 -0
- package/.claude/skills/index.md +5 -5
- package/.claude/skills/storybook-add-story.md +3 -1
- package/.claude/skills/theming-component-token-pattern.md +24 -0
- package/.claude/skills/theming-form-geometry-tokens.md +1 -1
- package/.claude/skills/theming-partial-override.md +1 -1
- package/.vscode/srcdev-component-alert-mask.code-snippets +41 -0
- package/.vscode/srcdev-component-form-field.code-snippets +7 -6
- package/.vscode/srcdev-component-form-fieldset.code-snippets +12 -12
- package/.vscode/srcdev-component-marquee-scroller.code-snippets +31 -28
- package/.vscode/{srcdev-component-triple-toggle-switch.code-snippets → srcdev-component-segmented-control.code-snippets} +6 -7
- package/app/assets/styles/setup/03.theming/_theme-slots.css +1 -1
- package/app/assets/styles/setup/04.elements/forms/03.generic-input-geometry.css +4 -0
- package/app/components/01.atoms/alert-mask/AlertMask.vue +194 -0
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +57 -42
- package/app/components/01.atoms/alert-mask/stories/AlertMask.stories.ts +198 -0
- package/app/components/01.atoms/alert-mask/tests/AlertMask.spec.ts +233 -0
- package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +77 -54
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +103 -91
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +132 -3
- package/app/components/01.atoms/animations/marquee-scroller/tests/MarqueeScroller.spec.ts +190 -102
- package/app/components/01.atoms/animations/marquee-scroller/tests/__snapshots__/MarqueeScroller.spec.ts.snap +17 -13
- package/app/components/02.molecules/alert-masked-content/AlertMaskedContent.vue +2 -2
- package/app/components/02.molecules/alert-masked-content/CONSUMER-STYLING.md +4 -4
- package/app/components/02.molecules/alert-masked-content/stories/AlertMaskedContent.stories.ts +60 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +12 -12
- package/app/components/02.molecules/cookie-consent-banner/CONSUMER-STYLING.md +3 -3
- package/app/components/02.molecules/cookie-consent-banner/CookieConsentBanner.vue +1 -1
- package/app/components/02.molecules/cookie-consent-banner/tests/CookieConsentBanner.spec.ts +1 -1
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +9 -9
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +16 -16
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +53 -1
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +8 -8
- package/app/components/02.molecules/input-copy/CONSUMER-STYLING.md +4 -4
- package/app/components/02.molecules/input-copy/InputCopy.vue +2 -2
- package/app/components/02.molecules/input-copy/tests/InputCopy.spec.ts +2 -2
- package/app/components/03.organisms/google-reviews/tests/GoogleReviews.spec.ts +4 -4
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +2 -2
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +2 -2
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +6 -6
- package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/form-errors/InputError.vue +43 -37
- package/app/components/05.forms/form-errors/stories/InputError.stories.ts +79 -1
- package/app/components/05.forms/form-errors/tests/InputError.spec.ts +29 -1
- package/app/components/05.forms/form-field/CONSUMER-STYLING.md +7 -0
- package/app/components/05.forms/form-field/stories/FormField.stories.ts +116 -2
- package/app/components/05.forms/form-fieldset/CONSUMER-STYLING.md +11 -1
- package/app/components/05.forms/form-fieldset/FormFieldset.vue +4 -2
- package/app/components/05.forms/form-fieldset/stories/FormFieldset.stories.ts +68 -4
- package/app/components/05.forms/form-fieldset/tests/FormFieldset.spec.ts +10 -0
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +80 -47
- package/app/components/05.forms/input-button/InputButton.vue +52 -100
- package/app/components/05.forms/input-button/playwright/input-button.playwright.ts +1 -1
- package/app/components/05.forms/input-button/stories/InputButton.stories.ts +145 -8
- package/app/components/05.forms/input-button/tests/InputButton.spec.ts +76 -46
- package/app/components/05.forms/input-button/tests/__snapshots__/InputButton.spec.ts.snap +17 -17
- package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +9 -1
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +8 -0
- package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +2 -2
- package/app/components/05.forms/input-description/CONSUMER-STYLING.md +14 -2
- package/app/components/05.forms/input-description/InputDescription.vue +2 -1
- package/app/components/05.forms/input-description/stories/InputDescription.stories.ts +70 -4
- package/app/components/05.forms/input-description/tests/InputDescription.spec.ts +2 -1
- package/app/components/05.forms/input-label/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/input-label/InputLabel.vue +1 -0
- package/app/components/05.forms/input-label/stories/InputLabel.stories.ts +68 -2
- package/app/components/05.forms/input-label/tests/InputLabel.spec.ts +13 -3
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-number/InputNumber.vue +4 -5
- package/app/components/05.forms/input-otp/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-otp/InputOtp.vue +2 -2
- package/app/components/05.forms/input-range/InputRange.vue +1 -1
- package/app/components/05.forms/input-select/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-select/InputSelect.vue +14 -5
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-text/InputText.vue +4 -4
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +7 -1
- package/app/components/05.forms/input-textarea/InputTextarea.vue +2 -3
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +4 -4
- package/app/components/05.forms/pending-effect/CONSUMER-STYLING.md +7 -3
- package/app/components/05.forms/pending-effect/PendingEffect.vue +3 -3
- package/app/components/05.forms/pending-effect/tests/PendingEffect.spec.ts +1 -1
- package/app/components/05.forms/segmented-control/CONSUMER-STYLING.md +125 -0
- package/app/components/05.forms/segmented-control/SegmentedControl.vue +231 -0
- package/app/components/05.forms/segmented-control/stories/SegmentedControl.stories.ts +215 -0
- package/app/components/05.forms/segmented-control/tests/SegmentedControl.spec.ts +202 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +7 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitch.vue +2 -0
- package/app/types/components/index.ts +1 -1
- package/package.json +1 -1
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +0 -31
- package/app/components/01.atoms/alert-mask/AlertMaskCore.vue +0 -166
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +0 -105
- package/app/components/01.atoms/alert-mask/tests/AlertMaskCore.spec.ts +0 -342
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +0 -99
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitch.vue +0 -251
- package/app/components/05.forms/triple-toggle-switch/stories/TripleToggleSwitch.stories.ts +0 -104
- package/app/components/05.forms/triple-toggle-switch/tests/TripleToggleSwitch.spec.ts +0 -138
- /package/app/types/components/{alert-mask-core.d.ts → alert-mask.d.ts} +0 -0
|
@@ -10,8 +10,9 @@ type: reference
|
|
|
10
10
|
|
|
11
11
|
`MarqueeScroller` renders an infinite horizontal scroller for small repeating items — client
|
|
12
12
|
logos, badges, testimonial avatars. The track is duplicated once so the loop is seamless
|
|
13
|
-
(
|
|
14
|
-
automatically on hover,
|
|
13
|
+
(`.marquee-scroller-group` renders twice: once real, once `aria-hidden="true"` and `inert`), and the
|
|
14
|
+
whole thing pauses automatically on hover, whenever focus is inside it, and when the user has
|
|
15
|
+
`prefers-reduced-motion` set.
|
|
15
16
|
|
|
16
17
|
## Props
|
|
17
18
|
|
|
@@ -24,11 +25,12 @@ automatically on hover, on keyboard focus, and when the user has `prefers-reduce
|
|
|
24
25
|
| `ariaLabel` | `string` | `"Scrolling content"` | `aria-label` on the root region — override for localisation. |
|
|
25
26
|
| `ariaDescription` | `string` | `"Use spacebar to pause or play the animation."` | Screen-reader-only instructions rendered inside the region — override for localisation. |
|
|
26
27
|
| `showControls` | `boolean` | `false` | Shows a visible pause/play button. Recommended (WCAG 2.2.2, Pause/Stop/Hide) whenever content scrolls automatically and indefinitely. |
|
|
27
|
-
| `respectReducedMotion` | `boolean` | `true` | Auto-pauses and disables the animation for users with `prefers-reduced-motion: reduce
|
|
28
|
+
| `respectReducedMotion` | `boolean` | `true` | Auto-pauses and disables the animation for users with `prefers-reduced-motion: reduce` (and keeps it paused after focus leaves). Focus-pausing no longer depends on it. |
|
|
28
29
|
| `playIcon` | `string` | `"mdi:play"` | Iconify icon name shown on the control button while paused. Ignored if the `toggle-icon` slot is used. |
|
|
29
30
|
| `pauseIcon` | `string` | `"mdi:pause"` | Iconify icon name shown on the control button while playing. Ignored if the `toggle-icon` slot is used. |
|
|
30
|
-
| `playLabel` | `string` | `"Play animation"` | Control button
|
|
31
|
-
| `pauseLabel` | `string` | `"Pause animation"` | Control button
|
|
31
|
+
| `playLabel` | `string` | `"Play animation"` | Control button text (visually hidden, its accessible name) while paused — override for localisation. |
|
|
32
|
+
| `pauseLabel` | `string` | `"Pause animation"` | Control button text (visually hidden, its accessible name) while playing — override for localisation. |
|
|
33
|
+
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root `.marquee-scroller`, via `useStyleClassPassthrough` (reacts to prop changes). |
|
|
32
34
|
|
|
33
35
|
Import the item types from the library's type exports when building `marqueeData`/`itemConfig`
|
|
34
36
|
outside the component:
|
|
@@ -52,8 +54,10 @@ One dynamically-named slot per `marqueeData` entry, named after that item's `id`
|
|
|
52
54
|
</MarqueeScroller>
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
Each slot is rendered twice (once in the visible group, once in the `aria-hidden`
|
|
56
|
-
so the loop wraps seamlessly
|
|
57
|
+
Each slot is rendered twice (once in the visible group, once in the `aria-hidden`, `inert`
|
|
58
|
+
duplicate group) so the loop wraps seamlessly. Don't put anything with side effects or unique ids
|
|
59
|
+
inside a slot. Links are fine: only the real copy is focusable. Items are fixed-size boxes; text
|
|
60
|
+
wraps inside them (`overflow-wrap: anywhere`) but content taller than `itemConfig.height` overflows.
|
|
57
61
|
|
|
58
62
|
`toggle-icon` (scoped with `{ isPaused: boolean }`) replaces the control button's icon entirely —
|
|
59
63
|
use this instead of `playIcon`/`pauseIcon` when an Iconify name isn't enough (custom SVG, a
|
|
@@ -70,16 +74,25 @@ different icon set):
|
|
|
70
74
|
## Accessibility behaviour
|
|
71
75
|
|
|
72
76
|
- The root is `role="region"` with `tabindex="0"` so it's independently focusable.
|
|
73
|
-
- Spacebar toggles pause/play while the region is focused
|
|
77
|
+
- Spacebar toggles pause/play while the region itself is focused (keydown from inside, e.g. on the
|
|
78
|
+
control button or a link, is left to that element). Arrow keys do nothing — there is no
|
|
74
79
|
manual-stepping behaviour, so the default `ariaDescription` doesn't claim one (fixed 2026-09-08;
|
|
75
80
|
it previously announced "arrow keys ... for manual control" with no implementation behind it).
|
|
76
|
-
-
|
|
77
|
-
|
|
81
|
+
- Focus anywhere inside the region (the region itself, or a link in an item) pauses the animation
|
|
82
|
+
and removes `aria-hidden` from the track, so the focused content is announced. Focus moving between
|
|
83
|
+
elements inside keeps it paused. When focus leaves, it resumes only if focus caused the pause and
|
|
84
|
+
the user doesn't prefer reduced motion: a pause chosen with the button or spacebar sticks.
|
|
85
|
+
Focus landing on the control button doesn't auto-pause, so one click pauses. (Changed 2026-10-17:
|
|
86
|
+
this used to listen for `focus`/`blur` on the region only, gated on `respectReducedMotion`, so
|
|
87
|
+
tabbing to a link in an item left it moving and `aria-hidden`.)
|
|
78
88
|
- Hovering anywhere over the scroller pauses the track and applies
|
|
79
89
|
`--marquee-scroller-group-hover-filter` (default `grayscale(1)`) to every item; hovering an
|
|
80
90
|
individual item restores it via `--marquee-scroller-item-hover-filter` (default `grayscale(0)`).
|
|
81
91
|
- `aria-live` switches to `"polite"` while paused so screen readers announce the paused content.
|
|
82
|
-
- The
|
|
92
|
+
- The track is `aria-hidden="true"` while playing (attribute removed when paused). The duplicate
|
|
93
|
+
group is always `aria-hidden="true"` and `inert`, so its copies of any links can't be focused.
|
|
94
|
+
- The control button is an `InputButton` (`variant="inline"`, icon-only): `playLabel`/`pauseLabel`
|
|
95
|
+
is its visually hidden text rather than an `aria-label`.
|
|
83
96
|
- All user-visible copy (`ariaLabel`, `ariaDescription`, `playLabel`, `pauseLabel`) is a plain
|
|
84
97
|
string prop with an English default — there's no i18n framework dependency in this library, so
|
|
85
98
|
pass translated strings from the consumer's own i18n solution.
|
|
@@ -110,11 +123,11 @@ theme); only its border width is (`--marquee-scroller-control-border-width`).
|
|
|
110
123
|
|
|
111
124
|
## Seamless loop math
|
|
112
125
|
|
|
113
|
-
The track duplicates `marqueeData` into two `.marquee-group` children so the loop can wrap without
|
|
126
|
+
The track duplicates `marqueeData` into two `.marquee-scroller-group` children so the loop can wrap without
|
|
114
127
|
a visible cut. The animation must shift the track by exactly one group's width plus its connecting
|
|
115
128
|
gap — not `translateX(-50%)`, which overshoots short of the seam once the track's own `gap`
|
|
116
129
|
between the two groups is added to its total width. This is computed as a private
|
|
117
|
-
`--_track-shift: calc(itemCount * (itemWidth + gap))` token and used in the `
|
|
130
|
+
`--_track-shift: calc(itemCount * (itemWidth + gap))` token and used in the `marquee-scroller-move`
|
|
118
131
|
keyframe. Fixed 2026-09-08 — the original `-50%` version produced a small but visible gap/overlap
|
|
119
132
|
at the loop point once more than a couple of items were in `marqueeData`.
|
|
120
133
|
|
|
@@ -125,6 +138,30 @@ section on a wide viewport), the loop briefly shows empty space before snapping
|
|
|
125
138
|
the visible track only spans as wide as the content, not the container. On mount (and on
|
|
126
139
|
container resize, via `ResizeObserver`), the component measures one rendered group's `scrollWidth`
|
|
127
140
|
against the root element's `offsetWidth` and repeats `marqueeData` internally (`repeatCount`)
|
|
128
|
-
enough times per `.marquee-group` so a single group's width always covers the container. This is
|
|
141
|
+
enough times per `.marquee-scroller-group` so a single group's width always covers the container. This is
|
|
129
142
|
transparent to consumers — it doesn't change `marqueeData` or any prop, only how many times each
|
|
130
143
|
item's slot is rendered internally. Fixed 2026-09-08.
|
|
144
|
+
|
|
145
|
+
The repeat count is capped at 50 copies per group, so tiny items in a huge container can't flood
|
|
146
|
+
the DOM (added 2026-10-17).
|
|
147
|
+
|
|
148
|
+
## Storybook
|
|
149
|
+
|
|
150
|
+
`Atoms/Effects/MarqueeScroller`: `WithControls`, `FocusableItems` (links in items, for the focus
|
|
151
|
+
behaviour), and `StressTest` (24 items of hostile copy, a broken image, long localised labels, a
|
|
152
|
+
single item, tiny items hitting the repeat cap, and empty data). Stories use the `CanvasSwitcher`
|
|
153
|
+
decorator.
|
|
154
|
+
|
|
155
|
+
## History
|
|
156
|
+
|
|
157
|
+
**2026-10-17** (stress-test pass, breaking class renames):
|
|
158
|
+
|
|
159
|
+
- Structural classes prefixed (`.marquee-scroller-item`, `-control`, `-track`, `-group`,
|
|
160
|
+
`-description`); bare `.reverse`/`.paused`/`.reduced-motion` became `data-*` attributes; keyframes
|
|
161
|
+
renamed `marquee-scroller-move`. See CONSUMER-STYLING.md for the full map.
|
|
162
|
+
- Raw `<button>` replaced with `InputButton variant="inline"` (icon-only slot).
|
|
163
|
+
- Focus-pausing reworked (focusin/focusout, see Accessibility behaviour); duplicate group `inert`.
|
|
164
|
+
- `prefers-reduced-motion` change listener is now removed on unmount.
|
|
165
|
+
- New `--marquee-scroller-focus-outline-colour` token (was bare `--theme-ring`).
|
|
166
|
+
- Added `styleClassPassthrough`. Removed the redundant `aspect-ratio: 1 / 1` on items (width and
|
|
167
|
+
height are both set, and it mis-centres on old Safari). Items wrap unbroken text.
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
`PendingEffect` is a decorative SVG that draws an animated dashed line around a busy button. It
|
|
6
6
|
is an internal part of `InputButton`: set `has-pending-effect` on the button and toggle
|
|
7
|
-
`is-pending`. Don't render it on its own. Its CSS depends on the host
|
|
8
|
-
(and `position: relative`, which that rule provides), plus
|
|
7
|
+
`is-pending`. Don't render it on its own. Its CSS depends on the host being `.input-button[data-pending-effect]`
|
|
8
|
+
(and `position: relative`, which that rule provides), plus `[data-pending]` to show and animate it.
|
|
9
|
+
Those were the classes `.pending-effect` and `.is-pending` until 2026-10-17.
|
|
9
10
|
|
|
10
11
|
```vue
|
|
11
12
|
<InputButton button-text="Submit" type="submit" has-pending-effect :is-pending="submitting" />
|
|
@@ -26,10 +27,10 @@ No slots, no events.
|
|
|
26
27
|
|
|
27
28
|
## Behaviour
|
|
28
29
|
|
|
29
|
-
- Hidden (`opacity: 0`) until the host has
|
|
30
|
+
- Hidden (`opacity: 0`) until the host has `[data-pending]`.
|
|
30
31
|
- The host must not clip overflow: the line is drawn outside the host's edge. `InputButton`
|
|
31
32
|
sets `overflow: hidden` on buttons generally, and switches it to `visible` when
|
|
32
|
-
|
|
33
|
+
`[data-pending-effect]` is present. Before 2026-09-26 it didn't, so the effect never rendered at all.
|
|
33
34
|
- Under `prefers-reduced-motion: reduce` it never shows, since the animation is the whole effect.
|
|
34
35
|
- `aria-hidden="true"` and `focusable="false"`: it is decorative. Busy state for assistive tech
|
|
35
36
|
is the host button's responsibility.
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
2
|
+
name: SegmentedControl
|
|
3
|
+
description: SegmentedControl icon radio pill with a sliding marker, v-model + v-model:field-data, system/light/dark marker gradients, CSS token API
|
|
4
4
|
type: reference
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# SegmentedControl
|
|
8
8
|
|
|
9
|
+
> **Renamed 2026-10-17**: `TripleToggleSwitch` → `SegmentedControl` (folder
|
|
10
|
+
> `05.forms/segmented-control/`). Classes `.triple-toggle-switch*` → `.segmented-control*`, tokens
|
|
11
|
+
> `--triple-toggle-switch-*` → `--segmented-control-*`; props, slots and behaviour unchanged.
|
|
12
|
+
>
|
|
9
13
|
> **Renamed 2026-09-27**: `TripleToggleSwitchCore` → `TripleToggleSwitch` (Control/Field naming,
|
|
10
|
-
> see `.claude/skills/component-naming.md`).
|
|
14
|
+
> see `.claude/skills/component-naming.md`).
|
|
11
15
|
|
|
12
16
|
## Overview
|
|
13
17
|
|
|
14
|
-
A pill
|
|
18
|
+
A pill of icon-only radio options (three is the usual case, any count works), with a circular marker that slides behind the
|
|
15
19
|
selected one. Native `<input type="radio">` elements do the work (keyboard arrows, form
|
|
16
20
|
submission), hidden under each icon; each option's `label` is rendered `sr-only`.
|
|
17
21
|
|
|
18
|
-
Lives in `app/components/05.forms/
|
|
22
|
+
Lives in `app/components/05.forms/segmented-control/`. Its one in-library consumer is
|
|
19
23
|
`DisplayThemeSwitch` (system/light/dark colour-scheme picker wired to `useSettingsStore`). Reach
|
|
20
|
-
for that for a theme switch; use `
|
|
24
|
+
for that for a theme switch; use `SegmentedControl` directly for any other three-way choice.
|
|
21
25
|
|
|
22
26
|
## Props
|
|
23
27
|
|
|
@@ -25,14 +29,13 @@ for that for a theme switch; use `TripleToggleSwitch` directly for any other thr
|
|
|
25
29
|
|---|---|---|---|
|
|
26
30
|
| `v-model` | `string \| number \| boolean` | (required) | Selected option `value`. |
|
|
27
31
|
| `v-model:field-data` | `IFormMultipleOptions` | (required) | Options (`data[]` of `{ id, name, value, label, icon? }`). Read-only in practice; a `computed` works. |
|
|
28
|
-
| `name` | `string` |
|
|
32
|
+
| `name` | `string` | generated | Radio `name`. Defaults to a unique `useId()` name, so several instances never share a group. |
|
|
29
33
|
| `aria-label` | `string` | `""` | Accessible name for the `role="radiogroup"`. Omitted from the DOM when empty. Always pass one. |
|
|
30
34
|
| `theme` | `FormUiTheme` | `"default"` | Rendered as `data-theme`. |
|
|
31
35
|
| `step-animation-duration` | `string` | `"250ms"` | Marker slide duration; also times the icon colour swap (see Behaviour). Ignored under `prefers-reduced-motion`. |
|
|
32
36
|
| `style-class-passthrough` | `string \| string[]` | `[]` | Classes on the root. |
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
page.
|
|
38
|
+
Element ids come from `useId()`, not from option `id`s, so duplicate or missing data ids are safe.
|
|
36
39
|
|
|
37
40
|
## Usage
|
|
38
41
|
|
|
@@ -54,18 +57,21 @@ const options = ref<IFormMultipleOptions>({
|
|
|
54
57
|
</script>
|
|
55
58
|
|
|
56
59
|
<template>
|
|
57
|
-
<
|
|
60
|
+
<SegmentedControl v-model="scheme" v-model:field-data="options" aria-label="Colour scheme" />
|
|
58
61
|
</template>
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
## Behaviour
|
|
62
65
|
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
- One column per option (`repeat(<count>, minmax(0, 1fr))`), so two, four or five options lay out
|
|
67
|
+
correctly; three is just the common case.
|
|
68
|
+
- Options without an `icon` render an empty circle (the label is `sr-only`), so give each option an
|
|
69
|
+
icon.
|
|
70
|
+
- The marker sits in a second grid identical to the options grid and moves with
|
|
71
|
+
`inset-inline-start: calc(index * (100% + gap))`, where `100%` is one column. No script measures
|
|
72
|
+
anything, so it's right from the first paint, follows token and size changes, and moves the
|
|
73
|
+
correct way in right-to-left layouts. A value that matches no option hides the marker (no
|
|
74
|
+
`data-has-selection` on the root).
|
|
69
75
|
- Icon colour swaps are timed to the marker: the newly active icon waits half of
|
|
70
76
|
`stepAnimationDuration`, then fades to the active colour over the other half, so it lands as the
|
|
71
77
|
marker arrives rather than turning white on the bare track. The previous icon fades back straight
|
|
@@ -73,12 +79,12 @@ const options = ref<IFormMultipleOptions>({
|
|
|
73
79
|
- Option circles are sized by their content (1em icon + equal padding) with `place-items: center`,
|
|
74
80
|
not `aspect-ratio`; older iPad Safari mis-centred the icons under the `aspect-ratio` version.
|
|
75
81
|
- Marker gradients only exist for the values `system`, `light` and `dark`; other values get the
|
|
76
|
-
plain `--
|
|
82
|
+
plain `--segmented-control-marker-surface` background.
|
|
77
83
|
|
|
78
84
|
## Styling
|
|
79
85
|
|
|
80
86
|
Full token list in `CONSUMER-STYLING.md` next to the component. Key tokens:
|
|
81
|
-
`--
|
|
87
|
+
`--segmented-control-surface`, `-border`, `-gap`, `-padding`, `-option-padding`, `-icon-size`,
|
|
82
88
|
`-marker-gradient-system`/`-light`/`-dark`, `-option-icon-color`/`-color-active`.
|
|
83
89
|
|
|
84
90
|
## Notes
|
|
@@ -89,3 +95,12 @@ Full token list in `CONSUMER-STYLING.md` next to the component. Key tokens:
|
|
|
89
95
|
dead `--_form-border-radius` token and commented-out CSS removed; three identical per-id icon
|
|
90
96
|
colour rules collapsed to one `.option-icon` rule (the per-id classes are still rendered);
|
|
91
97
|
`fieldData` is now a typed required `defineModel`. Tests added.
|
|
98
|
+
|
|
99
|
+
**2026-10-17**: resting shadow ring (`--segmented-control-shadow`), on the wrapper. Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
|
100
|
+
- 2026-10-17 migration: inner classes prefixed (`.option-group` → `.segmented-control-option`,
|
|
101
|
+
etc.) and state moved to `data-checked`/`data-value`/`data-has-selection`; the measured marker
|
|
102
|
+
(first option's width, 250ms timer, no resize handling) replaced by a CSS-only grid position;
|
|
103
|
+
any option count supported; ids and the default `name` from `useId()` (the fixed default name
|
|
104
|
+
made two instances one radio group); icons `aria-hidden`, marker layer `aria-hidden`;
|
|
105
|
+
passthrough now reactive; generic values no longer show a white icon on a white marker.
|
|
106
|
+
`StressTest` story added.
|
|
@@ -206,6 +206,6 @@ No `actions` slot — the entire card renders as an anchor (`NuxtLink` for the i
|
|
|
206
206
|
- Root markup is two rows (`grid-template-rows: auto 1fr`): `.services-card-image-wrapper`, then a `.services-card-details` (`display: flex; flex-direction: column`) holding the eyebrow, title, description, and a `.services-card-footer` wrapper. Description length is controlled by `--services-card-description-line-clamp` (default `none`) rather than a fixed-height grid row.
|
|
207
207
|
- `.services-card-footer` groups the meta row and the `actions` slot and gets `margin-block-start: auto`, pinning them to the bottom of the card. Combined with the root's `1fr` details row, this means when `ServicesCardGrid`'s default `align-items: stretch` makes a card taller than its own content (to match a taller sibling in the same row), the extra height goes to `.services-card-footer`'s top margin rather than leaving whitespace below the actions slot — so the meta row and actions/button line up across a row of cards regardless of each card's description length. This does the visual job of CSS subgrid without needing a subgrid chain across `ServicesCard`/`ServicesCardGrid` (which would also break whenever cards in a row don't all render the same optional rows — `.services-card-meta` and `actions` are both conditional).
|
|
208
208
|
- Image has a `3/4` aspect ratio with a subtle scale-on-hover effect, triggered by hovering the image only (not the whole card). When the card is a whole-card link styled with its own hover effect (e.g. `--services-card-transform-hover`), set `--services-card-image-zoom-transform: none` so the two effects don't stack; the "Dark Overlay Cards" story does this. Keep the zoom for the `actions`-slot layout, where the card itself has no hover state. The hover is on `.services-card-image-wrapper`, not the image, so it still fires when the overlaid titles cover the image. `.services-card-image-details` is `position: relative; z-index: 1` because the image's hover `transform` would otherwise paint over it, and `.services-card-image-wrapper` sets `isolation: isolate` so that z-index stays inside the card.
|
|
209
|
-
- `.services-card`, `.services-card-image-wrapper`, `.services-card-details`, and `.services-card-footer` all set `min-inline-size: 0`. Without it, an unbreakable child — most commonly a long `actions` slot button label,
|
|
209
|
+
- `.services-card`, `.services-card-image-wrapper`, `.services-card-details`, and `.services-card-footer` all set `min-inline-size: 0`. Without it, an unbreakable child — most commonly a long `actions` slot button label, kept on one line with `--input-button-text-white-space: nowrap` (the default until 2026-10-17) — forces its content's min-content width up through the flex/grid chain and widens that one card's grid column in `ServicesCardGrid` wider than its siblings (the image just rides along on the widened column; it isn't the actual cause). If you see one card/column wider than the rest with cut-off content, check for a long unbreakable string in a slot before assuming it's an image sizing issue.
|
|
210
210
|
- Usually consumed via `ServicesCardGrid` rather than directly.
|
|
211
211
|
- The "Dark Overlay Cards" story shows a full restyle using tokens only: dark panel, flush image, titles over the image, hover lift (`--services-card-transform-hover`), and EyebrowText typography tokens (`--eyebrow-text-font-style`, `-letter-spacing`, etc.) set on the card so they inherit into its eyebrow.
|
|
@@ -169,7 +169,7 @@ length, cancelling the per-frame checks while the highlight was still sliding an
|
|
|
169
169
|
colours (dark selected label, invisible neighbour). A More-button resize also re-places both
|
|
170
170
|
indicators (`syncIndicators(true)`), since its box moves even when no tab changes sides, and an
|
|
171
171
|
overflow change refreshes with `refreshTabs(true)` so an in-flight move retargets instead of
|
|
172
|
-
snapping. This replaces the per-component timing trick `
|
|
172
|
+
snapping. This replaces the per-component timing trick `SegmentedControl` uses (swap
|
|
173
173
|
colour at half the slide), which only works for single-step moves; a tab jump passes under
|
|
174
174
|
several labels.
|
|
175
175
|
|
|
@@ -141,3 +141,5 @@ error id when `field-has-error` is true.
|
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
The `description` slot was removed on 2026-09-29; use `descriptionText` or `descriptionHtml`.
|
|
144
|
+
|
|
145
|
+
**2026-10-17**: resting shadow ring (`--toggle-switch-shadow`), on the track. Theme step 2 by default, so it turns red under `data-invalid`; width is the global `--form-element-shadow-width`. Hover now draws a 1px outline in the border colour with no offset, over the ring's inner edge (was a 2px outline at a 2px offset in the focus colour); focus is unchanged and sits just outside the ring.
|
package/.claude/skills/index.md
CHANGED
|
@@ -77,8 +77,8 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
77
77
|
├── composable-anchored-popover.md — useAnchoredPopover: popover + anchor positioning with JS fallbacks for Safari 16 (no Popover API) and 17–18 (no anchor positioning); used by ActionMenu/SelectMenu/DeepExpandingMenu/DisplayTooltip/PopOver
|
|
78
78
|
└── components/
|
|
79
79
|
├── alert-content.md — AlertContent + AlertContentInner: themed alert panel (icon/title/content/actions/dismiss) under DisplayToast, DisplayPrompt and AlertMaskedContent; showIcon, #actions row, --alert-content-* tokens, app.config icon map
|
|
80
|
-
├── alert-mask
|
|
81
|
-
├── alert-masked-content.md — AlertMaskedContent: AlertContentInner inside
|
|
80
|
+
├── alert-mask.md — AlertMask (renamed from AlertMaskCore): SVG border/fill sized to slotted content via ResizeObserver; config-prop geometry, --alert-mask-* colour tokens
|
|
81
|
+
├── alert-masked-content.md — AlertMaskedContent: AlertContentInner inside AlertMask's SVG mask (accent cut-out border + translucent fill); masked variant for DisplayToast/DisplayPrompt; --alert-masked-content-* colour tokens
|
|
82
82
|
├── animated-svg-text.md — AnimatedSvgText: inline SVG stroke-draw-then-fill animation, text slot, CSS token API
|
|
83
83
|
├── accordian-core.md — AccordianCore indexed dynamic slots (accordian-{n}-summary/icon/content), exclusive-open grouping
|
|
84
84
|
├── eyebrow-text.md — EyebrowText props, usage patterns, styling
|
|
@@ -123,7 +123,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
123
123
|
├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-gap/min-col-size-{small,default,large} tokens, is-responsive, semantic tag + aria
|
|
124
124
|
├── display-avatar.md — DisplayAvatar (02.molecules): circular avatar with image/initials fallback (alt announced), broken-image fallback, data-size scale tokens, image-size, --display-avatar-* colours, chip badge, icon slot
|
|
125
125
|
├── profile-section.md — ProfileSection (02.molecules): picture + numbered profile-info-N slots, eyebrow/hero header (above, or over the picture top/bottom on a scrim via header-placement), profileLinks, container-query two-column layout, .location/.services highlight tokens
|
|
126
|
-
├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping
|
|
126
|
+
├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping SegmentedControl, wired to useSettingsStore, labels/icons props, small sizing variant
|
|
127
127
|
├── card-core.md — CardCore: generic card container, dynamic named slots as rows, 4 variants, blurred backdrop layer, full CSS token API
|
|
128
128
|
├── action-menu.md — ActionMenu + ActionMenuItem: ellipsis trigger + anchored popover menu, indexed item-{n} slots, link/button items, full CSS token API
|
|
129
129
|
├── select-menu.md — SelectMenu: v-model single-select listbox popover (ActionMenu's popover mechanics, InputSelect's selected-option semantics), icon/text/chevron trigger toggles, checkmark on selected option, options-array driven, full CSS token API
|
|
@@ -146,7 +146,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
146
146
|
├── display-tooltip-defined.md — DisplayTooltipDefined: structured title/body/action tooltip content with close button, composes DisplayTooltip
|
|
147
147
|
├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
|
|
148
148
|
├── input-text.md — InputText (bare control), InputTextField, InputPasswordField, InputTextAsNumberField: renamed from Core/WithLabel, data-* state hooks, readonly/autocomplete props, leak-free digit filtering, wrapper attr-leak/empty-description/passthrough fixes, rounded+clamped stepping, min/max pass-through, CSS token API, stress stories
|
|
149
|
-
├── input-button.md — InputButton (renamed from InputButtonCore 2026-09-28): button/NuxtLink/a,
|
|
149
|
+
├── input-button.md — InputButton (renamed from InputButtonCore 2026-09-28): button/NuxtLink/a; primary filled, secondary outlined, tertiary text-only, inline unstyled; data-* state hooks; shadow ring + border-colour hover; wrapping labels; icon slots; readonly (aria-disabled, activation blocked); tokens
|
|
150
150
|
├── input-range.md — InputRange: native range-slider primitive, markers/datalist slots, dead --theme-form-range-accent-color token fixed, CSS token API; Variants section covers InputRangeField; renamed from InputRangeCore/InputRangeDefault 2026-10-16, clamped stepping, useId datalist id, data-* hooks
|
|
151
151
|
├── input-number.md — InputNumber: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberField; clamped, rounded stepping, data-* state hooks, min/step-aware inputmode
|
|
152
152
|
├── input-otp.md — InputOtp: one-time-code digit boxes (paste/autofill spreading, Backspace/arrow/Home/End nav, Enter submits, leading zeros kept, complete event, hidden input); Variants section covers InputOtpField
|
|
@@ -163,7 +163,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
|
|
|
163
163
|
├── input-checkbox.md — MultipleCheckboxes/SingleCheckbox: fieldset-wrapped checkbox group (data-driven, button or labelled) and single checkbox, per-box native-required/styleClassPassthrough/data-testid bugs fixed, spacing token API, wraps hostile content (fieldset min-inline-size, useId option ids)
|
|
164
164
|
├── input-radio.md — MultipleRadiobuttons: fieldset radiogroup (data-driven, button or labelled), shared-name grouping bug fixed, dead label/placeholder/equalCols props removed, data-options-layout hook, spacing token API, useId button ids, wraps hostile content
|
|
165
165
|
├── toggle-switch.md — ToggleSwitch (bare control), ToggleSwitchField (labelled, layout="stacked"|"inline", replaces ToggleSwitchWithLabelInline): renamed from Core/WithLabel, role="switch", prefixed inner classes + data-checked, single-toggle click handling, reduced motion, round prop + border-radius token, track width from geometry, CSS token API, stress stories
|
|
166
|
-
├──
|
|
166
|
+
├── segmented-control.md — SegmentedControl (renamed from TripleToggleSwitch): icon radio pill for any number of options with sliding marker, radiogroup + ariaLabel, system/light/dark marker-gradient tokens, reduced-motion, CSS token API
|
|
167
167
|
├── entry-animation.md — EntryAnimation: scroll-driven entry animation wrapper (slide-in/zoom-reveal/exit-blur utility classes), skipAnimation for above-the-fold loop items, reduced-motion handled at the CSS layer
|
|
168
168
|
├── column-flow-grid.md — ColumnFlowGrid (renamed from MasonryGrid): CSS multi-column text-flow layout, named dynamic slots (no count/data prop), itemMinWidth/gap/unit sizing, CSS token API; not a true masonry, see "which one do I want?"
|
|
169
169
|
├── masonry-grid.md — MasonryGrid: real measured-height masonry (greedy shortest-column packing, animated resize) absorbed from the now-retired MasonryGridOrdered; named dynamic slots, fixedWidth/justify, CSS token API
|
|
@@ -393,7 +393,9 @@ story: "See the [CookieConsentBanner](?path=/story/molecules-cookieconsentbanner
|
|
|
393
393
|
default `"padded"` layout insets the canvas by 1rem, which masks viewport gutters, full-bleed
|
|
394
394
|
tracks and edge alignment (`PageRow`, `SliderGallery`). To swap canvas widths inside a story,
|
|
395
395
|
add the `CanvasSwitcher` decorator from `PageRow.stories.ts` (container-sized components only;
|
|
396
|
-
see `components/canvas-switcher.md`).
|
|
396
|
+
see `components/canvas-switcher.md`). The switcher renders its own buttons, so for a component
|
|
397
|
+
with Playwright tests that grab the story's only `role=button` (`InputButton`), put it on the
|
|
398
|
+
`StressTest` story alone, not in the meta.
|
|
397
399
|
- Use `table: { category: "..." }` in `argTypes` when a component has many props — it groups
|
|
398
400
|
them in the Storybook controls panel (e.g. `"Model"`, `"Basic"`, `"Validation"`, `"Styling"`, `"Slots"`).
|
|
399
401
|
- Export multiple named stories (`Default`, `WithError`, `Outlined`, etc.) when you want
|
|
@@ -191,6 +191,30 @@ Three tiers deep here: instance/global override → "look like the primary butto
|
|
|
191
191
|
slot underneath that. Each link in the chain is a real, resolvable token — nothing depends on a
|
|
192
192
|
consuming app having declared anything.
|
|
193
193
|
|
|
194
|
+
## Global colour knobs that follow the theme
|
|
195
|
+
|
|
196
|
+
A site-wide colour knob that should follow each element's `data-theme` (or `data-invalid`) can't be
|
|
197
|
+
declared on `:root` as `var(--colour-theme-2)`: a custom property holding `var()` is computed where
|
|
198
|
+
it's declared and inherited as that value, so every element gets the root theme's colour. Leave the
|
|
199
|
+
knob undeclared and fall back at the point of use instead:
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
/* global: only the geometry is declared */
|
|
203
|
+
:where(html) {
|
|
204
|
+
--form-element-shadow-width: 0.2rem;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* component: the colour fallback is evaluated on the themed element */
|
|
208
|
+
.input-text-wrapper {
|
|
209
|
+
box-shadow: 0 0 0 var(--form-element-shadow-width)
|
|
210
|
+
var(--input-text-shadow, var(--form-element-shadow-colour, var(--colour-theme-2)));
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`--colour-theme-N` and the `--theme-*` slots are themselves re-declared on every `[data-theme]` and
|
|
215
|
+
`[data-invalid]` element (`theme-ramp.css`, `_theme-slots.css`), which is why they work at the point of
|
|
216
|
+
use. Rule: `css-theme-relative-globals.md`.
|
|
217
|
+
|
|
194
218
|
## What this does NOT change
|
|
195
219
|
|
|
196
220
|
- Tier 1 theme slots (`_theme-slots.css`) stay bare `var()`, no fallback, by design.
|
|
@@ -42,7 +42,7 @@ From `00.element-defaults.css` through `09.animation-motion.css`:
|
|
|
42
42
|
| --- | --- |
|
|
43
43
|
| `01.field-layout-container-level.css` | `--field-margin-block`, `--field-gap-block`, `--field-gap-inline`, `--field-label-gap`, `--field-description-gap`, `--field-error-gap` |
|
|
44
44
|
| `02.typography.css` | `--field-label-font-size/-weight/-line-height`, `--input-font-size`, `--input-line-height`, `--theme-input-placeholder-font-size`, `--button-font-size/-line-height/-weight/-text-transform`, `--field-description-font-size/-line-height`, `--field-error-font-size/-weight` |
|
|
45
|
-
| `03.generic-input-geometry.css` | `--input-padding-block/-inline`, `--input-padding-inline-with-icon`, `--form-element-border-width`, `--form-element-border-bottom-width-underlined`, `--form-input-border-radius[-underlined]`, `--form-element-outline-width[-focus]`, `--form-element-outline-offset-focus`, `--input-focus-ring-width/-offset`, `--input-underline-width`, `--input-min-height`, `--input-element-line-height`, plus per-element-type geometry (textarea/select/range/checkbox/toggle — see file for the full set) |
|
|
45
|
+
| `03.generic-input-geometry.css` | `--input-padding-block/-inline`, `--input-padding-inline-with-icon`, `--form-element-border-width`, `--form-element-border-bottom-width-underlined`, `--form-input-border-radius[-underlined]`, `--form-element-outline-width[-focus]`, `--form-element-outline-offset-focus`, `--form-element-shadow-width` (the resting shadow ring on bordered controls and primary/secondary buttons; its colour `--form-element-shadow-colour` is deliberately undeclared so each control falls back to its own theme step 2), `--input-focus-ring-width/-offset`, `--input-underline-width`, `--input-min-height`, `--input-element-line-height`, plus per-element-type geometry (textarea/select/range/checkbox/toggle — see file for the full set) |
|
|
46
46
|
| `04.slot-icon-system.css` | `--input-slot-width/-gap`, `--input-icon-size`, `--input-icon-slot-gap`, `--input-icon-color[-focus/-error]` |
|
|
47
47
|
| `05.checkbox-radio-geometry.css` | `--control-size`, `--control-border-radius`, `--radio-border-radius`, `--control-border-width`, `--control-indicator-scale`, `--control-label-gap` |
|
|
48
48
|
| `06.button-geometry.css` | `--button-padding-block/-inline`, `--button-border-radius[-icon-only]`, `--button-border-width`, `--button-outline-width`, `--button-focus-ring-width/-offset`, `--button-icon-gap` |
|
|
@@ -40,7 +40,7 @@ affects buttons, inputs, prompts, and toasts simultaneously:
|
|
|
40
40
|
| --------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
41
41
|
| `--theme-surface` | `--colour-theme-7` / `9` | Filled button/chip surface |
|
|
42
42
|
| `--theme-surface-hover` | `--colour-theme-9` / `7` | Hover state of filled surface |
|
|
43
|
-
| `--theme-surface-inverted` | `--colour-theme-9` / `2` | Inverted surface (
|
|
43
|
+
| `--theme-surface-inverted` | `--colour-theme-9` / `2` | Inverted surface (dark panels; InputButton's secondary used it until 2026-10-17) |
|
|
44
44
|
| `--theme-accent` | `--colour-theme-5` / `4` | Decorative accent strip (prompt/toast left edge) |
|
|
45
45
|
| `--theme-surface-subtle` | `--colour-theme-1` / `9` | Subtle body bg for prompt/toast, outline element hover |
|
|
46
46
|
| `--theme-border` | `--colour-theme-6` / `5` | Input/card border |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SRCDEV AlertMask Basic": {
|
|
3
|
+
"description": "AlertMask: SVG border and fill sized to slotted content",
|
|
4
|
+
"scope": "vue,html",
|
|
5
|
+
"body": [
|
|
6
|
+
"<AlertMask>",
|
|
7
|
+
" $0",
|
|
8
|
+
"</AlertMask>"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"SRCDEV AlertMask Custom Config": {
|
|
12
|
+
"description": "AlertMask with custom colour/geometry config",
|
|
13
|
+
"scope": "vue,html",
|
|
14
|
+
"body": [
|
|
15
|
+
"<AlertMask",
|
|
16
|
+
" :config=\"{",
|
|
17
|
+
" borderColour: '${1:var(--theme-accent)}',",
|
|
18
|
+
" backgroundColour: '${2:rgba(0, 0, 0, 0.3)}',",
|
|
19
|
+
" radiusLeft: ${3:8},",
|
|
20
|
+
" radiusRight: ${4:4},",
|
|
21
|
+
" borderLeft: ${5:6},",
|
|
22
|
+
" borderTop: ${6:1},",
|
|
23
|
+
" borderRight: ${7:1},",
|
|
24
|
+
" borderBottom: ${8:1},",
|
|
25
|
+
" }\"",
|
|
26
|
+
">",
|
|
27
|
+
" $0",
|
|
28
|
+
"</AlertMask>"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"SRCDEV AlertMask CSS Override": {
|
|
32
|
+
"description": "CSS override scaffold for AlertMask colour tokens",
|
|
33
|
+
"scope": "css",
|
|
34
|
+
"body": [
|
|
35
|
+
".${1:my-section} {",
|
|
36
|
+
" --alert-mask-border-colour: $2;",
|
|
37
|
+
" --alert-mask-background-colour: $3;",
|
|
38
|
+
"}"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "FormField layout wrapper around a single form control",
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
|
-
"<FormField width=\"${1|wide,medium,narrow|}\" :has-gutter=\"${2|false,true|}\" :field-has-error=\"!!$
|
|
6
|
+
"<FormField width=\"${1|wide,medium,narrow|}\" :has-gutter=\"${2|false,true|}\" :field-has-error=\"!!${3:errors.fieldName}\">",
|
|
7
7
|
" $0",
|
|
8
8
|
"</FormField>"
|
|
9
9
|
]
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
"description": "CSS override scaffold for FormField tokens",
|
|
13
13
|
"scope": "css",
|
|
14
14
|
"body": [
|
|
15
|
-
".$
|
|
15
|
+
".${1:my-page} {",
|
|
16
16
|
" .form-field {",
|
|
17
|
-
" --form-field-max-width-narrow: $2;",
|
|
18
|
-
" --form-field-gutter-width: $3;",
|
|
19
|
-
" --form-field-margin-block: $4;",
|
|
20
|
-
" --form-field-background-color: $5;",
|
|
17
|
+
" --form-field-max-width-narrow: ${2:400px};",
|
|
18
|
+
" --form-field-gutter-width: ${3:1.6rem};",
|
|
19
|
+
" --form-field-margin-block: ${4:0 1.6rem};",
|
|
20
|
+
" --form-field-background-color: ${5:transparent};",
|
|
21
|
+
" --form-field-border-radius: ${6:0.4rem};",
|
|
21
22
|
" }",
|
|
22
23
|
"}"
|
|
23
24
|
]
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"scope": "vue,html",
|
|
5
5
|
"body": [
|
|
6
6
|
"<FormFieldset",
|
|
7
|
-
" id=\"$
|
|
8
|
-
" name=\"$
|
|
9
|
-
" legend=\"$
|
|
7
|
+
" id=\"${1:fieldId}\"",
|
|
8
|
+
" name=\"${2:fieldName}\"",
|
|
9
|
+
" legend=\"${3:Legend}\"",
|
|
10
10
|
" group-role=\"${4|group,radiogroup|}\"",
|
|
11
|
-
" :field-has-error=\"!!$
|
|
11
|
+
" :field-has-error=\"!!${5:errors.fieldName}\"",
|
|
12
12
|
">",
|
|
13
13
|
" <template #content>",
|
|
14
14
|
" $0",
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
"description": "CSS override scaffold for FormFieldset tokens",
|
|
21
21
|
"scope": "css",
|
|
22
22
|
"body": [
|
|
23
|
-
".$
|
|
23
|
+
".${1:my-page} {",
|
|
24
24
|
" .form-fieldset {",
|
|
25
|
-
" --form-fieldset-padding-block: $
|
|
26
|
-
" --form-fieldset-padding-inline: $
|
|
27
|
-
" --form-fieldset-border: $
|
|
28
|
-
" --form-fieldset-legend-color: $
|
|
29
|
-
" --form-fieldset-legend-font-size: $
|
|
30
|
-
" --form-fieldset-legend-font-weight: $
|
|
31
|
-
" --form-fieldset-legend-margin-block: $
|
|
25
|
+
" --form-fieldset-padding-block: ${2:0};",
|
|
26
|
+
" --form-fieldset-padding-inline: ${3:0};",
|
|
27
|
+
" --form-fieldset-border: ${4:0};",
|
|
28
|
+
" --form-fieldset-legend-color: ${5:inherit};",
|
|
29
|
+
" --form-fieldset-legend-font-size: ${6:var(--step-5)};",
|
|
30
|
+
" --form-fieldset-legend-font-weight: ${7:normal};",
|
|
31
|
+
" --form-fieldset-legend-margin-block: ${8:0.8rem};",
|
|
32
32
|
" }",
|
|
33
33
|
"}"
|
|
34
34
|
]
|