srcdev-nuxt-components 9.4.8 → 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.
- package/.claude/commands/migrate-component.md +20 -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-range-core.md +142 -0
- package/.claude/skills/components/section-parallax.md +32 -10
- 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 +10 -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-range.code-snippets +85 -0
- package/.vscode/srcdev-component-section-parallax.code-snippets +27 -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/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 +1 -1
- package/app/components/01.atoms/canvas-switcher/stories/CanvasSwitcher.stories.ts +1 -1
- 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/navigation/skip-links/SkipLinks.vue +7 -1
- 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/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 +96 -11
- 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-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/patterns/stories/MigratedFieldsForm.stories.ts +135 -0
- package/app/components/05.forms/triple-toggle-switch/CONSUMER-STYLING.md +16 -2
- package/app/components/05.forms/triple-toggle-switch/TripleToggleSwitchCore.vue +6 -4
- package/app/types/components/container-glow.d.ts +8 -0
- package/app/types/components/index.ts +1 -1
- package/package.json +3 -2
- 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,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,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` | `light-dark(var(--slate-01), var(--slate-08))` | 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.
|
package/app/components/{parallax → 01.atoms/animations/section-parallax}/SectionParallax.vue
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<component :is="tag" class="section-parallax" :class="[elementClasses]">
|
|
3
|
-
<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
|
-
|
|
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, light-dark(var(--slate-01), var(--slate-08)));
|
|
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>
|
package/app/components/01.atoms/animations/section-parallax/stories/SectionParallax.stories.ts
ADDED
|
@@ -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
|
+
});
|