srcdev-nuxt-components 9.4.9 → 9.4.10

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 (112) hide show
  1. package/.claude/commands/migrate-component.md +20 -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-range-core.md +142 -0
  16. package/.claude/skills/components/section-parallax.md +32 -10
  17. package/.claude/skills/components/ui-block-decorated.md +56 -0
  18. package/.claude/skills/components/wipe-away-vertical.md +98 -0
  19. package/.claude/skills/icon-sets.md +1 -1
  20. package/.claude/skills/index.md +10 -1
  21. package/.vscode/css-custom-data.json +38 -0
  22. package/.vscode/settings.json +3 -0
  23. package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
  24. package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
  25. package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
  26. package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
  27. package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
  28. package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
  29. package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
  30. package/.vscode/srcdev-component-input-range.code-snippets +85 -0
  31. package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
  32. package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
  33. package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
  34. package/README.md +18 -37
  35. package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
  36. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
  37. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
  38. package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
  39. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
  40. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
  41. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
  42. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
  43. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
  44. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
  45. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
  46. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
  47. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
  48. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
  49. package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
  50. package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
  51. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
  52. package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
  53. package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
  54. package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
  55. package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
  56. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
  57. package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
  58. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +1 -1
  59. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
  60. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
  61. package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
  62. package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
  63. package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
  64. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
  65. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
  66. package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
  67. package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
  68. package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
  69. package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
  70. package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
  71. package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
  72. package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
  73. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
  74. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
  75. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
  76. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
  77. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
  78. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
  79. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
  80. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +96 -11
  81. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
  82. package/app/components/05.forms/form-field/FormField.vue +1 -1
  83. package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
  84. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
  85. package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
  86. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
  87. package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
  88. package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
  89. package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
  90. package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
  91. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +135 -0
  92. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +16 -2
  93. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +6 -4
  94. package/app/types/components/container-glow.d.ts +8 -0
  95. package/app/types/components/index.ts +1 -1
  96. package/package.json +3 -2
  97. package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
  98. package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
  99. package/app/components/carousels/CarouselBasic.vue +0 -317
  100. package/app/components/carousels/CarouselInfinite.vue +0 -358
  101. package/app/components/container-glow/ContainerGlowCore.vue +0 -296
  102. package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
  103. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
  104. package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
  105. package/app/components/glowing-border/GlowingBorder.vue +0 -142
  106. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
  107. package/app/components/layout-grids/LayoutGridB.vue +0 -134
  108. package/app/components/test-storybook/TestStorybook.vue +0 -49
  109. package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
  110. package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
  111. package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
  112. package/app/types/components/carousel-basic.d.ts +0 -19
