srcdev-nuxt-components 9.4.9 → 9.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/.claude/commands/migrate-component.md +38 -6
  2. package/.claude/component-ledger/audit.json +1 -1
  3. package/.claude/component-ledger/build.mjs +26 -0
  4. package/.claude/component-ledger/output.html +8 -3
  5. package/.claude/component-ledger/template.html +7 -2
  6. package/.claude/skills/component-dynamic-slots.md +1 -1
  7. package/.claude/skills/components/alert-mask-core.md +84 -0
  8. package/.claude/skills/components/canvas-switcher.md +1 -1
  9. package/.claude/skills/components/carousel-flip.md +112 -168
  10. package/.claude/skills/components/container-glow.md +123 -0
  11. package/.claude/skills/components/dashboard-quad-grid.md +98 -0
  12. package/.claude/skills/components/dashboard-stats-grid.md +103 -0
  13. package/.claude/skills/components/display-theme-switch.md +60 -0
  14. package/.claude/skills/components/glowing-border.md +66 -0
  15. package/.claude/skills/components/input-number-core.md +214 -0
  16. package/.claude/skills/components/input-range-core.md +142 -0
  17. package/.claude/skills/components/input-select-core.md +151 -0
  18. package/.claude/skills/components/input-text-core.md +16 -0
  19. package/.claude/skills/components/input-textarea-core.md +154 -0
  20. package/.claude/skills/components/section-parallax.md +32 -10
  21. package/.claude/skills/components/select-menu.md +195 -0
  22. package/.claude/skills/components/toggle-switch-core.md +150 -0
  23. package/.claude/skills/components/ui-block-decorated.md +56 -0
  24. package/.claude/skills/components/wipe-away-vertical.md +98 -0
  25. package/.claude/skills/icon-sets.md +1 -1
  26. package/.claude/skills/index.md +15 -1
  27. package/.vscode/css-custom-data.json +38 -0
  28. package/.vscode/settings.json +3 -0
  29. package/.vscode/srcdev-component-alert-mask-core.code-snippets +31 -0
  30. package/.vscode/srcdev-component-carousel-flip.code-snippets +41 -0
  31. package/.vscode/srcdev-component-container-glow.code-snippets +33 -0
  32. package/.vscode/srcdev-component-dashboard-quad-grid.code-snippets +36 -0
  33. package/.vscode/srcdev-component-dashboard-stats-grid.code-snippets +35 -0
  34. package/.vscode/srcdev-component-display-theme-switch.code-snippets +27 -0
  35. package/.vscode/srcdev-component-glowing-border.code-snippets +23 -0
  36. package/.vscode/srcdev-component-input-number.code-snippets +65 -0
  37. package/.vscode/srcdev-component-input-range.code-snippets +85 -0
  38. package/.vscode/srcdev-component-input-select.code-snippets +42 -0
  39. package/.vscode/srcdev-component-input-textarea.code-snippets +40 -0
  40. package/.vscode/srcdev-component-section-parallax.code-snippets +27 -0
  41. package/.vscode/srcdev-component-select-menu.code-snippets +65 -0
  42. package/.vscode/srcdev-component-toggle-switch.code-snippets +49 -0
  43. package/.vscode/srcdev-component-ui-block-decorated.code-snippets +24 -0
  44. package/.vscode/srcdev-component-wipe-away-vertical.code-snippets +25 -0
  45. package/README.md +18 -37
  46. package/app/components/{alert-mask → 01.atoms/alert-mask}/AlertMaskCore.vue +16 -17
  47. package/app/components/01.atoms/alert-mask/CONSUMER-STYLING.md +46 -0
  48. package/app/components/01.atoms/alert-mask/stories/AlertMaskCore.stories.ts +79 -0
  49. package/app/components/{alert-mask → 01.atoms/alert-mask}/tests/AlertMaskCore.spec.ts +10 -10
  50. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +86 -0
  51. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +299 -0
  52. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +175 -0
  53. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +131 -0
  54. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +14 -0
  55. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +61 -0
  56. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +132 -0
  57. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +165 -0
  58. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +94 -0
  59. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +3 -0
  60. package/app/components/01.atoms/animations/marquee-scroller/CONSUMER-STYLING.md +1 -1
  61. package/app/components/01.atoms/animations/marquee-scroller/MarqueeScroller.vue +1 -1
  62. package/app/components/01.atoms/animations/marquee-scroller/stories/MarqueeScroller.stories.ts +1 -1
  63. package/app/components/01.atoms/animations/section-parallax/CONSUMER-STYLING.md +44 -0
  64. package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue +11 -10
  65. package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts +98 -0
  66. package/app/components/01.atoms/animations/section-parallax/tests/SectionParallax.spec.ts +102 -0
  67. package/app/components/01.atoms/animations/section-parallax/tests/__snapshots__/SectionParallax.spec.ts.snap +9 -0
  68. package/app/components/01.atoms/animations/view-timeline/CONSUMER-STYLING.md +70 -0
  69. package/app/components/01.atoms/animations/view-timeline/WipeAwayVertical.vue +181 -0
  70. package/app/components/01.atoms/animations/view-timeline/stories/WipeAwayVertical.stories.ts +269 -0
  71. package/app/components/01.atoms/animations/view-timeline/tests/WipeAwayVertical.spec.ts +127 -0
  72. package/app/components/01.atoms/canvas-switcher/CONSUMER-STYLING.md +3 -3
  73. package/app/components/01.atoms/canvas-switcher/CanvasSwitcher.vue +2 -2
  74. package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
  75. package/app/components/01.atoms/card/CardCore.vue +4 -4
  76. package/app/components/01.atoms/clipped-panel/CONSUMER-STYLING.md +2 -2
  77. package/app/components/01.atoms/clipped-panel/ClippedPanel.vue +2 -2
  78. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/CONSUMER-STYLING.md +42 -0
  79. package/app/components/{layout-grids/LayoutGridA.vue → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/DashboardQuadGrid.vue} +23 -13
  80. package/app/components/{layout-grids/stories/LayoutGridA.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/stories/DashboardQuadGrid.stories.ts} +151 -23
  81. package/app/components/{layout-grids/tests/LayoutGridA.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-quad-grid/tests/DashboardQuadGrid.spec.ts} +25 -25
  82. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/CONSUMER-STYLING.md +41 -0
  83. package/app/components/01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/DashboardStatsGrid.vue +133 -0
  84. package/app/components/{layout-grids/stories/LayoutGridB.stories.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/stories/DashboardStatsGrid.stories.ts} +24 -24
  85. package/app/components/{layout-grids/tests/LayoutGridB.spec.ts → 01.atoms/content-wrappers/layout-grid/dashboard-stats-grid/tests/DashboardStatsGrid.spec.ts} +23 -23
  86. package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +8 -8
  87. package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +8 -8
  88. package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +6 -6
  89. package/app/components/01.atoms/pop-over/PopOver.vue +6 -6
  90. package/app/components/01.atoms/ui-block-decorated/CONSUMER-STYLING.md +39 -0
  91. package/app/components/01.atoms/ui-block-decorated/UiBlockDecorated.vue +141 -0
  92. package/app/components/01.atoms/ui-block-decorated/stories/UiBlockDecorated.stories.ts +148 -0
  93. package/app/components/01.atoms/ui-block-decorated/tests/UiBlockDecorated.spec.ts +80 -0
  94. package/app/components/01.atoms/ui-block-decorated/tests/__snapshots__/UiBlockDecorated.spec.ts.snap +3 -0
  95. package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +6 -6
  96. package/app/components/02.molecules/display-theme-switch/CONSUMER-STYLING.md +34 -0
  97. package/app/components/02.molecules/display-theme-switch/DisplayThemeSwitch.vue +102 -0
  98. package/app/components/02.molecules/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +115 -0
  99. package/app/components/02.molecules/display-theme-switch/tests/DisplayThemeSwitch.spec.ts +86 -0
  100. package/app/components/02.molecules/navigation/deep-expanding-menu/CONSUMER-STYLING.md +1 -1
  101. package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -1
  102. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/CONSUMER-STYLING.md +1 -1
  103. package/app/components/02.molecules/navigation/deep-expanding-menu-classic/DeepExpandingMenuClassic.vue +1 -1
  104. package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +168 -0
  105. package/app/components/02.molecules/select-menu/SelectMenu.vue +384 -0
  106. package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +359 -0
  107. package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +476 -0
  108. package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +42 -0
  109. package/app/components/03.organisms/image-galleries/carousel-flip/CONSUMER-STYLING.md +46 -0
  110. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/CarouselFlip.vue +63 -42
  111. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/stories/CarouselFlip.stories.ts +114 -29
  112. package/app/components/{carousels → 03.organisms/image-galleries/carousel-flip}/tests/CarouselFlip.spec.ts +80 -0
  113. package/app/components/05.forms/form-field/FormField.vue +1 -1
  114. package/app/components/05.forms/form-wrapper/FormWrapper.vue +1 -1
  115. package/app/components/05.forms/input-button/CONSUMER-STYLING.md +2 -7
  116. package/app/components/05.forms/input-button/InputButtonCore.vue +1 -5
  117. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +26 -6
  118. package/app/components/05.forms/input-number/InputNumberCore.vue +125 -85
  119. package/app/components/05.forms/input-number/stories/InputNumberCore.stories.ts +92 -0
  120. package/app/components/05.forms/input-number/tests/InputNumberCore.spec.ts +119 -0
  121. package/app/components/05.forms/input-number/variants/InputNumberDefault.vue +30 -22
  122. package/app/components/05.forms/input-number/variants/stories/InputNumberDefault.stories.ts +123 -0
  123. package/app/components/05.forms/input-number/variants/tests/InputNumberDefault.spec.ts +143 -0
  124. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +45 -0
  125. package/app/components/05.forms/input-range/InputRangeCore.vue +79 -143
  126. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +126 -0
  127. package/app/components/05.forms/input-range/tests/InputRangeCore.spec.ts +124 -0
  128. package/app/components/05.forms/input-range/variants/InputRangeDefault.vue +9 -32
  129. package/app/components/05.forms/input-range/variants/stories/InputRangeDefault.stories.ts +108 -0
  130. package/app/components/05.forms/input-range/variants/tests/InputRangeDefault.spec.ts +140 -0
  131. package/app/components/05.forms/input-select/InputSelectCore.vue +23 -30
  132. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +2 -2
  133. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +115 -0
  134. package/app/components/05.forms/input-select/tests/InputSelectCore.spec.ts +129 -0
  135. package/app/components/05.forms/input-select/variants/InputSelectWithLabel.vue +1 -1
  136. package/app/components/05.forms/input-select/variants/tests/InputSelectWithLabel.spec.ts +87 -0
  137. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +14 -0
  138. package/app/components/05.forms/input-text/variants/InputTextAsNumberWithLabel.vue +1 -1
  139. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +55 -0
  140. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +24 -16
  141. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +1 -1
  142. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +119 -0
  143. package/app/components/05.forms/input-textarea/variants/InputTextareaWithLabel.vue +3 -13
  144. package/app/components/05.forms/input-textarea/variants/tests/InputTextareaWithLabel.spec.ts +93 -0
  145. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +220 -0
  146. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +29 -0
  147. package/app/components/05.forms/toggle-switch/ToggleSwitchCore.vue +120 -119
  148. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +1 -1
  149. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +1 -1
  150. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +2 -2
  151. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +101 -0
  152. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabel.vue +3 -5
  153. package/app/components/05.forms/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +1 -2
  154. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabel.spec.ts +77 -0
  155. package/app/components/05.forms/toggle-switch/variants/tests/ToggleSwitchWithLabelInline.spec.ts +66 -0
  156. package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +20 -6
  157. package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +12 -10
  158. package/app/types/components/container-glow.d.ts +8 -0
  159. package/app/types/components/index.ts +2 -1
  160. package/app/types/components/select-menu.d.ts +5 -0
  161. package/package.json +3 -2
  162. package/.vscode/srcdev-component-toggle-switch-core.code-snippets +0 -13
  163. package/app/components/05.forms/input-range-fancy/InputRangeFancyCore.vue +0 -426
  164. package/app/components/05.forms/input-range-fancy/InputRangeFancyWithLabel.vue +0 -94
  165. package/app/components/carousels/CarouselBasic.vue +0 -317
  166. package/app/components/carousels/CarouselInfinite.vue +0 -358
  167. package/app/components/container-glow/ContainerGlowCore.vue +0 -296
  168. package/app/components/container-glow/stories/ContainerGlowCore.stories.ts +0 -337
  169. package/app/components/display-theme-switch/DisplayThemeSwitch.vue +0 -189
  170. package/app/components/display-theme-switch/stories/DisplayThemeSwitch.stories.ts +0 -154
  171. package/app/components/glowing-border/GlowingBorder.vue +0 -142
  172. package/app/components/glowing-border/stories/GlowingBorder.stories.ts +0 -141
  173. package/app/components/layout-grids/LayoutGridB.vue +0 -134
  174. package/app/components/test-storybook/TestStorybook.vue +0 -49
  175. package/app/components/test-storybook/stories/TestStorybook.stories.ts +0 -28
  176. package/app/components/ui-block-decorated/UiBlockDecorated.vue +0 -167
  177. package/app/components/view-timeline/WipeAwayVertical.vue +0 -180
  178. package/app/types/components/carousel-basic.d.ts +0 -19
