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.
- package/.claude/commands/migrate-component.md +38 -6
- package/.claude/component-ledger/audit.json +1 -1
- package/.claude/component-ledger/build.mjs +26 -0
- package/.claude/component-ledger/output.html +8 -3
- package/.claude/component-ledger/template.html +7 -2
- package/.claude/skills/component-dynamic-slots.md +1 -1
- package/.claude/skills/components/alert-mask-core.md +84 -0
- package/.claude/skills/components/canvas-switcher.md +1 -1
- package/.claude/skills/components/carousel-flip.md +112 -168
- package/.claude/skills/components/container-glow.md +123 -0
- package/.claude/skills/components/dashboard-quad-grid.md +98 -0
- package/.claude/skills/components/dashboard-stats-grid.md +103 -0
- package/.claude/skills/components/display-theme-switch.md +60 -0
- package/.claude/skills/components/glowing-border.md +66 -0
- package/.claude/skills/components/input-number-core.md +214 -0
- package/.claude/skills/components/input-range-core.md +142 -0
- package/.claude/skills/components/input-select-core.md +151 -0
- package/.claude/skills/components/input-text-core.md +16 -0
- package/.claude/skills/components/input-textarea-core.md +154 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- package/.claude/skills/components/select-menu.md +195 -0
- package/.claude/skills/components/toggle-switch-core.md +150 -0
- package/.claude/skills/components/ui-block-decorated.md +56 -0
- package/.claude/skills/components/wipe-away-vertical.md +98 -0
- package/.claude/skills/icon-sets.md +1 -1
- package/.claude/skills/index.md +15 -1
- package/.vscode/css-custom-data.json +38 -0
- package/.vscode/settings.json +3 -0
- package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
- package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
- package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
- package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
- package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
- package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
- package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
- package/.vscode/srcdev-component-input-number.code-snippets +65 -0
- package/.vscode/srcdev-component-input-range.code-snippets +85 -0
- package/.vscode/srcdev-component-input-select.code-snippets +42 -0
- package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
- package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
- package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
- package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
- package/README.md +18 -37
- package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
- package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
- package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
- package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
- package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
- package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
- package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
- package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
- package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
- package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
- package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
- package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
- package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
- package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
- package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
- package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
- package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- package/app/components/01.atoms/card/CardCore.vue +4 -4
- package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
- package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
- package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
- package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
- package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
- package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
- package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
- package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
- package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
- package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
- package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
- package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
- package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
- package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
- package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
- package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
- package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
- package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
- package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
- package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
- package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
- package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
- package/app/components/05.forms/form-field/FormField.vue +1 -1
- package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
- package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
- package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
- package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
- package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
- package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
- package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
- package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
- package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
- package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
- package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
- package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
- package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
- package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
- package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
- package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
- package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
- package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
- package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
- package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
- package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
- package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
- package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
- package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
- package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
- package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
- package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
- package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
- package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
- package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
- package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
- package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
- package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
- package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
- package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
- package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
- package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +2 -1
- package/app/types/components/select-menu.d.ts +5 -0
- package/package.json +3 -2
- package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
- package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
- package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
- package/app/components/carousels/CarouselBasic.vue +0 -317
- package/app/components/carousels/CarouselInfinite.vue +0 -358
- package/app/components/container-glow/ContainerGlowCore.vue +0 -296
- package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
- package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
- package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
- package/app/components/glowing-border/GlowingBorder.vue +0 -142
- package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
- package/app/components/layout-grids/LayoutGridB.vue +0 -134
- package/app/components/test-storybook/TestStorybook.vue +0 -49
- package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
- package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
- package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
- 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` |
|
|
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 |
|
package/.claude/skills/index.md
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/.vscode/settings.json
CHANGED
|
@@ -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
|
+
}
|