@@ -0,0 +1,39 @@
1
+ # UiBlockDecorated — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ Each strength level (border 1-6, shadow 1-6, inner shadow 1-4) has its own token, defaulting to a
6
+ built-in elevation/border scale. Only the tokens for the strength levels actually in use need
7
+ overriding.
8
+
9
+ | Token | Default | Controls |
10
+ |---|---|---|
11
+ | `--ui-block-decorated-border-1` .. `-6` | `1px` .. `6px solid var(--theme-border)` | Border at each strength level |
12
+ | `--ui-block-decorated-shadow-1` .. `-6` | `0 1px 2px rgba(0,0,0,0.08)` .. `0 24px 32px rgba(0,0,0,0.18)` | Drop shadow at each strength level |
13
+ | `--ui-block-decorated-inner-shadow-1` .. `-4` | `inset 0 1px 2px rgba(0,0,0,0.08)` .. `inset 0 6px 8px rgba(0,0,0,0.14)` | Inset shadow at each strength level |
14
+ | `--ui-block-decorated-inner-shadow-highlight` | `inset 0 1px 0 rgba(255,255,255,0.15)` | Highlight layered on top of every inner shadow strength |
15
+
16
+ ```css
17
+ .my-page {
18
+ --ui-block-decorated-shadow-3: 0 6px 12px rgba(0, 0, 0, 0.2);
19
+ --ui-block-decorated-border-2: 2px dashed hotpink;
20
+ }
21
+ ```
22
+
23
+ Or scope to a single instance via `styleClassPassthrough`:
24
+
25
+ ```vue
26
+ <UiBlockDecorated :shadow-strength="3" style-class-passthrough="promo-block">...</UiBlockDecorated>
27
+ ```
28
+
29
+ ```css
30
+ .promo-block {
31
+ --ui-block-decorated-shadow-3: 0 6px 12px rgba(0, 0, 0, 0.2);
32
+ }
33
+ ```
34
+
35
+ ## Strength props apply at most one class each
36
+
37
+ `borderStrength`, `shadowStrength` and `innerShadowStrength` are independent — a value of `0`
38
+ (the default) applies no class for that decoration. All three can be combined on the same
39
+ instance (e.g. a border and a shadow together).
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ class="ui-block-decorated"
5
+ :class="[
6
+ { [`ui-block-decorated-border-${borderStrength}`]: borderStrength > 0 },
7
+ { [`ui-block-decorated-shadow-${shadowStrength}`]: shadowStrength > 0 },
8
+ { [`ui-block-decorated-inner-shadow-${innerShadowStrength}`]: innerShadowStrength > 0 },
9
+ elementClasses,
10
+ ]"
11
+ >
12
+ <slot name="default"></slot>
13
+ </component>
14
+ </template>
15
+
16
+ <script setup lang="ts">
17
+ interface Props {
18
+ tag?: "div" | "p" | "span" | "section" | "article" | "aside" | "header" | "footer" | "main" | "nav" | "ul" | "ol";
19
+ borderStrength?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
20
+ shadowStrength?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
21
+ innerShadowStrength?: 0 | 1 | 2 | 3 | 4;
22
+ styleClassPassthrough?: string | string[];
23
+ }
24
+
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ tag: "div",
27
+ borderStrength: 0,
28
+ shadowStrength: 0,
29
+ innerShadowStrength: 0,
30
+ styleClassPassthrough: () => [],
31
+ });
32
+
33
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
34
+
35
+ watch(
36
+ () => props.styleClassPassthrough,
37
+ () => {
38
+ resetElementClasses(props.styleClassPassthrough);
39
+ }
40
+ );
41
+ </script>
42
+
43
+ <style lang="css">
44
+ @layer components {
45
+ .ui-block-decorated {
46
+ /*
47
+ * Border strength classes
48
+ */
49
+ &.ui-block-decorated-border-1 {
50
+ border: var(--ui-block-decorated-border-1, 1px solid var(--theme-border));
51
+ }
52
+ &.ui-block-decorated-border-2 {
53
+ border: var(--ui-block-decorated-border-2, 2px solid var(--theme-border));
54
+ }
55
+ &.ui-block-decorated-border-3 {
56
+ border: var(--ui-block-decorated-border-3, 3px solid var(--theme-border));
57
+ }
58
+ &.ui-block-decorated-border-4 {
59
+ border: var(--ui-block-decorated-border-4, 4px solid var(--theme-border));
60
+ }
61
+ &.ui-block-decorated-border-5 {
62
+ border: var(--ui-block-decorated-border-5, 5px solid var(--theme-border));
63
+ }
64
+ &.ui-block-decorated-border-6 {
65
+ border: var(--ui-block-decorated-border-6, 6px solid var(--theme-border));
66
+ }
67
+
68
+ /*
69
+ * Shadow strength classes
70
+ */
71
+ &.ui-block-decorated-shadow-1 {
72
+ box-shadow: var(--ui-block-decorated-shadow-1, 0 1px 2px rgba(0, 0, 0, 0.08));
73
+ }
74
+ &.ui-block-decorated-shadow-2 {
75
+ box-shadow: var(--ui-block-decorated-shadow-2, 0 2px 4px rgba(0, 0, 0, 0.1));
76
+ }
77
+ &.ui-block-decorated-shadow-3 {
78
+ box-shadow: var(--ui-block-decorated-shadow-3, 0 4px 8px rgba(0, 0, 0, 0.12));
79
+ }
80
+ &.ui-block-decorated-shadow-4 {
81
+ box-shadow: var(--ui-block-decorated-shadow-4, 0 8px 16px rgba(0, 0, 0, 0.14));
82
+ }
83
+ &.ui-block-decorated-shadow-5 {
84
+ box-shadow: var(--ui-block-decorated-shadow-5, 0 16px 24px rgba(0, 0, 0, 0.16));
85
+ }
86
+ &.ui-block-decorated-shadow-6 {
87
+ box-shadow: var(--ui-block-decorated-shadow-6, 0 24px 32px rgba(0, 0, 0, 0.18));
88
+ }
89
+
90
+ /*
91
+ * Inner shadow strength classes
92
+ * Could use substring match [class*="ui-block-decorated-inner-shadow"] but not as performant
93
+ */
94
+
95
+ &.ui-block-decorated-inner-shadow-1,
96
+ &.ui-block-decorated-inner-shadow-2,
97
+ &.ui-block-decorated-inner-shadow-3,
98
+ &.ui-block-decorated-inner-shadow-4 {
99
+ position: relative;
100
+ isolation: isolate;
101
+ }
102
+
103
+ &.ui-block-decorated-inner-shadow-1::before,
104
+ &.ui-block-decorated-inner-shadow-2::before,
105
+ &.ui-block-decorated-inner-shadow-3::before,
106
+ &.ui-block-decorated-inner-shadow-4::before {
107
+ content: "";
108
+ display: block;
109
+ position: absolute;
110
+ inset: 0;
111
+ pointer-events: none;
112
+ border-radius: inherit;
113
+ z-index: 2;
114
+ }
115
+
116
+ &.ui-block-decorated-inner-shadow-1::before {
117
+ box-shadow:
118
+ var(--ui-block-decorated-inner-shadow-1, inset 0 1px 2px rgba(0, 0, 0, 0.08)),
119
+ var(--ui-block-decorated-inner-shadow-highlight, inset 0 1px 0 rgba(255, 255, 255, 0.15));
120
+ }
121
+
122
+ &.ui-block-decorated-inner-shadow-2::before {
123
+ box-shadow:
124
+ var(--ui-block-decorated-inner-shadow-2, inset 0 2px 4px rgba(0, 0, 0, 0.1)),
125
+ var(--ui-block-decorated-inner-shadow-highlight, inset 0 1px 0 rgba(255, 255, 255, 0.15));
126
+ }
127
+
128
+ &.ui-block-decorated-inner-shadow-3::before {
129
+ box-shadow:
130
+ var(--ui-block-decorated-inner-shadow-3, inset 0 4px 6px rgba(0, 0, 0, 0.12)),
131
+ var(--ui-block-decorated-inner-shadow-highlight, inset 0 1px 0 rgba(255, 255, 255, 0.15));
132
+ }
133
+
134
+ &.ui-block-decorated-inner-shadow-4::before {
135
+ box-shadow:
136
+ var(--ui-block-decorated-inner-shadow-4, inset 0 6px 8px rgba(0, 0, 0, 0.14)),
137
+ var(--ui-block-decorated-inner-shadow-highlight, inset 0 1px 0 rgba(255, 255, 255, 0.15));
138
+ }
139
+ }
140
+ }
141
+ </style>
@@ -0,0 +1,148 @@
1
+ import UiBlockDecorated from "../UiBlockDecorated.vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+
4
+ const meta: Meta<typeof UiBlockDecorated> = {
5
+ title: "Atoms/UiBlockDecorated",
6
+ component: UiBlockDecorated,
7
+ argTypes: {
8
+ tag: {
9
+ control: { type: "select" },
10
+ options: ["div", "p", "span", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"],
11
+ description: "Root element tag",
12
+ table: { category: "Basic" },
13
+ },
14
+ borderStrength: {
15
+ control: { type: "select" },
16
+ options: [0, 1, 2, 3, 4, 5, 6],
17
+ description: "Border strength level (0 = none)",
18
+ table: { category: "Basic" },
19
+ },
20
+ shadowStrength: {
21
+ control: { type: "select" },
22
+ options: [0, 1, 2, 3, 4, 5, 6],
23
+ description: "Drop shadow strength level (0 = none)",
24
+ table: { category: "Basic" },
25
+ },
26
+ innerShadowStrength: {
27
+ control: { type: "select" },
28
+ options: [0, 1, 2, 3, 4],
29
+ description: "Inner shadow strength level (0 = none)",
30
+ table: { category: "Basic" },
31
+ },
32
+ styleClassPassthrough: {
33
+ table: { disable: true },
34
+ },
35
+ },
36
+ parameters: {
37
+ docs: {
38
+ description: {
39
+ component:
40
+ "A plain block wrapper that applies independent border / drop-shadow / inner-shadow strength levels via CSS custom properties (see CONSUMER-STYLING.md).",
41
+ },
42
+ },
43
+ },
44
+ };
45
+
46
+ export default meta;
47
+ type Story = StoryObj<typeof UiBlockDecorated>;
48
+
49
+ const blockStyle = "padding: 2rem; background: var(--theme-surface, #fff);";
50
+
51
+ export const Default: Story = {
52
+ args: {
53
+ tag: "div",
54
+ },
55
+ render: (args) => ({
56
+ components: { UiBlockDecorated },
57
+ setup() {
58
+ return { args, blockStyle };
59
+ },
60
+ template: `<UiBlockDecorated v-bind="args" :style="blockStyle">No decoration</UiBlockDecorated>`,
61
+ }),
62
+ };
63
+
64
+ export const Border: Story = {
65
+ args: {
66
+ tag: "div",
67
+ borderStrength: 3,
68
+ },
69
+ render: (args) => ({
70
+ components: { UiBlockDecorated },
71
+ setup() {
72
+ return { args, blockStyle };
73
+ },
74
+ template: `<UiBlockDecorated v-bind="args" :style="blockStyle">Border strength 3</UiBlockDecorated>`,
75
+ }),
76
+ };
77
+
78
+ export const Shadow: Story = {
79
+ args: {
80
+ tag: "div",
81
+ shadowStrength: 4,
82
+ },
83
+ render: (args) => ({
84
+ components: { UiBlockDecorated },
85
+ setup() {
86
+ return { args, blockStyle };
87
+ },
88
+ template: `<UiBlockDecorated v-bind="args" :style="blockStyle">Shadow strength 4</UiBlockDecorated>`,
89
+ }),
90
+ };
91
+
92
+ export const InnerShadow: Story = {
93
+ args: {
94
+ tag: "div",
95
+ innerShadowStrength: 3,
96
+ },
97
+ render: (args) => ({
98
+ components: { UiBlockDecorated },
99
+ setup() {
100
+ return { args, blockStyle };
101
+ },
102
+ template: `<UiBlockDecorated v-bind="args" :style="blockStyle">Inner shadow strength 3</UiBlockDecorated>`,
103
+ }),
104
+ };
105
+
106
+ export const AllCombined: Story = {
107
+ args: {
108
+ tag: "div",
109
+ borderStrength: 2,
110
+ shadowStrength: 3,
111
+ innerShadowStrength: 2,
112
+ },
113
+ render: (args) => ({
114
+ components: { UiBlockDecorated },
115
+ setup() {
116
+ return { args, blockStyle };
117
+ },
118
+ template: `<UiBlockDecorated v-bind="args" :style="blockStyle">All three combined</UiBlockDecorated>`,
119
+ }),
120
+ };
121
+
122
+ export const CustomTokens: Story = {
123
+ args: {
124
+ tag: "div",
125
+ shadowStrength: 3,
126
+ },
127
+ render: (args) => ({
128
+ components: { UiBlockDecorated },
129
+ setup() {
130
+ return { args, blockStyle };
131
+ },
132
+ template: `
133
+ <UiBlockDecorated
134
+ v-bind="args"
135
+ :style="blockStyle + ' --ui-block-decorated-shadow-3: 0 6px 12px rgba(0, 0, 0, 0.25);'"
136
+ >
137
+ Custom shadow-3 token
138
+ </UiBlockDecorated>
139
+ `,
140
+ }),
141
+ parameters: {
142
+ docs: {
143
+ description: {
144
+ story: "Overriding a strength level's token via an inline `--ui-block-decorated-*` custom property.",
145
+ },
146
+ },
147
+ },
148
+ };
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import UiBlockDecorated from "../UiBlockDecorated.vue";
4
+
5
+ describe("UiBlockDecorated", () => {
6
+ it("mounts without error", async () => {
7
+ const wrapper = await mountSuspended(UiBlockDecorated);
8
+ expect(wrapper.vm).toBeTruthy();
9
+ });
10
+
11
+ it("renders correct HTML structure (default props)", async () => {
12
+ const wrapper = await mountSuspended(UiBlockDecorated);
13
+ expect(wrapper.html()).toMatchSnapshot();
14
+ });
15
+
16
+ it("renders a <div> by default", async () => {
17
+ const wrapper = await mountSuspended(UiBlockDecorated);
18
+ expect(wrapper.element.tagName).toBe("DIV");
19
+ });
20
+
21
+ it("renders the given tag", async () => {
22
+ const wrapper = await mountSuspended(UiBlockDecorated, { props: { tag: "section" } });
23
+ expect(wrapper.element.tagName).toBe("SECTION");
24
+ });
25
+
26
+ it("applies no strength classes by default", async () => {
27
+ const wrapper = await mountSuspended(UiBlockDecorated);
28
+ expect(wrapper.classes()).toEqual(["ui-block-decorated"]);
29
+ });
30
+
31
+ it("applies the border strength class", async () => {
32
+ const wrapper = await mountSuspended(UiBlockDecorated, { props: { borderStrength: 3 } });
33
+ expect(wrapper.classes()).toContain("ui-block-decorated-border-3");
34
+ });
35
+
36
+ it("applies the shadow strength class", async () => {
37
+ const wrapper = await mountSuspended(UiBlockDecorated, { props: { shadowStrength: 5 } });
38
+ expect(wrapper.classes()).toContain("ui-block-decorated-shadow-5");
39
+ });
40
+
41
+ it("applies the inner shadow strength class", async () => {
42
+ const wrapper = await mountSuspended(UiBlockDecorated, { props: { innerShadowStrength: 2 } });
43
+ expect(wrapper.classes()).toContain("ui-block-decorated-inner-shadow-2");
44
+ });
45
+
46
+ it("combines all three strength classes together", async () => {
47
+ const wrapper = await mountSuspended(UiBlockDecorated, {
48
+ props: { borderStrength: 1, shadowStrength: 2, innerShadowStrength: 3 },
49
+ });
50
+ expect(wrapper.classes()).toContain("ui-block-decorated-border-1");
51
+ expect(wrapper.classes()).toContain("ui-block-decorated-shadow-2");
52
+ expect(wrapper.classes()).toContain("ui-block-decorated-inner-shadow-3");
53
+ });
54
+
55
+ it("renders slot content", async () => {
56
+ const wrapper = await mountSuspended(UiBlockDecorated, {
57
+ slots: { default: "<p class='slot-child'>Hello</p>" },
58
+ });
59
+ expect(wrapper.find(".slot-child").exists()).toBe(true);
60
+ expect(wrapper.find(".slot-child").text()).toBe("Hello");
61
+ });
62
+
63
+ it("applies styleClassPassthrough classes", async () => {
64
+ const wrapper = await mountSuspended(UiBlockDecorated, {
65
+ props: { styleClassPassthrough: ["custom-class"] },
66
+ });
67
+ expect(wrapper.classes()).toContain("custom-class");
68
+ });
69
+
70
+ it("resets classes when styleClassPassthrough prop changes", async () => {
71
+ const wrapper = await mountSuspended(UiBlockDecorated, {
72
+ props: { styleClassPassthrough: ["initial-class"] },
73
+ });
74
+ expect(wrapper.classes()).toContain("initial-class");
75
+
76
+ await wrapper.setProps({ styleClassPassthrough: ["updated-class"] });
77
+ expect(wrapper.classes()).not.toContain("initial-class");
78
+ expect(wrapper.classes()).toContain("updated-class");
79
+ });
80
+ });
@@ -0,0 +1,3 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`UiBlockDecorated > renders correct HTML structure (default props) 1`] = `"<div class="ui-block-decorated"></div>"`;
@@ -135,11 +135,11 @@ describe("AlertMaskedContent", () => {
135
135
  const wrapper = await mountSuspended(AlertMaskedContent, { props: { theme: "info" } });
136
136
  const contentEl = wrapper.find(".alert-mask-content");
137
137
  const style = (contentEl.element as HTMLElement).style;
138
- // Default borderLeft: 6 → --insetInlineStart: 6px
139
- expect(style.getPropertyValue("--insetInlineStart")).toBe("6px");
140
- expect(style.getPropertyValue("--insetBlockStart")).toBe("1px");
141
- expect(style.getPropertyValue("--insetInlineEnd")).toBe("1px");
142
- expect(style.getPropertyValue("--insetBlockEnd")).toBe("1px");
138
+ // Default borderLeft: 6 → --_inset-inline-start: 6px
139
+ expect(style.getPropertyValue("--_inset-inline-start")).toBe("6px");
140
+ expect(style.getPropertyValue("--_inset-block-start")).toBe("1px");
141
+ expect(style.getPropertyValue("--_inset-inline-end")).toBe("1px");
142
+ expect(style.getPropertyValue("--_inset-block-end")).toBe("1px");
143
143
  });