@@ -0,0 +1,132 @@
1
+ <template>
2
+ <component :is="tag" class="glowing-border" :class="[variant, elementClasses]">
3
+ <slot></slot>
4
+ </component>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ interface Props {
9
+ tag?: "div" | "p" | "span" | "section" | "article" | "aside" | "header" | "footer" | "main" | "nav" | "ul" | "ol";
10
+ variant?: "subtle" | "vivid" | "silver" | "steel" | "green";
11
+ styleClassPassthrough?: string | string[];
12
+ }
13
+
14
+ const props = withDefaults(defineProps<Props>(), {
15
+ tag: "div",
16
+ variant: "subtle",
17
+ styleClassPassthrough: () => [],
18
+ });
19
+
20
+ const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
21
+
22
+ watch(
23
+ () => props.styleClassPassthrough,
24
+ () => {
25
+ resetElementClasses(props.styleClassPassthrough);
26
+ }
27
+ );
28
+ </script>
29
+
30
+ <style lang="css">
31
+ @layer components {
32
+ @property --_glow-deg {
33
+ syntax: "<angle>";
34
+ inherits: true;
35
+ initial-value: -90deg;
36
+ }
37
+
38
+ @property --_clr-1 {
39
+ syntax: "<color>";
40
+ inherits: true;
41
+ initial-value: red;
42
+ }
43
+
44
+ @property --_clr-2 {
45
+ syntax: "<color>";
46
+ inherits: true;
47
+ initial-value: yellow;
48
+ }
49
+
50
+ @property --_clr-3 {
51
+ syntax: "<color>";
52
+ inherits: true;
53
+ initial-value: green;
54
+ }
55
+
56
+ @property --_clr-4 {
57
+ syntax: "<color>";
58
+ inherits: true;
59
+ initial-value: blue;
60
+ }
61
+
62
+ @property --_clr-5 {
63
+ syntax: "<color>";
64
+ inherits: true;
65
+ initial-value: purple;
66
+ }
67
+
68
+ .glowing-border {
69
+ &.vivid {
70
+ --_clr-1: var(--glowing-border-vivid-color-1, #ff0000);
71
+ --_clr-2: var(--glowing-border-vivid-color-2, #ffa500);
72
+ --_clr-3: var(--glowing-border-vivid-color-3, #ffff00);
73
+ --_clr-4: var(--glowing-border-vivid-color-4, #008000);
74
+ --_clr-5: var(--glowing-border-vivid-color-5, #0000ff);
75
+ }
76
+
77
+ &.subtle {
78
+ --_clr-1: var(--glowing-border-subtle-color-1, #ff9a9e);
79
+ --_clr-2: var(--glowing-border-subtle-color-2, #fad0c4);
80
+ --_clr-3: var(--glowing-border-subtle-color-3, #fad0c4);
81
+ --_clr-4: var(--glowing-border-subtle-color-4, #fbc2eb);
82
+ --_clr-5: var(--glowing-border-subtle-color-5, #a18cd1);
83
+ }
84
+
85
+ &.silver {
86
+ --_clr-1: var(--glowing-border-silver-color-1, #d4d4d4);
87
+ --_clr-2: var(--glowing-border-silver-color-2, #e4e4e4);
88
+ --_clr-3: var(--glowing-border-silver-color-3, #f5f5f5);
89
+ --_clr-4: var(--glowing-border-silver-color-4, #e4e4e4);
90
+ --_clr-5: var(--glowing-border-silver-color-5, #d4d4d4);
91
+ }
92
+
93
+ &.steel {
94
+ --_clr-1: var(--glowing-border-steel-color-1, #434343);
95
+ --_clr-2: var(--glowing-border-steel-color-2, #5a5a5a);
96
+ --_clr-3: var(--glowing-border-steel-color-3, #6e6e6e);
97
+ --_clr-4: var(--glowing-border-steel-color-4, #5a5a5a);
98
+ --_clr-5: var(--glowing-border-steel-color-5, #434343);
99
+ }
100
+
101
+ &.green {
102
+ --_clr-1: var(--glowing-border-green-color-1, #00ff87);
103
+ --_clr-2: var(--glowing-border-green-color-2, #39ff14);
104
+ --_clr-3: var(--glowing-border-green-color-3, #00c853);
105
+ --_clr-4: var(--glowing-border-green-color-4, #64dd17);
106
+ --_clr-5: var(--glowing-border-green-color-5, #00e676);
107
+ }
108
+
109
+ --_gradient-glow: var(--_clr-1), var(--_clr-2), var(--_clr-3), var(--_clr-4), var(--_clr-5), var(--_clr-1);
110
+
111
+ border: var(--glowing-border-width, 3px) solid transparent;
112
+ border-radius: var(--glowing-border-radius, 30px);
113
+ background:
114
+ linear-gradient(var(--glowing-border-surface, canvas) 0 0) padding-box,
115
+ conic-gradient(from var(--_glow-deg), var(--_gradient-glow)) border-box;
116
+
117
+ animation: glow var(--glowing-border-animation-duration, 10s) infinite linear;
118
+
119
+ overflow: hidden;
120
+
121
+ @media (prefers-reduced-motion: reduce) {
122
+ animation: none;
123
+ }
124
+ }
125
+
126
+ @keyframes glow {
127
+ 100% {
128
+ --_glow-deg: 270deg;
129
+ }
130
+ }
131
+ }
132
+ </style>
@@ -0,0 +1,165 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import StorybookComponent from "../GlowingBorder.vue";
3
+
4
+ const PX_OPTIONS = ["1px", "2px", "3px", "4px", "5px", "6px"] as const;
5
+ const DURATION_OPTIONS = ["5s", "6s", "7s", "8s", "9s", "10s", "11s", "12s", "13s", "14s", "15s"] as const;
6
+
7
+ type StoryArgs = {
8
+ tag: "div" | "p" | "span" | "section" | "article" | "aside" | "header" | "footer" | "main" | "nav" | "ul" | "ol";
9
+ variant: "subtle" | "vivid" | "silver" | "steel" | "green";
10
+ content: string;
11
+ borderWidth: (typeof PX_OPTIONS)[number];
12
+ borderRadius: (typeof PX_OPTIONS)[number];
13
+ animationDuration: (typeof DURATION_OPTIONS)[number];
14
+ };
15
+
16
+ export default {
17
+ title: "Atoms/Effects/GlowingBorder",
18
+ component: StorybookComponent,
19
+ argTypes: {
20
+ tag: {
21
+ control: { type: "select" },
22
+ options: ["div", "p", "span", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"],
23
+ description: "Root element tag",
24
+ },
25
+ variant: {
26
+ control: { type: "inline-radio" },
27
+ options: ["subtle", "vivid", "silver", "steel", "green"],
28
+ description: "Glow colour variant",
29
+ },
30
+ content: {
31
+ control: { type: "text" },
32
+ description: "Slot content",
33
+ },
34
+ borderWidth: {
35
+ control: { type: "select" },
36
+ options: PX_OPTIONS,
37
+ description: "--glowing-border-width",
38
+ table: { category: "CSS tokens" },
39
+ },
40
+ borderRadius: {
41
+ control: { type: "select" },
42
+ options: PX_OPTIONS,
43
+ description: "--glowing-border-radius",
44
+ table: { category: "CSS tokens" },
45
+ },
46
+ animationDuration: {
47
+ control: { type: "select" },
48
+ options: DURATION_OPTIONS,
49
+ description: "--glowing-border-animation-duration",
50
+ table: { category: "CSS tokens" },
51
+ },
52
+ styleClassPassthrough: {
53
+ table: { disable: true },
54
+ },
55
+ class: {
56
+ table: { disable: true },
57
+ },
58
+ style: {
59
+ table: { disable: true },
60
+ },
61
+ },
62
+ args: {
63
+ tag: "div",
64
+ variant: "vivid",
65
+ content: "This is default slot content for the GlowingBorder component. Any HTML content can be placed here.",
66
+ borderWidth: "3px",
67
+ borderRadius: "6px",
68
+ animationDuration: "10s",
69
+ },
70
+ } as Meta<StoryArgs>;
71
+
72
+ const Template: StoryFn<StoryArgs> = (args) => ({
73
+ components: { StorybookComponent },
74
+ setup() {
75
+ return { args };
76
+ },
77
+ template: `
78
+ <div style="padding: 40px; background-color: var(--slate-10);">
79
+ <StorybookComponent
80
+ :tag="args.tag"
81
+ :variant="args.variant"
82
+ :style="{
83
+ '--glowing-border-width': args.borderWidth,
84
+ '--glowing-border-radius': args.borderRadius,
85
+ '--glowing-border-animation-duration': args.animationDuration,
86
+ }"
87
+ >
88
+ <div style="padding: 20px; color: var(--slate-02);">
89
+ <h3 style="margin: 0 0 12px 0; font-size: 1.5rem; font-weight: 600;">GlowingBorder</h3>
90
+ <p style="margin: 0; line-height: 1.6;">{{ args.content }}</p>
91
+ </div>
92
+ </StorybookComponent>
93
+ </div>
94
+ `,
95
+ });
96
+
97
+ export const Default = Template.bind({});
98
+
99
+ export const Vivid = Template.bind({});
100
+ Vivid.args = {
101
+ variant: "vivid",
102
+ content:
103
+ "Vivid variant creates a bright, attention-grabbing glow effect perfect for highlighting important content or call-to-action elements.",
104
+ };
105
+
106
+ export const Subtle = Template.bind({});
107
+ Subtle.args = {
108
+ variant: "subtle",
109
+ content:
110
+ "Subtle variant provides a gentle glow that adds visual interest without being overwhelming, ideal for elegant content presentation.",
111
+ };
112
+
113
+ export const Silver = Template.bind({});
114
+ Silver.args = {
115
+ variant: "silver",
116
+ content:
117
+ "Silver variant offers a metallic, sophisticated glow that works well in professional or modern design contexts.",
118
+ };
119
+
120
+ export const Steel = Template.bind({});
121
+ Steel.args = {
122
+ variant: "steel",
123
+ content:
124
+ "Steel variant provides a cool, industrial glow effect that's perfect for technical or utilitarian design themes.",
125
+ };
126
+
127
+ export const Green = Template.bind({});
128
+ Green.args = {
129
+ variant: "green",
130
+ content:
131
+ "Green variant cycles through vibrant, saturated greens for a fresh, energetic glow effect.",
132
+ };
133
+
134
+ const CustomTokensTemplate: StoryFn<StoryArgs> = (args) => ({
135
+ components: { StorybookComponent },
136
+ setup() {
137
+ return { args };
138
+ },
139
+ template: `
140
+ <div style="padding: 40px; background-color: var(--slate-10);">
141
+ <StorybookComponent
142
+ :tag="args.tag"
143
+ :variant="args.variant"
144
+ :style="{
145
+ '--glowing-border-width': args.borderWidth,
146
+ '--glowing-border-radius': args.borderRadius,
147
+ '--glowing-border-animation-duration': args.animationDuration,
148
+ }"
149
+ >
150
+ <div style="padding: 20px; color: var(--slate-02);">
151
+ <p style="margin: 0; line-height: 1.6;">{{ args.content }}</p>
152
+ </div>
153
+ </StorybookComponent>
154
+ </div>
155
+ `,
156
+ });
157
+
158
+ export const CustomTokens = CustomTokensTemplate.bind({});
159
+ CustomTokens.args = {
160
+ variant: "vivid",
161
+ content: "Border width, radius, and animation speed all driven by the controls above.",
162
+ borderWidth: "6px",
163
+ borderRadius: "4px",
164
+ animationDuration: "5s",
165
+ };
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
3
+ import GlowingBorder from "../GlowingBorder.vue";
4
+
5
+ describe("GlowingBorder", () => {
6
+ // ─── Mount ───────────────────────────────────────────────────────────────
7
+
8
+ it("mounts without error", async () => {
9
+ const wrapper = await mountSuspended(GlowingBorder);
10
+ expect(wrapper.vm).toBeTruthy();
11
+ });
12
+
13
+ // ─── Snapshots ───────────────────────────────────────────────────────────
14
+
15
+ it("renders correct HTML structure (default)", async () => {
16
+ const wrapper = await mountSuspended(GlowingBorder, {
17
+ slots: { default: "Content" },
18
+ });
19
+ expect(wrapper.html()).toMatchSnapshot();
20
+ });
21
+
22
+ // ─── Root element ─────────────────────────────────────────────────────────
23
+
24
+ it("renders as <div> by default", async () => {
25
+ const wrapper = await mountSuspended(GlowingBorder);
26
+ expect(wrapper.element.tagName).toBe("DIV");
27
+ });
28
+
29
+ it.each(["div", "p", "span", "section", "article", "aside", "header", "footer", "main", "nav", "ul", "ol"] as const)(
30
+ "renders as <%s> when tag='%s'",
31
+ async (tag) => {
32
+ const wrapper = await mountSuspended(GlowingBorder, { props: { tag } });
33
+ expect(wrapper.element.tagName).toBe(tag.toUpperCase());
34
+ }
35
+ );
36
+
37
+ // ─── Base class ───────────────────────────────────────────────────────────
38
+
39
+ it("always has the glowing-border class", async () => {
40
+ const wrapper = await mountSuspended(GlowingBorder);
41
+ expect(wrapper.classes()).toContain("glowing-border");
42
+ });
43
+
44
+ // ─── Slot ─────────────────────────────────────────────────────────────────
45
+
46
+ it("renders default slot content", async () => {
47
+ const wrapper = await mountSuspended(GlowingBorder, {
48
+ slots: { default: "<p class='custom'>Custom</p>" },
49
+ });
50
+ expect(wrapper.find(".custom").exists()).toBe(true);
51
+ });
52
+
53
+ // ─── Variant ──────────────────────────────────────────────────────────────
54
+
55
+ it("applies subtle variant class by default", async () => {
56
+ const wrapper = await mountSuspended(GlowingBorder);
57
+ expect(wrapper.classes()).toContain("subtle");
58
+ });
59
+
60
+ it.each(["subtle", "vivid", "silver", "steel", "green"] as const)(
61
+ "applies %s variant class when variant='%s'",
62
+ async (variant) => {
63
+ const wrapper = await mountSuspended(GlowingBorder, { props: { variant } });
64
+ expect(wrapper.classes()).toContain(variant);
65
+ }
66
+ );
67
+
68
+ // ─── styleClassPassthrough ────────────────────────────────────────────────
69
+
70
+ it("applies a single styleClassPassthrough string", async () => {
71
+ const wrapper = await mountSuspended(GlowingBorder, {
72
+ props: { styleClassPassthrough: "featured" },
73
+ });
74
+ expect(wrapper.classes()).toContain("featured");
75
+ });
76
+
77
+ it("applies multiple styleClassPassthrough classes from an array", async () => {
78
+ const wrapper = await mountSuspended(GlowingBorder, {
79
+ props: { styleClassPassthrough: ["featured", "highlighted"] },
80
+ });
81
+ expect(wrapper.classes()).toContain("featured");
82
+ expect(wrapper.classes()).toContain("highlighted");
83
+ });
84
+
85
+ it("updates classes when styleClassPassthrough prop changes", async () => {
86
+ const wrapper = await mountSuspended(GlowingBorder, {
87
+ props: { styleClassPassthrough: ["featured"] },
88
+ });
89
+ expect(wrapper.classes()).toContain("featured");
90
+ await wrapper.setProps({ styleClassPassthrough: ["highlighted"] });
91
+ expect(wrapper.classes()).not.toContain("featured");
92
+ expect(wrapper.classes()).toContain("highlighted");
93
+ });
94
+ });
@@ -0,0 +1,3 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`GlowingBorder > renders correct HTML structure (default) 1`] = `"<div class="glowing-border subtle">Content</div>"`;
@@ -11,7 +11,7 @@
11
11
  | `--marquee-scroller-item-hover-filter` | `grayscale(0)` | Filter applied to a single item when it is individually hovered (overrides the group filter for that item) |
12
12
  | `--marquee-scroller-item-transition-duration` | `0.5s` | Transition duration for the item hover filter change |
13
13
  | `--marquee-scroller-item-border-width` | `1px` | Item border width |
14
- | `--marquee-scroller-item-border-colour` | `light-dark(var(--slate-10), var(--slate-00))` | Item border colour |
14
+ | `--marquee-scroller-item-border-colour` | `var(--slate-10)` | Item border colour |
15
15
  | `--marquee-scroller-item-border-radius` | `4px` | Item border radius |
16
16
  | `--marquee-scroller-control-offset` | `8px` | Distance of the pause/play button from the top and end edges |
17
17
  | `--marquee-scroller-control-background-colour` | `rgba(0, 0, 0, 0.7)` | Pause/play button background |
@@ -291,7 +291,7 @@ onUnmounted(() => {
291
291
  flex-shrink: 0;
292
292
 
293
293
  border: var(--marquee-scroller-item-border-width, 1px) solid
294
- var(--marquee-scroller-item-border-colour, light-dark(var(--slate-10), var(--slate-00)));
294
+ var(--marquee-scroller-item-border-colour, var(--slate-10));
295
295
  border-radius: var(--marquee-scroller-item-border-radius, 4px);
296
296
 
297
297
  &:hover {
@@ -33,7 +33,7 @@ const logoSlots = logoData
33
33
  .map(
34
34
  (item) => `
35
35
  <template #${item.id}>
36
- <div style="display: grid; place-items: center; width: 100%; height: 100%; background: light-dark(#eee, #333); font-weight: 600;">
36
+ <div style="display: grid; place-items: center; width: 100%; height: 100%; background: #eee; font-weight: 600;">
37
37
  ${item.content}
38
38
  </div>
39
39
  </template>`
@@ -0,0 +1,44 @@
1
+ # SectionParallax — Consumer Styling Guide
2
+
3
+ ## Public CSS custom properties
4
+
5
+ | Token | Default | Description |
6
+ |---|---|---|
7
+ | `--section-parallax-min-height` | `100svh` | Section height on browsers/inputs where the fixed-background parallax doesn't apply (touch devices, `prefers-reduced-motion: reduce`, or no `background-attachment: fixed` support). |
8
+ | `--section-parallax-min-height-fixed` | `120vh` | Section height once the parallax effect is active — taller than the base height to give enough scroll travel to see the motion. |
9
+ | `--section-parallax-background-position` | `center` | `background-position` of the image. |
10
+ | `--section-parallax-background-size` | `cover` | `background-size` of the image. |
11
+ | `--section-parallax-background-colour` | `var(--slate-01)` | Fallback colour visible if the image fails to load or hasn't loaded yet. |
12
+
13
+ ```vue
14
+ <SectionParallax
15
+ background-image="/images/candle-and-stones.jpg"
16
+ style="--section-parallax-min-height: 60vh; --section-parallax-min-height-fixed: 70vh;"
17
+ >
18
+ <p>Shorter atmospheric break</p>
19
+ </SectionParallax>
20
+ ```
21
+
22
+ ## Motion
23
+
24
+ The parallax effect (`background-attachment: fixed`) only activates on `(hover: hover) and
25
+ (pointer: fine)` inputs that support it, and is disabled under `prefers-reduced-motion: reduce`
26
+ (falls back to `background-attachment: scroll` and the base `--section-parallax-min-height`).
27
+
28
+ ## Class passthrough
29
+
30
+ Use `styleClassPassthrough` to add classes to the root element — e.g. to override `min-height`
31
+ from a consuming page's own stylesheet instead of inline tokens:
32
+
33
+ ```css
34
+ .my-page {
35
+ .section-parallax {
36
+ min-height: 60vh;
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## No overlay/gradient built in
42
+
43
+ The component has no built-in overlay or gradient scrim — add one via the default slot or a
44
+ `::before` pseudo-element in your consuming-page styles.
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <component :is="tag" class="section-parallax" :class="[elementClasses]">
3
- <slot v-if="slots.default" name="default"></slot>
3
+ <slot name="default"></slot>
4
4
  </component>
5
5
  </template>
6
6
 
@@ -16,8 +16,6 @@ const props = withDefaults(defineProps<Props>(), {
16
16
  styleClassPassthrough: () => [],
17
17
  });
18
18
 
19
- const slots = useSlots();
20
-
21
19
  const backgroundImage = computed(() => `url("${props.backgroundImage}")`);
22
20
 
23
21
  const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
@@ -33,24 +31,27 @@ watch(
33
31
  <style lang="css">
34
32
  @layer components {
35
33
  .section-parallax {
36
- /* Component styles */
37
-
38
- min-height: 100svh;
34
+ min-height: var(--section-parallax-min-height, 100svh);
39
35
  background-image: v-bind(backgroundImage);
40
- background-position: center;
36
+ background-position: var(--section-parallax-background-position, center);
41
37
  background-repeat: no-repeat;
42
- background-size: cover;
38
+ background-size: var(--section-parallax-background-size, cover);
43
39
  position: relative;
44
40
 
45
- background-color: light-dark(var(--slate-01), var(--slate-08));
41
+ background-color: var(--section-parallax-background-colour, var(--slate-01));
46
42
  width: 100%;
47
43
 
48
44
  @media (hover: hover) and (pointer: fine) {
49
45
  @supports (background-attachment: fixed) {
50
46
  background-attachment: fixed;
51
- min-height: 120vh;
47
+ min-height: var(--section-parallax-min-height-fixed, 120vh);
52
48
  }
53
49
  }
50
+
51
+ @media (prefers-reduced-motion: reduce) {
52
+ background-attachment: scroll;
53
+ min-height: var(--section-parallax-min-height, 100svh);
54
+ }
54
55
  }
55
56
  }
56
57
  </style>
@@ -0,0 +1,98 @@
1
+ import type { Meta, StoryFn } from "@nuxtjs/storybook";
2
+ import StorybookComponent from "../SectionParallax.vue";
3
+
4
+ type StoryArgs = {
5
+ tag: "div" | "section" | "article" | "aside";
6
+ backgroundImage: string;
7
+ showOverlay: boolean;
8
+ overlayText: string;
9
+ };
10
+
11
+ export default {
12
+ title: "Atoms/Effects/SectionParallax",
13
+ component: StorybookComponent,
14
+ argTypes: {
15
+ tag: {
16
+ control: { type: "select" },
17
+ options: ["div", "section", "article", "aside"],
18
+ description: "Root element tag",
19
+ },
20
+ backgroundImage: {
21
+ control: { type: "text" },
22
+ description: "Path to the background image",
23
+ },
24
+ showOverlay: {
25
+ control: { type: "boolean" },
26
+ description: "Show overlay slot content",
27
+ },
28
+ overlayText: {
29
+ control: { type: "text" },
30
+ description: "Overlay slot text",
31
+ },
32
+ styleClassPassthrough: {
33
+ table: { disable: true },
34
+ },
35
+ class: {
36
+ table: { disable: true },
37
+ },
38
+ style: {
39
+ table: { disable: true },
40
+ },
41
+ },
42
+ args: {
43
+ tag: "section",
44
+ backgroundImage: "https://picsum.photos/id/28/1600/1200",
45
+ showOverlay: true,
46
+ overlayText: "A quiet moment.",
47
+ },
48
+ } as Meta<StoryArgs>;
49
+
50
+ const Template: StoryFn<StoryArgs> = (args) => ({
51
+ components: { StorybookComponent },
52
+ setup() {
53
+ return { args };
54
+ },
55
+ template: `
56
+ <StorybookComponent :tag="args.tag" :background-image="args.backgroundImage">
57
+ <div
58
+ v-if="args.showOverlay"
59
+ style="display: flex; align-items: center; justify-content: center; height: 100%; color: white; font-size: 2rem; text-shadow: 0 2px 8px rgba(0,0,0,0.6);"
60
+ >
61
+ {{ args.overlayText }}
62
+ </div>
63
+ </StorybookComponent>
64
+ `,
65
+ });
66
+
67
+ export const Default = Template.bind({});
68
+
69
+ export const NoOverlay = Template.bind({});
70
+ NoOverlay.args = {
71
+ showOverlay: false,
72
+ };
73
+
74
+ export const CustomHeight = Template.bind({});
75
+ CustomHeight.args = {
76
+ showOverlay: true,
77
+ overlayText: "Shorter atmospheric break via --section-parallax-min-height and -min-height-fixed.",
78
+ };
79
+ CustomHeight.render = (args) => ({
80
+ components: { StorybookComponent },
81
+ setup() {
82
+ return { args };
83
+ },
84
+ template: `
85
+ <StorybookComponent
86
+ :tag="args.tag"
87
+ :background-image="args.backgroundImage"
88
+ style="--section-parallax-min-height: 60vh; --section-parallax-min-height-fixed: 70vh;"
89
+ >
90
+ <div
91
+ v-if="args.showOverlay"
92
+ style="display: flex; align-items: center; justify-content: center; height: 100%; color: white; font-size: 2rem; text-shadow: 0 2px 8px rgba(0,0,0,0.6);"
93
+ >
94
+ {{ args.overlayText }}
95
+ </div>
96
+ </StorybookComponent>
97
+ `,
98
+ });