srcdev-nuxt-components 9.4.9 → 9.4.11

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 (178) hide show
  1. package/.claude/commands/migrate-component.md +38 -6
  2. package/.claude/component-ledger/audit.json +1 -1
  3. package/.claude/component-ledger/build.mjs +26 -0
  4. package/.claude/component-ledger/output.html +8 -3
  5. package/.claude/component-ledger/template.html +7 -2
  6. package/.claude/skills/component-dynamic-slots.md +1 -1
  7. package/.claude/skills/components/alert-mask-core.md +84 -0
  8. package/.claude/skills/components/canvas-switcher.md +1 -1
  9. package/.claude/skills/components/carousel-flip.md +112 -168
  10. package/.claude/skills/components/container-glow.md +123 -0
  11. package/.claude/skills/components/dashboard-quad-grid.md +98 -0
  12. package/.claude/skills/components/dashboard-stats-grid.md +103 -0
  13. package/.claude/skills/components/display-theme-switch.md +60 -0
  14. package/.claude/skills/components/glowing-border.md +66 -0
  15. package/.claude/skills/components/input-number-core.md +214 -0
  16. package/.claude/skills/components/input-range-core.md +142 -0
  17. package/.claude/skills/components/input-select-core.md +151 -0
  18. package/.claude/skills/components/input-text-core.md +16 -0
  19. package/.claude/skills/components/input-textarea-core.md +154 -0
  20. package/.claude/skills/components/section-parallax.md +32 -10
  21. package/.claude/skills/components/select-menu.md +195 -0
  22. package/.claude/skills/components/toggle-switch-core.md +150 -0
  23. package/.claude/skills/components/ui-block-decorated.md +56 -0
  24. package/.claude/skills/components/wipe-away-vertical.md +98 -0
  25. package/.claude/skills/icon-sets.md +1 -1
  26. package/.claude/skills/index.md +15 -1
  27. package/.vscode/css-custom-data.json +38 -0
  28. package/.vscode/settings.json +3 -0
  29. package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
  30. package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
  31. package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
  32. package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
  33. package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
  34. package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
  35. package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
  36. package/.vscode/srcdev-component-input-number.code-snippets +65 -0
  37. package/.vscode/srcdev-component-input-range.code-snippets +85 -0
  38. package/.vscode/srcdev-component-input-select.code-snippets +42 -0
  39. package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
  40. package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
  41. package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
  42. package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
  43. package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
  44. package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
  45. package/README.md +18 -37
  46. package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
  47. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
  48. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
  49. package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
  50. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
  51. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
  52. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
  53. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
  54. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
  55. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
  56. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
  57. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
  58. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
  59. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
  60. package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
  61. package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
  62. package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
  63. package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
  64. package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
  65. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
  66. package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
  67. package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
  68. package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
  69. package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
  70. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
  71. package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
  72. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
  73. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
  74. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
  75. package/app/components/01.atoms/card/CardCore.vue +4 -4
  76. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
  77. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
  78. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
  79. package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
  80. package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
  81. package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
  82. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
  83. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
  84. package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
  85. package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
  86. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
  87. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
  88. package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
  89. package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
  90. package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
  91. package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
  92. package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
  93. package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
  94. package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
  95. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
  96. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
  97. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
  98. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
  99. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
  100. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
  101. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
  102. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
  103. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
  104. package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
  105. package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
  106. package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
  107. package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
  108. package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
  109. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
  110. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
  111. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
  112. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
  113. package/app/components/05.forms/form-field/FormField.vue +1 -1
  114. package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
  115. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
  116. package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
  117. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
  118. package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
  119. package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
  120. package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
  121. package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
  122. package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
  123. package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
  124. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
  125. package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
  126. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
  127. package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
  128. package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
  129. package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
  130. package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
  131. package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
  132. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
  133. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
  134. package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
  135. package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
  136. package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
  137. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
  138. package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
  139. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
  140. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
  141. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
  142. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
  143. package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
  144. package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
  145. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
  146. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
  147. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
  148. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
  149. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
  150. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
  151. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
  152. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
  153. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
  154. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
  155. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
  156. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
  157. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
  158. package/app/types/components/container-glow.d.ts +8 -0
  159. package/app/types/components/index.ts +2 -1
  160. package/app/types/components/select-menu.d.ts +5 -0
  161. package/package.json +3 -2
  162. package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
  163. package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
  164. package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
  165. package/app/components/carousels/CarouselBasic.vue +0 -317
  166. package/app/components/carousels/CarouselInfinite.vue +0 -358
  167. package/app/components/container-glow/ContainerGlowCore.vue +0 -296
  168. package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
  169. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
  170. package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
  171. package/app/components/glowing-border/GlowingBorder.vue +0 -142
  172. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
  173. package/app/components/layout-grids/LayoutGridB.vue +0 -134
  174. package/app/components/test-storybook/TestStorybook.vue +0 -49
  175. package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
  176. package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
  177. package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
  178. package/app/types/components/carousel-basic.d.ts +0 -19