144
144
 
145
145
  it("merges custom maskConfig over defaults", async () => {
@@ -148,6 +148,6 @@ describe("AlertMaskedContent", () => {
148
148
  });
149
149
  const contentEl = wrapper.find(".alert-mask-content");
150
150
  const style = (contentEl.element as HTMLElement).style;
151
- expect(style.getPropertyValue("--insetInlineStart")).toBe("12px");
151
+ expect(style.getPropertyValue("--_inset-inline-start")).toBe("12px");
152
152
  });
153
153
  });
@@ -0,0 +1,34 @@
1
+ # DisplayThemeSwitch — Consumer Styling Guide
2
+
3
+ ## No tokens of its own — delegates entirely to TripleToggleSwitchCore
4
+
5
+ `DisplayThemeSwitch` is a thin wrapper around `TripleToggleSwitchCore` (see
6
+ `app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md`) and declares no
7
+ `--display-theme-switch-*` tokens of its own. Every colour token documented there
8
+ (`--triple-toggle-switch-surface`, `--triple-toggle-switch-border`, `--triple-toggle-switch-marker-surface`,
9
+ etc.) applies unchanged, since `DisplayThemeSwitch` renders that component directly with no
10
+ intermediate styling layer.
11
+
12
+ ## `small` class — compact sizing
13
+
14
+ Pass `"small"` via `styleClassPassthrough` for a more compact gap/padding/icon-size, useful in a
15
+ tight header/nav slot:
16
+
17
+ ```vue
18
+ <DisplayThemeSwitch style-class-passthrough="small" />
19
+ ```
20
+
21
+ This sets `TripleToggleSwitchCore`'s public `--triple-toggle-switch-gap`/`-padding`/
22
+ `-option-padding`/`-icon-size` tokens to smaller values (`0.2rem`/`0.2rem`/`0.2rem`/`1.6rem` vs the
23
+ default `0.4rem`/`0.4rem`/`0.4rem`/`2rem` — both already more compact than `TripleToggleSwitchCore`'s
24
+ own defaults of `1rem`/`0.6rem`/`0.5rem`/`2rem`). Override any of the four directly for a custom
25
+ size instead of using `small`:
26
+
27
+ ```vue
28
+ <DisplayThemeSwitch style="--triple-toggle-switch-icon-size: 2.4rem;" />
29
+ ```
30
+
31
+ ## Class passthrough
32
+
33
+ `styleClassPassthrough` also accepts any other class for layout purposes, alongside or instead of
34
+ `small`.
@@ -0,0 +1,102 @@
1
+ <template>
2
+ <ClientOnly>
3
+ <TripleToggleSwitchCore
4
+ v-model="colorModeVal"
5
+ v-model:field-data="sampleFieldData"
6
+ :style-class-passthrough="[`colour-scheme-select`, elementClasses]"
7
+ />
8
+ </ClientOnly>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import type { IFormMultipleOptions } from "~/types/forms/types.forms";
13
+ interface Props {
14
+ styleClassPassthrough?: string | string[];
15
+ systemLabel?: string;
16
+ lightLabel?: string;
17
+ darkLabel?: string;
18
+ systemIcon?: string;
19
+ lightIcon?: string;
20
+ darkIcon?: string;
21
+ }
22
+
23
+ const props = withDefaults(defineProps<Props>(), {
24
+ styleClassPassthrough: () => [],
25
+ systemLabel: "System",
26
+ lightLabel: "Light",
27
+ darkLabel: "Dark",
28
+ systemIcon: "material-symbols:night-sight-auto-sharp",
29
+ lightIcon: "radix-icons:sun",
30
+ darkIcon: "radix-icons:moon",
31
+ });
32
+
33
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
34
+
35
+ const sampleFieldData = computed<IFormMultipleOptions>(() => ({
36
+ data: [
37
+ {
38
+ id: "system",
39
+ name: "colorModeVal",
40
+ value: "system",
41
+ label: props.systemLabel,
42
+ icon: props.systemIcon,
43
+ },
44
+ {
45
+ id: "light",
46
+ name: "colorModeVal",
47
+ value: "light",
48
+ label: props.lightLabel,
49
+ icon: props.lightIcon,
50
+ },
51
+ {
52
+ id: "dark",
53
+ name: "colorModeVal",
54
+ value: "dark",
55
+ label: props.darkLabel,
56
+ icon: props.darkIcon,
57
+ },
58
+ ],
59
+ total: 3,
60
+ skip: 0,
61
+ limit: 3,
62
+ }));
63
+
64
+ const { colourScheme, setColourScheme } = useSettingsStore();
65
+
66
+ const colorModeVal = ref(colourScheme);
67
+ watch(colorModeVal, (val) => {
68
+ setColourScheme(val);
69
+ });
70
+ </script>
71
+
72
+ <style lang="css">
73
+ @layer components {
74
+ .colour-scheme-select {
75
+ /* Everything DisplayThemeSwitch used to redeclare here beyond sizing (wrapper background/border/
76
+ focus ring, marker border, option border/outline/focus/hover, option-icon colours, and the
77
+ system/light/dark marker gradients) was a byte-for-byte duplicate of TripleToggleSwitchCore's
78
+ own defaults for the exact same selectors — a no-op at best, and for the ones that bypassed
79
+ TripleToggleSwitchCore's public --triple-toggle-switch-* tokens with a hardcoded literal
80
+ (background-color, border, focus outline, marker border, option border/hover/focus, icon
81
+ colour) an active bug: a consumer setting e.g. --triple-toggle-switch-surface globally would
82
+ have been silently overridden back to the hardcoded default by this block's higher
83
+ specificity. Removed 2026-09-20 — TripleToggleSwitchCore already owns all of that. Only the
84
+ genuine "small" sizing variant remains, now driving TripleToggleSwitchCore's own public
85
+ sizing tokens instead of reaching into its private --_form-* and --_scheme-icon-font-size
86
+ locals directly (see TripleToggleSwitchCore's CONSUMER-STYLING.md). */
87
+ &.triple-toggle-switch {
88
+ --triple-toggle-switch-gap: 0.4rem;
89
+ --triple-toggle-switch-padding: 0.4rem;
90
+ --triple-toggle-switch-option-padding: 0.4rem;
91
+ --triple-toggle-switch-icon-size: 2rem;
92
+
93
+ &.small {
94
+ --triple-toggle-switch-gap: 0.2rem;
95
+ --triple-toggle-switch-padding: 0.2rem;
96
+ --triple-toggle-switch-option-padding: 0.2rem;
97
+ --triple-toggle-switch-icon-size: 1.6rem;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ </style>
@@ -0,0 +1,115 @@
1
+ import DisplayThemeSwitch from "../DisplayThemeSwitch.vue";
2
+ import type { Meta, StoryObj } from "@nuxtjs/storybook";
3
+
4
+ const meta: Meta<typeof DisplayThemeSwitch> = {
5
+ title: "Molecules/DisplayThemeSwitch",
6
+ component: DisplayThemeSwitch,
7
+ argTypes: {
8
+ styleClassPassthrough: {
9
+ control: "object",
10
+ description: "Extra classes — pass \"small\" for a compact size",
11
+ table: { category: "Styling" },
12
+ },
13
+ systemLabel: {
14
+ control: "text",
15
+ description: "Accessible label for the system option",
16
+ table: { category: "Labels" },
17
+ },
18
+ lightLabel: {
19
+ control: "text",
20
+ description: "Accessible label for the light option",
21
+ table: { category: "Labels" },
22
+ },
23
+ darkLabel: {
24
+ control: "text",
25
+ description: "Accessible label for the dark option",
26
+ table: { category: "Labels" },
27
+ },
28
+ systemIcon: {
29
+ control: "text",
30
+ description: "Icon name for the system option",
31
+ table: { category: "Icons" },
32
+ },
33
+ lightIcon: {
34
+ control: "text",
35
+ description: "Icon name for the light option",
36
+ table: { category: "Icons" },
37
+ },
38
+ darkIcon: {
39
+ control: "text",
40
+ description: "Icon name for the dark option",
41
+ table: { category: "Icons" },
42
+ },
43
+ },
44
+ parameters: {
45
+ docs: {
46
+ description: {
47
+ component:
48
+ "A system/light/dark theme switch wrapping TripleToggleSwitchCore, wired to useSettingsStore. Colour and sizing are entirely delegated to TripleToggleSwitchCore's public --triple-toggle-switch-* tokens — see CONSUMER-STYLING.md.",
49
+ },
50
+ },
51
+ },
52
+ };
53
+
54
+ export default meta;
55
+ type Story = StoryObj<typeof DisplayThemeSwitch>;
56
+
57
+ export const Default: Story = {
58
+ args: {},
59
+ render: (args) => ({
60
+ components: { DisplayThemeSwitch },
61
+ setup() {
62
+ return { args };
63
+ },
64
+ template: `<DisplayThemeSwitch v-bind="args" />`,
65
+ }),
66
+ };
67
+
68
+ export const Small: Story = {
69
+ args: {
70
+ styleClassPassthrough: "small",
71
+ },
72
+ render: (args) => ({
73
+ components: { DisplayThemeSwitch },
74
+ setup() {
75
+ return { args };
76
+ },
77
+ template: `<DisplayThemeSwitch v-bind="args" />`,
78
+ }),
79
+ parameters: {
80
+ docs: {
81
+ description: {
82
+ story: "Compact sizing for use in a tight header/nav slot.",
83
+ },
84
+ },
85
+ },
86
+ };
87
+
88
+ export const InNavigation: Story = {
89
+ args: {
90
+ styleClassPassthrough: "small",
91
+ },
92
+ render: (args) => ({
93
+ components: { DisplayThemeSwitch },
94
+ setup() {
95
+ return { args };
96
+ },
97
+ template: `
98
+ <nav style="display: flex; align-items: center; justify-content: space-between; padding: 1.6rem 2.4rem; background: var(--theme-surface); border-radius: 0.8rem; border: 1px solid var(--theme-border);">
99
+ <div style="display: flex; align-items: center; gap: 1.6rem;">
100
+ <span style="font-weight: 600; color: var(--theme-text);">My App</span>
101
+ <a href="#" style="color: var(--theme-text); text-decoration: none;">Home</a>
102
+ <a href="#" style="color: var(--theme-text); text-decoration: none;">About</a>
103
+ </div>
104
+ <DisplayThemeSwitch v-bind="args" />
105
+ </nav>
106
+ `,
107
+ }),
108
+ parameters: {
109
+ docs: {
110
+ description: {
111
+ story: "Example of the theme switch integrated into a navigation bar context.",
112
+ },
113
+ },
114
+ },
115
+ };