@@ -0,0 +1,195 @@
1
+ # SelectMenu
2
+
3
+ ## Overview
4
+
5
+ `SelectMenu` is a trigger-and-popover component for selecting from a list of options — a custom,
6
+ fully token-styled alternative to a native `<select>` or `InputSelectCore`, built for non-form
7
+ contexts (language switchers, category filters, faceted filter bars, multi-pick treatment lists).
8
+ It reuses the same Popover API + CSS anchor-positioning mechanism as `ActionMenu` (see that skill
9
+ doc), but its items carry a `value`/`label`/optional `icon`, and the whole thing is driven by
10
+ `v-model` instead of slotted actions. Defaults to single-select (checkmark, closes on pick); set
11
+ `multiple` for a checkbox-per-option multi-select where `v-model` is an array and the popover stays
12
+ open between picks.
13
+
14
+ **Location**: `app/components/02.molecules/select-menu/`
15
+
16
+ ---
17
+
18
+ ## Components
19
+
20
+ ### SelectMenu
21
+
22
+ | Prop | Type | Default | Notes |
23
+ |---|---|---|---|
24
+ | `options` | `SelectMenuOption[]` | — | **Required.** `{ value: string \| number; label: string; icon?: string }[]`. |
25
+ | `label` | `string` | — | **Required.** Accessible name for the trigger + listbox (`aria-label`). Also the trigger's fallback text when nothing is selected and no `placeholder` is set — this doubles as a visible category tag, e.g. `"Choose a service"`. |
26
+ | `placeholder` | `string` | `undefined` | Trigger text shown when nothing is selected. Falls back to `label` when omitted. |
27
+ | `showIcon` | `boolean` | `true` | Show the selected option's icon in the trigger. |
28
+ | `showLabel` | `boolean` | `true` | Show the selected option's label (or placeholder/label fallback) text in the trigger. Set `false` for an icon-only compact trigger. |
29
+ | `showChevron` | `boolean` | `true` | Show the trailing chevron in the trigger. |
30
+ | `multiple` | `boolean` | `false` | Allow selecting more than one option. Each option gets a checkbox indicator, `v-model` becomes an array, and picking an option leaves the popover open so more can be toggled. Trigger icon is not shown when `true` (no single option to represent). |
31
+ | `showSelectionInTrigger` | `boolean` | `false` | Multiple mode only. When `true`, the trigger text updates to a comma-separated list of the currently checked options instead of staying fixed on `placeholder`/`label`. No effect outside `multiple`. |
32
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes on the root `<div>`. |
33
+
34
+ **v-model**
35
+
36
+ `defineModel<string | number | (string | number)[] | undefined>({ default: undefined })` — the
37
+ selected value(s). In single-select mode (default) it's the one selected option's `value`, or
38
+ `undefined` if nothing is selected — a valid, expected state that renders the placeholder. With
39
+ `multiple`, it's an array of selected values (`[]` when nothing is selected). `{ default: undefined }`
40
+ is this repo's fix for the `vue/require-default-prop` false positive on `defineModel`-declared
41
+ props that are legitimately optional — unlike `InputSelectCore`'s `modelValue`, which uses
42
+ `{ required: true }` because a native form field genuinely can't be meaningfully empty, this model
43
+ is optional by design (see that rule's `defineModel` gap noted in the repo's ledger `eslint_issues`
44
+ docs). For several independent single-select categories, place multiple `SelectMenu` instances
45
+ side by side rather than using `multiple` on one instance — see the "Filter bar" section below.
46
+
47
+ **Type import**
48
+
49
+ ```ts
50
+ import type { SelectMenuOption } from "~/types/components/select-menu";
51
+ // consuming apps: import type { SelectMenuOption } from "srcdev-nuxt-components";
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Basic usage
57
+
58
+ ```vue
59
+ <script setup lang="ts">
60
+ import type { SelectMenuOption } from "~/types/components/select-menu";
61
+
62
+ const language = ref<string>("en");
63
+ const languageOptions: SelectMenuOption[] = [
64
+ { value: "en", label: "English", icon: "flag:gb-4x3" },
65
+ { value: "fr", label: "Français", icon: "flag:fr-4x3" },
66
+ ];
67
+ </script>
68
+
69
+ <template>
70
+ <SelectMenu v-model="language" :options="languageOptions" label="Language" />
71
+ </template>
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Trigger content variants
77
+
78
+ | Variant | Usage |
79
+ |---|---|
80
+ | Icon-only (compact switcher, e.g. a flag in a tight header) | `:show-label="false" :show-chevron="false"` |
81
+ | Icon + chevron, no text (flag with a visible dropdown affordance) | `:show-label="false"` |
82
+ | Text + chevron (category filter, no per-option icons) | `:show-icon="false"` |
83
+ | Icon + text + chevron (full select) | Defaults — no overrides needed |
84
+
85
+ When nothing is selected, the trigger shows `placeholder` (or `label` if no placeholder is given)
86
+ — this is what makes it work as a filter-category tag, e.g. a `SelectMenu` with
87
+ `label="Choose a service"` and no `placeholder` shows "Choose a service" until an option is
88
+ picked, then swaps to the selected option's label.
89
+
90
+ ---
91
+
92
+ ## Multi-select (checkboxes)
93
+
94
+ Set `multiple` to turn every option into a checkbox row. `v-model` becomes an array, and clicking
95
+ an option toggles it without closing the popover:
96
+
97
+ ```vue
98
+ <script setup lang="ts">
99
+ const treatments = ref<string[]>([]);
100
+ const treatmentOptions = [
101
+ { value: "trim", label: "Trim" },
102
+ { value: "layers", label: "Layers" },
103
+ { value: "restyle", label: "Restyle" },
104
+ { value: "straightening", label: "Straightening" },
105
+ ];
106
+ </script>
107
+
108
+ <template>
109
+ <SelectMenu v-model="treatments" :options="treatmentOptions" label="Services required" multiple />
110
+ </template>
111
+ ```
112
+
113
+ Unlike single-select, the trigger text stays fixed by default on `placeholder`/`label` as a static
114
+ category tag regardless of the current selection — it doesn't update to list checked options, so
115
+ `label="Services required"` remains visible in the trigger the whole time. Use the checkboxes
116
+ inside the open popover to see what's currently selected.
117
+
118
+ Set `showSelectionInTrigger` to opt out of that and have the trigger update instead:
119
+
120
+ ```vue
121
+ <SelectMenu
122
+ v-model="treatments"
123
+ :options="treatmentOptions"
124
+ label="Services required"
125
+ multiple
126
+ show-selection-in-trigger
127
+ />
128
+ <!-- Trigger reads e.g. "Layers, Restyle" once those two are checked, falling back to
129
+ "Services required" again if everything is unchecked. -->
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Filter bar (multiple categories)
135
+
136
+ `SelectMenu` is single-select per instance — a faceted filter bar is built by placing one instance
137
+ per category, each with its own `v-model`:
138
+
139
+ ```vue
140
+ <template>
141
+ <div style="display: flex; gap: 1.2rem;">
142
+ <SelectMenu label="Choose a service" v-model="service" :options="serviceOptions" />
143
+ <SelectMenu label="Choose a stylist" v-model="stylist" :options="stylistOptions" />
144
+ </div>
145
+ </template>
146
+ ```
147
+
148
+ ---
149
+
150
+ ## CSS token API
151
+
152
+ See `CONSUMER-STYLING.md` in the component folder for the full token reference. Default styling
153
+ targets the same visual language as the library's other select-like controls (border, background,
154
+ radius, focus ring drawn from `--theme-*` tokens).
155
+
156
+ Quick reference:
157
+
158
+ ```css
159
+ /* assets/styles/setup/07.components/select-menu.css */
160
+ :root {
161
+ --select-menu-trigger-border-radius: 999rem;
162
+ --select-menu-trigger-surface: var(--brand-surface);
163
+ --select-menu-popover-surface: var(--brand-surface);
164
+ --select-menu-item-surface-hover: var(--brand-surface-subtle);
165
+ --select-menu-item-check-color: var(--brand-accent);
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Notes
172
+
173
+ - **Popover API + CSS anchor positioning** — same mechanism as `ActionMenu`. Broadly supported
174
+ (Chrome 114+, Firefox 125+, Safari 17+). No polyfill is included.
175
+ - **Left-aligned popover** (`left: anchor(left)`) — unlike `ActionMenu` which right-aligns. Matches
176
+ native `<select>` dropdown behaviour. Flips above the trigger near the bottom of the viewport.
177
+ - **Keyboard navigation** follows the WAI-ARIA listbox pattern: `ArrowDown`/`ArrowUp` move between
178
+ options (wraps around), `Home`/`End` jump to first/last, `Enter`/`Space` select the focused
179
+ option, `Tab` closes without stealing focus back to the trigger (matches `ActionMenu`'s
180
+ `handleKeydown` convention — see that skill doc's note on the `currentIndex === -1` guard, which
181
+ applies identically here). `Enter`/`Space` close the popover in single-select mode but only
182
+ toggle the checkbox (leaving it open) when `multiple` is set.
183
+ - **Opens focused on the current selection** — `handleToggle` focuses the first selected
184
+ `[role="option"]` if one exists (in `multiple` mode, the first checked item), otherwise the first
185
+ option (unlike `ActionMenu`, which always focuses the first item since it has no selection state).
186
+ - **`aria-multiselectable="true"`** is set on the listbox only when `multiple` is `true`.
187
+ - **Chevron rotation is pure CSS** via `:has(.select-menu-popover:popover-open)` on the root — no
188
+ JS state drives the visual. `isOpen` is still tracked internally, but only to set `aria-expanded`
189
+ on the trigger.
190
+ - **No native `<select>`/`appearance: base-select` involved** — this is a from-scratch popover
191
+ listbox, not a styled native select, so it doesn't inherit `InputSelectCore`'s WebKit
192
+ `appearance: base-select` constraints.
193
+ - **Not a form field** — `SelectMenu` has no `fieldHasError`/`theme`/`inputVariant` props like the
194
+ `05.forms` inputs. Use `InputSelectCore` instead for an actual form field that needs validation
195
+ state and native `<select>` semantics.
@@ -0,0 +1,150 @@
1
+ # ToggleSwitchCore Component
2
+
3
+ ## Overview
4
+
5
+ `ToggleSwitchCore` is the low-level pill-shaped (or square, via `round`) toggle switch primitive
6
+ for the `05.forms` toggle family. It renders a visually-hidden native `<input type="checkbox">`
7
+ (kept in the DOM for real keyboard/checkbox semantics) plus a styled track/thumb visual, with
8
+ optional icon slots shown inside the thumb for the on/off states. It has no label, description, or
9
+ error-message rendering of its own — that's composed by the `ToggleSwitchWithLabel` and
10
+ `ToggleSwitchWithLabelInline` variants below.
11
+
12
+ Most consumers should reach for **ToggleSwitchWithLabel** or **ToggleSwitchWithLabelInline** rather
13
+ than `ToggleSwitchCore` directly.
14
+
15
+ ---
16
+
17
+ ## Props reference
18
+
19
+ > **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:field-has-error`, `:style-class-passthrough`, `:aria-describedby`, `:true-value`, `:false-value`.
20
+
21
+ | Prop (template form) | Type | Default | Notes |
22
+ |------|------|---------|-------|
23
+ | `:id` | `string` | (required) | Combined with a fixed prefix to build the native input's `id` (`toggle-switch-${id}`). |
24
+ | `:name` | `string` | (required) | Applied to the native checkbox. |
25
+ | `:required` | `boolean` | `false` | Native `required`. |
26
+ | `:field-has-error` | `boolean` | `false` | Drives `aria-invalid` on the checkbox, and switches `data-theme` to `"error"` regardless of the `theme` prop's own value. |
27
+ | `:true-value` / `:false-value` | `string \| number \| boolean` | `true` / `false` | The two values `v-model` toggles between. |
28
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes applied to the root element. |
29
+ | `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the root (superseded by `"error"` when `field-has-error` is true — see above). |
30
+ | `:round` | `boolean` | `true` | `true` renders a pill-shaped track/thumb (`border-radius: 100vw`); `false` renders a rounded-square shape (`border-radius: 0.4rem`). See the `--toggle-switch-border-radius` token in CONSUMER-STYLING.md to override the exact value either state produces. |
31
+ | `:aria-describedby` | `string` | `""` | Forwarded to the native checkbox. |
32
+
33
+ ### v-model
34
+
35
+ - `v-model` — `string | number | boolean`, **required** (`defineModel({ required: true })`, satisfying `vue/require-default-prop` — see `project_ledger_eslint_issues_column` in memory).
36
+
37
+ ### Slots
38
+
39
+ - `iconOn` / `iconOff` — replace the default `material-symbols:circle-outline` icon shown inside the thumb for each state. Providing either slot switches off the `use-default-icons` wrapper class (checked via `useSlots()`, so this only reacts to slot *presence*, not conditional slot content).
40
+
41
+ ---
42
+
43
+ ## CSS token API
44
+
45
+ See [CONSUMER-STYLING.md](../../app/components/05.forms/toggle-switch/CONSUMER-STYLING.md) for the
46
+ full table with defaults.
47
+
48
+ **Common tokens:**
49
+ - `--toggle-switch-surface` / `-surface-hover` — track background resting/hover, fall back to `--theme-checkbox-symbol-surface`/`--theme-surface-subtle`
50
+ - `--toggle-switch-border` / `-border-focus` — track border colour, and `:focus-visible` outline colour, fall back to `--theme-border`/`--theme-border-focus`
51
+ - `--toggle-switch-symbol-surface-off` / `-symbol-surface-on` — thumb background unchecked/checked
52
+ - `--toggle-switch-border-radius` — track and thumb shape; see the `round` prop above
53
+
54
+ ---
55
+
56
+ ## Fixed 2026-09-23: dead functionality and story bugs
57
+
58
+ `ToggleSwitchCore` (and its two variants) migrated to full compliance from a `variants:true`,
59
+ `eslint_issues:true`, score-3/5 state (no tests, no skill doc).
60
+
61
+ - **`round` prop was completely dead** — both variants declared and forwarded it to
62
+ `ToggleSwitchCore`, and Storybook's `SquareShape` story existed specifically to demonstrate
63
+ `round: false`, but `ToggleSwitchCore` never declared the prop at all, so it silently fell
64
+ through as an inert `round` DOM attribute on the root `<div>` and the track/thumb stayed pill-shaped
65
+ regardless. Fixed by adding `round` to `ToggleSwitchCore` and wiring it to a new
66
+ `--toggle-switch-border-radius` token (100vw when round, 0.4rem when square).
67
+ - **`fieldHasError` didn't switch the component's theme** — `ToggleSwitchCore` and
68
+ `ToggleSwitchWithLabel` both computed an error-aware theme value but bound `data-theme` to the
69
+ raw `theme` prop instead of that computed, so this library's `[data-theme="error"]` global
70
+ token overrides never applied when a consumer set `fieldHasError`. Fixed by binding `data-theme`
71
+ to the computed value in both files.
72
+ - **An invalid `for` attribute on a `<div>`** (`.toggle-switch-wrapper`) was removed — `for` is
73
+ only a valid HTML attribute on `<label>`/`<output>`, so it was inert dead markup; the real
74
+ label association is via `InputLabel`'s own `for`/`id` wiring in the variants.
75
+ - **`defineModel` had no default**, tripping `vue/require-default-prop` (a known false-positive
76
+ for `defineModel` — see `project_ledger_eslint_issues_column` in memory) — fixed with
77
+ `{ required: true }` across `ToggleSwitchCore` and both variants, since a checkbox genuinely
78
+ can't be meaningfully empty.
79
+ - **Two dead, unread `--_transition-duration` private tokens** (one per variant) were removed —
80
+ nothing in either variant's own CSS or `ToggleSwitchCore`'s ever read them; the actual
81
+ transition durations are hardcoded `0.4s` literals inside `ToggleSwitchCore`.
82
+ - **Custom `iconOn`/`iconOff` slot content could render as a non-square box, throwing off
83
+ centering** — `.symbol-icon` is `display: grid; place-content: center;`, and its single
84
+ auto-sized track shrink-wraps to the slotted content's own box. A plain inline element (e.g. a
85
+ `<span>` wrapping a 16×16 SVG, as in the `WithCustomIcons` stories) sizes to its line box's
86
+ leading rather than its intrinsic height, rendering as ~16×20 instead of 16×16. Fixed by adding
87
+ `line-height: 0` to `.symbol-icon`, which collapses that leading for whatever markup a consumer
88
+ slots in. The default icon (`@nuxt/icon`'s `Icon` component) wasn't affected, since iconify sets
89
+ its own sizing/`vertical-align` internally.
90
+ - **All three story files' `theme` control listed the wrong option set**
91
+ (`["primary", "secondary", "tertiary", "ghost", "error", "success", "warning"]`, none of which
92
+ match `FormUiTheme`) — fixed to the real `["default", "success", "error", "warning"]`.
93
+ `ToggleSwitchWithLabelInline.stories.ts`'s `InlineFormExample` story also passed the now-invalid
94
+ `theme="secondary"` directly — fixed to `"default"`.
95
+
96
+ ---
97
+
98
+ ## Variants
99
+
100
+ ### ToggleSwitchWithLabel
101
+
102
+ `ToggleSwitchWithLabel` (`variants/ToggleSwitchWithLabel.vue`) composes `ToggleSwitchCore` with
103
+ `InputLabel`, an optional `description` slot, and `InputError`.
104
+
105
+ **Additional props over ToggleSwitchCore:**
106
+
107
+ | Prop (template form) | Type | Default | Notes |
108
+ |------|------|---------|-------|
109
+ | `:label` | `string` | (required) | Rendered via `InputLabel`. |
110
+ | `:error-message` | `object \| string` | `""` | Rendered via `InputError` when `field-has-error` is true. |
111
+
112
+ Note this wrapper passes `id`/`name`/`required`/`field-has-error`/`true-value`/`false-value`/
113
+ `theme`/`round`/`aria-describedby` straight through to `ToggleSwitchCore`; `id` is a self-generated
114
+ `useId()` value, not a prop — consumers don't set it directly.
115
+
116
+ **Slots**: `description`, `iconOn`, `iconOff` (the latter two forwarded to `ToggleSwitchCore`).
117
+
118
+ ```vue
119
+ <ToggleSwitchWithLabel
120
+ name="notifications"
121
+ label="Enable notifications"
122
+ v-model="notificationsEnabled"
123
+ :error-message="errors.notifications"
124
+ :field-has-error="!!errors.notifications"
125
+ >
126
+ <template #description>Turn this on to receive notifications</template>
127
+ </ToggleSwitchWithLabel>
128
+ ```
129
+
130
+ ### ToggleSwitchWithLabelInline
131
+
132
+ `ToggleSwitchWithLabelInline` (`variants/ToggleSwitchWithLabelInline.vue`) composes
133
+ `ToggleSwitchCore` with `InputLabel` laid out inline (label and switch side by side, no
134
+ description/error-message support — this variant is for compact settings-row usage).
135
+
136
+ **Additional props over ToggleSwitchCore:**
137
+
138
+ | Prop (template form) | Type | Default | Notes |
139
+ |------|------|---------|-------|
140
+ | `:label` | `string` | (required) | Rendered via `InputLabel`. |
141
+ | `:label-weight` | `LabelWeight` (`"normal" \| "semi-bold" \| "bold"`) | `"normal"` | Maps to a `body-normal`/`body-normal-semibold`/`body-normal-bold` class on the label. |
142
+
143
+ This variant has no `required`/`field-has-error`/`error-message` props — it never claimed error-state
144
+ support, unlike `ToggleSwitchWithLabel`.
145
+
146
+ **Slots**: `iconOn`, `iconOff` (forwarded to `ToggleSwitchCore`).
147
+
148
+ ```vue
149
+ <ToggleSwitchWithLabelInline name="darkMode" label="Dark mode" v-model="darkModeEnabled" />
150
+ ```
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: UiBlockDecorated
3
+ description: UiBlockDecorated plain block wrapper with independent border/shadow/inner-shadow strength levels — props, CSS tokens
4
+ type: reference
5
+ ---
6
+
7
+ # UiBlockDecorated
8
+
9
+ ## Overview
10
+
11
+ `UiBlockDecorated` renders its slot content in a plain block element (configurable `tag`) with
12
+ three independent, optional decorations: a border, a drop shadow, and an inset ("inner") shadow.
13
+ Each decoration has its own 0-based "strength" prop that selects a level from a built-in scale;
14
+ `0` (the default) applies no class for that decoration, so all three are opt-in and combinable.
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |------|------|---------|-------------|
20
+ | `tag` | `"div" \| "p" \| "span" \| "section" \| "article" \| "aside" \| "header" \| "footer" \| "main" \| "nav" \| "ul" \| "ol"` | `"div"` | Root element tag. |
21
+ | `borderStrength` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | `0` | Border strength level; `0` = no border. |
22
+ | `shadowStrength` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | `0` | Drop shadow strength level; `0` = no shadow. |
23
+ | `innerShadowStrength` | `0 \| 1 \| 2 \| 3 \| 4` | `0` | Inset shadow strength level; `0` = no inner shadow. |
24
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
25
+
26
+ ## Basic usage
27
+
28
+ ```vue
29
+ <UiBlockDecorated :shadow-strength="3">
30
+ <p>Block content</p>
31
+ </UiBlockDecorated>
32
+
33
+ <UiBlockDecorated tag="section" :border-strength="2" :inner-shadow-strength="1">
34
+ <p>Block content</p>
35
+ </UiBlockDecorated>
36
+ ```
37
+
38
+ ## CSS custom properties
39
+
40
+ Each strength level has its own public token — see CONSUMER-STYLING.md for the full table. In
41
+ short: `--ui-block-decorated-border-1` .. `-6`, `--ui-block-decorated-shadow-1` .. `-6`,
42
+ `--ui-block-decorated-inner-shadow-1` .. `-4`, and `--ui-block-decorated-inner-shadow-highlight`
43
+ (layered on top of every inner shadow level).
44
+
45
+ ## Notes
46
+
47
+ - 2026-09-16 migration: previously used options-style `defineProps` and referenced six families of
48
+ global tokens (`--border-1`..`-6`, `--shadow-1`..`-6`, `--inner-shadow-1`..`-4`,
49
+ `--inner-shadow-highlight`) that were never declared anywhere in the codebase — every border,
50
+ shadow and inner shadow rendered as nothing. Fixed by giving each strength level its own
51
+ component-scoped public token (`--ui-block-decorated-*`) with a real fallback value baked in, so
52
+ the decorations work out of the box and are still fully overridable.
53
+ - The applied classes were also renamed from bare `.border-1`/`.shadow-1`/`.inner-shadow-1` to
54
+ `.ui-block-decorated-border-1` etc. — the un-prefixed names risked colliding with a consumer
55
+ app's own utility classes, since this component renders inline in the consumer's DOM (not
56
+ teleported/portaled).
@@ -0,0 +1,98 @@
1
+ ---
2
+ name: WipeAwayVertical
3
+ description: WipeAwayVertical scroll-driven vertical wipe-away effect — props, dynamic slot API, CSS grid overlay mechanics, CSS tokens
4
+ type: reference
5
+ ---
6
+
7
+ # WipeAwayVertical
8
+
9
+ ## Overview
10
+
11
+ `WipeAwayVertical` is a scroll-driven "wipe away" effect: a stack of sticky panels, each of
12
+ which wipes away (via a `clip-path` animation) to reveal the panel underneath as the user
13
+ scrolls past its paired tracking section. Pure CSS — built on `animation-timeline: view()`, a
14
+ single-column CSS grid overlay, and no JavaScript scroll listeners or measurement at all.
15
+
16
+ ## How it works
17
+
18
+ - The root is a single-column CSS grid. `.sticky-items-container` spans every row as an overlay
19
+ (`grid-row: 1 / -1`), holding `itemCount` `.sticky-item` panels stacked with descending
20
+ `z-index` (item 0 on top).
21
+ - Each real `.scrolling-section` gets its own explicit grid row and acts as a scroll tracker —
22
+ its entry into the viewport drives the wipe-out animation of its paired sticky item via a
23
+ named `view-timeline`. Only `itemCount - 1` sections are rendered: the **last** sticky item
24
+ has nothing left to reveal, so it has no wipe animation (`animation-timeline: none`) and no
25
+ scrolling section of its own.
26
+ - A `.leading-buffer` (fixed `100vh`) renders before the first scrolling section, so section 0
27
+ isn't already partway through its `entry` timeline range at page load, whatever content
28
+ precedes the component.
29
+ - A `.trailing-buffer`, sized by `--wipe-away-vertical-trailing-buffer` (default `20vh`), renders
30
+ after the last real scrolling section — just enough room for the sticky container to release
31
+ cleanly once the final panel is fully revealed.
32
+ - `.sticky-items-container` is sized by `--wipe-away-vertical-height` (default `100vh`) and
33
+ centers itself with `top: calc((100vh - var(--wipe-away-vertical-height, 100vh)) / 2)` — a
34
+ plain `calc()`, deliberately **not** a `transform: translateY()`. `position: sticky`'s
35
+ stick/release threshold is computed from the element's *untransformed* layout position, so a
36
+ transform-based visual shift releases the sticky panel early by the transformed amount,
37
+ leaving a gap after the last panel. `.scrolling-section`'s `view-timeline-inset` uses the same
38
+ `calc()` so the entry/exit range lines up with the same visual offset. (An earlier version
39
+ used `top: 50%` — also wrong, since a percentage `top` on `position: sticky` resolves against
40
+ the containing block's height, not the viewport, and this component's containing block is
41
+ deliberately many viewport-heights tall.)
42
+ - Where `@supports not (animation-timeline: view())`, the grid/sticky/animation machinery is
43
+ dropped entirely: the root becomes `display: block`, the leading/trailing buffers and
44
+ scrolling sections are hidden, and the sticky items render as plain stacked panels in normal
45
+ document flow. No JS fallback, no opacity crossfade — just a flat, static stack.
46
+
47
+ ## Props
48
+
49
+ | Prop | Type | Default | Description |
50
+ |------|------|---------|-------------|
51
+ | `tag` | `"div" \| "section" \| "main" \| "article" \| "aside"` | `"div"` | Root element tag. |
52
+ | `itemCount` | `number` | — (required) | Number of sticky panels. Drives the sticky-item slot loop, `itemCount - 1` scrolling sections, per-item `z-index`, and `view-timeline` naming. |
53
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
54
+
55
+ ## Slots
56
+
57
+ Dynamic named slots:
58
+
59
+ | Slot | Description |
60
+ |------|-------------|
61
+ | `stickyItem-{n}` | Content for sticky panel `n`, indexed `0` to `itemCount - 1`. |
62
+ | `scrollingItem-{n}` | Content (or an empty spacer) for scroll-tracking section `n`, indexed `0` to `itemCount - 2` only — the last panel has no scrolling section of its own. Typically an empty `100vh`-tall `div`; its height determines how much scroll distance that panel's wipe covers. |
63
+
64
+ ## Basic usage
65
+
66
+ ```vue
67
+ <WipeAwayVertical :item-count="3">
68
+ <template #stickyItem-0><PanelOne /></template>
69
+ <template #stickyItem-1><PanelTwo /></template>
70
+ <template #stickyItem-2><PanelThree /></template>
71
+
72
+ <template #scrollingItem-0><div style="height: 100vh;"></div></template>
73
+ <template #scrollingItem-1><div style="height: 100vh;"></div></template>
74
+ </WipeAwayVertical>
75
+ ```
76
+
77
+ Leave the root's own `height` unset — it's a grid that sizes itself from its children (leading
78
+ buffer + scrolling sections + trailing buffer). Setting an explicit height undersizes the root
79
+ relative to its real content and releases the sticky panel mid-wipe.
80
+
81
+ ## CSS custom properties
82
+
83
+ | Property | Default | Applies to |
84
+ |----------|---------|------------|
85
+ | `--wipe-away-vertical-height` | `100vh` | `.sticky-items-container` height (the visible sticky panel stack) |
86
+ | `--wipe-away-vertical-border-radius` | `0.5rem` | `.sticky-item` corner radius |
87
+ | `--wipe-away-vertical-animation-duration` | `1s` | `wipe-out` keyframe duration (view-timeline-supporting browsers) |
88
+ | `--wipe-away-vertical-trailing-buffer` | `20vh` | height of the release cushion after the last real scrolling section |
89
+
90
+ ## Notes
91
+
92
+ - Uses indexed dynamic slots (not the library's default named-dynamic-slot pattern) because
93
+ `itemCount` also drives non-slot logic: per-item `z-index`, grid rows, and the
94
+ `view-timeline-name`/`animation-timeline` CSS variable naming, not just the slot loop itself.
95
+ - Reduced motion: the wipe animation is disabled under `prefers-reduced-motion: reduce`
96
+ (`animation: none; clip-path: none;`) — all panels render fully visible with no clip.
97
+ - No hardcoded consumer-facing text — this is a purely structural/animation wrapper around
98
+ consumer-supplied slot content.
@@ -21,7 +21,7 @@ The layer's `modules/icon-sets.ts` runs at dev/build time and logs an info messa
21
21
  | `bi` | `@iconify-json/bi` | NavigationItems (overflow caret) |
22
22
  | `bitcoin-icons` | `@iconify-json/bitcoin-icons` | Display components |
23
23
  | `gravity-ui` | `@iconify-json/gravity-ui` | NavigationItems (burger/ellipsis overflow) |
24
- | `ic` | `@iconify-json/ic` | CarouselBasic, CarouselFlip, CarouselInfinite, SliderGallery, CanvasSwitcher |
24
+ | `ic` | `@iconify-json/ic` | CarouselFlip, SliderGallery, CanvasSwitcher |
25
25
  | `lucide` | `@iconify-json/lucide` | ColourFinder, TreatmentConsultant |
26
26
  | `material-symbols` | `@iconify-json/material-symbols` | form components |
27
27
  | `mdi` | `@iconify-json/mdi` | NavigationHorizontal, form components, ServicesCard |
@@ -63,6 +63,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
63
63
  ├── composable-analytics.md — useAnalytics: provider-agnostic trackEvent/page-view tracking (google-analytics only implemented), consent-gated, single call site for setup + firing events
64
64
  └── components/
65
65
  ├── alert-content-inner.md — AlertContentInner: shared icon/body/dismiss molecule under AlertContent + AlertMaskedContent; app.config icon map (alertContent.icons + dismissIcon) covers all consumers
66
+ ├── alert-mask-core.md — AlertMaskCore: SVG border/background mask sized to slotted content via ResizeObserver, config-prop-driven geometry/colour (no CSS token API)
66
67
  ├── animated-svg-text.md — AnimatedSvgText: inline SVG stroke-draw-then-fill animation, text slot, CSS token API
67
68
  ├── accordian-core.md — AccordianCore indexed dynamic slots (accordian-{n}-summary/icon/content), exclusive-open grouping
68
69
  ├── eyebrow-text.md — EyebrowText props, usage patterns, styling
@@ -92,6 +93,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
92
93
  ├── grid-stack.md — GridStack: CSS Grid z-axis stacking, slot API, z-order rules, sizing, video+overlay and image+text patterns
93
94
  ├── scroll-reveal-frame.md — ScrollRevealFrame: generic parallax clipping frame, slot API, image grid pattern, CSS tokens, browser support
94
95
  ├── scroll-reveal-image.md — ScrollRevealImage: single-image parallax reveal, focalX, imgWidth/imgHeight, responsive frame height
96
+ ├── wipe-away-vertical.md — WipeAwayVertical: scroll-driven vertical wipe-away effect, pure-CSS grid overlay (no JS), indexed dynamic slots, sticky-centering-via-calc gotcha, CSS tokens
95
97
  ├── marquee-scroller.md — MarqueeScroller: infinite logo/badge scroller, per-item dynamic slots (marqueeData id), hover/focus/keyboard pause, reduced-motion, CSS tokens
96
98
  ├── rotating-carousel-image.md — RotatingCarouselImage: 3D rotating image carousel, scroll-parallax tilt, focus/keyboard/hover pause, reduced-motion, CSS tokens
97
99
  ├── site-navigation.md — SiteNavigation: responsive nav with auto-collapse, burger menu, decorator indicators, CSS token API
@@ -102,8 +104,10 @@ Each skill is a single markdown file named `<area>-<task>.md`.
102
104
  ├── decode-qr-code.md — DecodeQrCode: file picker + drag-and-drop image decoder, shared results list, CSS override points
103
105
  ├── auto-grid.md — AutoGrid: auto-fit responsive grid, $slots iteration, --auto-grid-min-col-size/gap tokens, semantic tag + aria
104
106
  ├── display-avatar.md — DisplayAvatar: circular avatar with image/initials fallback, size variants, chip badge, icon slot, styleClassPassthrough
107
+ ├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping TripleToggleSwitchCore, wired to useSettingsStore, labels/icons props, small sizing variant
105
108
  ├── card-core.md — CardCore: generic card container, dynamic named slots as rows, 4 variants, blurred backdrop layer, full CSS token API
106
109
  ├── action-menu.md — ActionMenu + ActionMenuItemCore: ellipsis trigger + anchored popover menu, indexed item-{n} slots, link/button items, full CSS token API
110
+ ├── select-menu.md — SelectMenu: v-model single-select listbox popover (ActionMenu's popover mechanics, InputSelectCore's selected-option semantics), icon/text/chevron trigger toggles, checkmark on selected option, options-array driven, full CSS token API
107
111
  ├── display-dialog.md — DisplayDialog: native <dialog> overlay, 5 variants (dialog/modal/confirm/alert/fullscreen), useDialogControls integration, CSS token API
108
112
  ├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
109
113
  ├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, full CSS token API for border/outline/colour
@@ -123,9 +127,19 @@ Each skill is a single markdown file named `<area>-<task>.md`.
123
127
  ├── display-tooltip-defined.md — DisplayTooltipDefined: structured title/body/action tooltip content with close button, composes DisplayTooltip
124
128
  ├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
125
129
  ├── input-text-core.md — InputTextCore: native text/date/number input primitive, min/max pass-through (date-picker range), CSS token API; Variants section covers InputTextWithLabel/InputPasswordWithLabel/InputTextAsNumberWithLabel
130
+ ├── input-range-core.md — InputRangeCore: native range-slider primitive, markers/datalist slots, dead --theme-form-range-accent-color token fixed, CSS token API; Variants section covers InputRangeDefault
131
+ ├── input-number-core.md — InputNumberCore: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberDefault
132
+ ├── input-textarea-core.md — InputTextareaCore: native textarea primitive, left/right decorative slots, undefined-token/dead-code/label-leak bugs fixed, CSS token API; Variants section covers InputTextareaWithLabel
133
+ ├── input-select-core.md — InputSelectCore: native select primitive, data-driven options with icon decorator, dead required/styleClassPassthrough/isDirty-isActive functionality fixed, appearance:base-select browser-support note, CSS token API; Variants section covers InputSelectWithLabel
134
+ ├── toggle-switch-core.md — ToggleSwitchCore: pill/square checkbox-backed toggle primitive, dead round-prop/data-theme-error functionality and story theme-options bugs fixed, CSS token API; Variants section covers ToggleSwitchWithLabel/ToggleSwitchWithLabelInline
126
135
  ├── 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
127
136
  ├── 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?"
128
- └── 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
137
+ ├── 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
138
+ ├── glowing-border.md — GlowingBorder: animated conic-gradient glow border, 5 colour variants, tag prop, full CSS token API for width/radius/surface/duration/per-variant colour stops, reduced-motion guard
139
+ ├── section-parallax.md — SectionParallax: CSS fixed-background parallax section, iOS Safari limitation, when to use vs ScrollRevealImage, CSS token API, reduced-motion guard
140
+ ├── container-glow.md — ContainerGlow (renamed from ContainerGlowCore): pointer-proximity glow-border cards, named dynamic slots (one card per slot), config-prop-driven layout/interaction, full CSS token API for static visuals, reduced-motion guard
141
+ ├── dashboard-quad-grid.md — DashboardQuadGrid (renamed from LayoutGridA): fixed 4-slot dashboard grid, container-query breakpoints, CSS token API
142
+ └── dashboard-stats-grid.md — DashboardStatsGrid (renamed from LayoutGridB): top-row panel cluster + bottom-row panel strip dashboard grid, topRowSlot1ItemCount/bottomRowItemCount, CSS token API
129
143
  ```
130
144
 
131
145
  ## Skill file template
@@ -0,0 +1,38 @@
1
+ {
2
+ "version": 1.1,
3
+ "properties": [
4
+ {
5
+ "name": "rule",
6
+ "status": "experimental",
7
+ "description": "CSS Gap Decorations: shorthand for row-rule/column-rule width, style, and colour — draws lines in the gaps between grid/flex items. Limited browser support as of 2026.",
8
+ "references": [
9
+ {
10
+ "name": "MDN Reference",
11
+ "url": "https://developer.mozilla.org/en-US/docs/Web/CSS/rule"
12
+ }
13
+ ]
14
+ },
15
+ {
16
+ "name": "rule-break",
17
+ "status": "experimental",
18
+ "description": "CSS Gap Decorations: controls whether gap rule lines break at grid item intersections (e.g. \"intersection\", \"spanning-item\"). Limited browser support as of 2026.",
19
+ "references": [
20
+ {
21
+ "name": "MDN Reference",
22
+ "url": "https://developer.mozilla.org/en-US/docs/Web/CSS/rule-break"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "name": "rule-inset",
28
+ "status": "experimental",
29
+ "description": "CSS Gap Decorations: insets gap rule lines from the edges of the items they run between. Limited browser support as of 2026.",
30
+ "references": [
31
+ {
32
+ "name": "MDN Reference",
33
+ "url": "https://developer.mozilla.org/en-US/docs/Web/CSS/rule-inset"
34
+ }
35
+ ]
36
+ }
37
+ ]
38
+ }
@@ -36,6 +36,9 @@
36
36
  "editor.trimAutoWhitespace": "explicit"
37
37
  }
38
38
  },
39
+ // Recognises CSS properties too new for VS Code's built-in CSS language service (e.g. Gap Decorations' rule/rule-break/rule-inset)
40
+ "css.customData": [".vscode/css-custom-data.json"],
41
+
39
42
  // More info: https://open-props.style/#getting-started
40
43
  "cssvar.files": ["assets/styles/main.css"],
41
44
  "cssvar.ignore": [],
@@ -0,0 +1,31 @@
1
+ {
2
+ "SRCDEV AlertMaskCore Basic": {
3
+ "description": "AlertMaskCore — SVG border/background mask sized to slotted content",
4
+ "scope": "vue,html",
5
+ "body": [
6
+ "<AlertMaskCore>",
7
+ " $0",
8
+ "</AlertMaskCore>"
9
+ ]
10
+ },
11
+ "SRCDEV AlertMaskCore Custom Config": {
12
+ "description": "AlertMaskCore with custom colour/geometry config",
13
+ "scope": "vue,html",
14
+ "body": [
15
+ "<AlertMaskCore",
16
+ " :config=\"{",
17
+ " borderColour: '$1var(--theme-accent)',",
18
+ " backgroundColour: '$2rgba(0, 0, 0, 0.3)',",
19
+ " radiusLeft: $38,",
20
+ " radiusRight: $44,",
21
+ " borderLeft: $56,",
22
+ " borderTop: $61,",
23
+ " borderRight: $71,",
24
+ " borderBottom: $81,",
25
+ " }\"",
26
+ ">",
27
+ " $0",
28
+ "</AlertMaskCore>"
29
+ ]
30
+ }
31
+ }