srcdev-nuxt-components 9.5.7 → 9.6.1
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/skills/component-compliance-checklist.md +8 -2
- package/.claude/skills/component-dynamic-slots.md +3 -3
- package/.claude/skills/component-export-types.md +10 -16
- package/.claude/skills/component-naming.md +1 -1
- package/.claude/skills/components/alert-content.md +9 -1
- package/.claude/skills/components/breadcrumb.md +5 -0
- package/.claude/skills/components/container-glow.md +21 -8
- package/.claude/skills/components/display-avatar.md +13 -7
- package/.claude/skills/components/display-chip.md +37 -24
- package/.claude/skills/components/display-pill.md +58 -8
- package/.claude/skills/components/display-prompt.md +8 -5
- package/.claude/skills/components/display-toast.md +21 -4
- package/.claude/skills/components/glowing-border.md +20 -2
- package/.claude/skills/components/input-button.md +3 -0
- package/.claude/skills/components/responsive-header.md +59 -0
- package/.claude/skills/components/select-menu.md +54 -5
- package/.claude/skills/components/service-detail.md +32 -0
- package/.claude/skills/components/service-summary.md +8 -0
- package/.claude/skills/components/services-card-grid.md +5 -3
- package/.claude/skills/components/services-card.md +22 -12
- package/.claude/skills/components/site-header.md +2 -2
- package/.claude/skills/components/slider-gallery.md +10 -4
- package/.claude/skills/components/tabbed-content.md +300 -0
- package/.claude/skills/composable-anchor-scroll.md +16 -15
- package/.claude/skills/index.md +4 -5
- package/.claude/skills/storybook-add-story.md +53 -0
- package/.claude/skills/theming-status-tokens.md +60 -0
- package/.vscode/srcdev-component-display-avatar.code-snippets +2 -1
- package/.vscode/srcdev-component-display-chip.code-snippets +3 -2
- package/.vscode/srcdev-component-display-pill.code-snippets +10 -0
- package/.vscode/srcdev-component-responsive-header.code-snippets +16 -0
- package/.vscode/srcdev-component-select-menu.code-snippets +28 -0
- package/.vscode/srcdev-component-service-detail.code-snippets +1 -0
- package/.vscode/srcdev-component-service-summary.code-snippets +3 -1
- package/.vscode/srcdev-component-services-card.code-snippets +38 -36
- package/.vscode/srcdev-component-services-grids.code-snippets +2 -1
- package/.vscode/{srcdev-component-tabs-core.code-snippets → srcdev-component-tabbed-content.code-snippets} +35 -16
- package/app/assets/styles/setup/03.theming/_status.css +24 -0
- package/app/assets/styles/setup/03.theming/index.css +1 -0
- package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +39 -35
- package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +25 -15
- package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +130 -24
- package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +45 -0
- package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +2 -2
- package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +76 -19
- package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +12 -10
- package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +201 -42
- package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +15 -4
- package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +1 -1
- package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +78 -9
- package/app/components/01.atoms/display-pill/DisplayPill.vue +59 -23
- package/app/components/01.atoms/display-pill/stories/DisplayPill.stories.ts +240 -16
- package/app/components/01.atoms/display-pill/tests/DisplayPill.spec.ts +43 -14
- package/app/components/01.atoms/display-pill/tests/__snapshots__/DisplayPill.spec.ts.snap +3 -3
- package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +16 -4
- package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +28 -5
- package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +94 -0
- package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +27 -0
- package/app/components/01.atoms/tabbed-content/CONSUMER-STYLING.md +201 -0
- package/app/components/01.atoms/tabbed-content/TabbedContent.vue +750 -0
- package/app/components/01.atoms/tabbed-content/stories/TabbedContent.stories.ts +279 -0
- package/app/components/01.atoms/tabbed-content/tests/TabbedContent.spec.ts +816 -0
- package/app/components/02.molecules/alert-content/AlertContent.vue +11 -2
- package/app/components/02.molecules/alert-content/AlertContentInner.vue +19 -0
- package/app/components/02.molecules/alert-content/CONSUMER-STYLING.md +17 -4
- package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +200 -17
- package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +33 -0
- package/app/components/02.molecules/display-avatar/CONSUMER-STYLING.md +7 -5
- package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +6 -2
- package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +24 -3
- package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +19 -0
- package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +2 -1
- package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +52 -19
- package/app/components/02.molecules/display-chip/DisplayChip.vue +72 -54
- package/app/components/02.molecules/display-chip/stories/DisplayChip.stories.ts +103 -6
- package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +98 -29
- package/app/components/02.molecules/display-chip/tests/__snapshots__/DisplayChip.spec.ts.snap +8 -4
- package/app/components/02.molecules/prompt/CONSUMER-STYLING.md +9 -1
- package/app/components/02.molecules/prompt/DisplayPrompt.vue +2 -2
- package/app/components/02.molecules/prompt/stories/DisplayPrompt.stories.ts +208 -71
- package/app/components/02.molecules/prompt/tests/DisplayPrompt.spec.ts +19 -6
- package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +66 -8
- package/app/components/02.molecules/select-menu/SelectMenu.vue +141 -18
- package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +232 -2
- package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +151 -0
- package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +6 -2
- package/app/components/02.molecules/toast/CONSUMER-STYLING.md +91 -27
- package/app/components/02.molecules/toast/DisplayToast.vue +109 -50
- package/app/components/02.molecules/toast/DisplayToastProvider.vue +109 -45
- package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +23 -0
- package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +73 -0
- package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +132 -18
- package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +105 -39
- package/app/components/03.organisms/image-galleries/slider-gallery/CONSUMER-STYLING.md +19 -1
- package/app/components/03.organisms/image-galleries/slider-gallery/SliderGallery.vue +65 -7
- package/app/components/03.organisms/image-galleries/slider-gallery/stories/SliderGallery.stories.ts +201 -63
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +41 -2
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/__snapshots__/SliderGallery.spec.ts.snap +6 -6
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +42 -11
- package/app/components/03.organisms/responsive-header/NavigationItems.vue +60 -16
- package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +86 -24
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +52 -0
- package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +20 -0
- package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +40 -0
- package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +92 -41
- package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +198 -73
- package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +157 -9
- package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +58 -0
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +5 -4
- package/app/components/03.organisms/services/service-summary/CONSUMER-STYLING.md +30 -0
- package/app/components/03.organisms/services/service-summary/ServiceSummary.vue +32 -8
- package/app/components/03.organisms/services/service-summary/stories/ServiceSummary.stories.ts +147 -0
- package/app/components/03.organisms/services/service-summary/tests/ServiceSummary.spec.ts +26 -0
- package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +2 -2
- package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +104 -63
- package/app/components/03.organisms/services/services-card/ServicesCard.vue +92 -66
- package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +194 -27
- package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +34 -8
- package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +8 -8
- package/app/components/03.organisms/services/services-grids/CONSUMER-STYLING.md +9 -0
- package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +2 -1
- package/app/components/03.organisms/services/services-grids/stories/ServiceSummaryGrid.stories.ts +46 -6
- package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +89 -11
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +8 -0
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +6 -6
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +24 -24
- package/app/components/05.forms/input-button/CONSUMER-STYLING.md +8 -0
- package/app/components/05.forms/input-button/InputButton.vue +3 -1
- package/app/composables/tests/useAnchorScroll.spec.ts +8 -0
- package/app/composables/useAnchorScroll.ts +1 -1
- package/app/composables/useTabs.ts +148 -206
- package/app/types/components/display-chip.d.ts +8 -4
- package/app/types/components/display-toast.d.ts +2 -0
- package/app/types/components/index.ts +0 -1
- package/app/types/components/select-menu.d.ts +2 -0
- package/app/utils/pausableTimeout.ts +41 -0
- package/nuxt.config.ts +1 -1
- package/package.json +1 -1
- package/.claude/skills/components/site-navigation.md +0 -145
- package/.claude/skills/components/tab-navigation.md +0 -272
- package/.claude/skills/components/tabs-core.md +0 -187
- package/.vscode/srcdev-component-site-navigation.code-snippets +0 -38
- package/.vscode/srcdev-component-tab-navigation.code-snippets +0 -30
- package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +0 -120
- package/app/components/01.atoms/navigation/tabs/TabsCore.vue +0 -272
- package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +0 -199
- package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +0 -274
- package/app/components/02.molecules/navigation/site-navigation/CONSUMER-STYLING.md +0 -109
- package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +0 -751
- package/app/components/02.molecules/navigation/site-navigation/stories/SiteNavigation.stories.ts +0 -356
- package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +0 -367
- package/app/components/02.molecules/navigation/site-navigation/tests/__snapshots__/SiteNavigation.spec.ts.snap +0 -30
- package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +0 -150
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +0 -518
- package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +0 -340
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +0 -339
- package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +0 -33
- package/app/composables/useNavCollapse.ts +0 -86
- package/app/types/components/nav-item.d.ts +0 -11
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Meta, StoryFn } from "@nuxtjs/storybook";
|
|
2
|
+
import { computed, ref } from "vue";
|
|
2
3
|
import StorybookComponent from "../GlowingBorder.vue";
|
|
4
|
+
import InputTextCore from "../../../../05.forms/input-text/InputTextCore.vue";
|
|
5
|
+
import InputButton from "../../../../05.forms/input-button/InputButton.vue";
|
|
6
|
+
import CanvasSwitcher from "../../../canvas-switcher/CanvasSwitcher.vue";
|
|
7
|
+
import type { MediaCanvas } from "~/types/components";
|
|
3
8
|
|
|
4
9
|
const PX_OPTIONS = ["1px", "2px", "3px", "4px", "5px", "6px"] as const;
|
|
5
10
|
const DURATION_OPTIONS = ["5s", "6s", "7s", "8s", "9s", "10s", "11s", "12s", "13s", "14s", "15s"] as const;
|
|
@@ -11,8 +16,24 @@ type StoryArgs = {
|
|
|
11
16
|
borderWidth: (typeof PX_OPTIONS)[number];
|
|
12
17
|
borderRadius: (typeof PX_OPTIONS)[number];
|
|
13
18
|
animationDuration: (typeof DURATION_OPTIONS)[number];
|
|
19
|
+
canvasBackground: string;
|
|
20
|
+
surface: string;
|
|
14
21
|
};
|
|
15
22
|
|
|
23
|
+
const darkColours = ["#000000", "#020024", "#0f0f23", "#1e293b", "#262626"];
|
|
24
|
+
const darkColourLabels = {
|
|
25
|
+
"": "Theme default (light)",
|
|
26
|
+
"#000000": "Black",
|
|
27
|
+
"#020024": "Midnight",
|
|
28
|
+
"#0f0f23": "Ink",
|
|
29
|
+
"#1e293b": "Slate",
|
|
30
|
+
"#262626": "Charcoal",
|
|
31
|
+
};
|
|
32
|
+
const surfaceLabels = { ...darkColourLabels, "#ffffff": "White" };
|
|
33
|
+
const isDark = (colour: string) => darkColours.includes(colour);
|
|
34
|
+
|
|
35
|
+
const tokenCategory = "CSS tokens (story only, set in your CSS)";
|
|
36
|
+
|
|
16
37
|
export default {
|
|
17
38
|
title: "Atoms/Effects/GlowingBorder",
|
|
18
39
|
component: StorybookComponent,
|
|
@@ -29,25 +50,41 @@ export default {
|
|
|
29
50
|
},
|
|
30
51
|
content: {
|
|
31
52
|
control: { type: "text" },
|
|
32
|
-
description: "
|
|
53
|
+
description: "**Story control, not a prop.** Text placed in the default slot.",
|
|
54
|
+
table: { category: "Slot content (story only)" },
|
|
33
55
|
},
|
|
34
56
|
borderWidth: {
|
|
35
57
|
control: { type: "select" },
|
|
36
58
|
options: PX_OPTIONS,
|
|
37
|
-
description: "--glowing-border-width",
|
|
38
|
-
table: { category:
|
|
59
|
+
description: "**Story control, not a prop.** Sets `--glowing-border-width`. In your CSS: `.promo { --glowing-border-width: 4px; }`",
|
|
60
|
+
table: { category: tokenCategory },
|
|
39
61
|
},
|
|
40
62
|
borderRadius: {
|
|
41
63
|
control: { type: "select" },
|
|
42
64
|
options: PX_OPTIONS,
|
|
43
|
-
description: "--glowing-border-radius",
|
|
44
|
-
table: { category:
|
|
65
|
+
description: "**Story control, not a prop.** Sets `--glowing-border-radius`. In your CSS: `.promo { --glowing-border-radius: 6px; }`",
|
|
66
|
+
table: { category: tokenCategory },
|
|
45
67
|
},
|
|
46
68
|
animationDuration: {
|
|
47
69
|
control: { type: "select" },
|
|
48
70
|
options: DURATION_OPTIONS,
|
|
49
|
-
description:
|
|
50
|
-
|
|
71
|
+
description:
|
|
72
|
+
"**Story control, not a prop.** Sets `--glowing-border-animation-duration`. In your CSS: `.promo { --glowing-border-animation-duration: 6s; }`",
|
|
73
|
+
table: { category: tokenCategory },
|
|
74
|
+
},
|
|
75
|
+
surface: {
|
|
76
|
+
control: { type: "select", labels: surfaceLabels },
|
|
77
|
+
options: ["", "#ffffff", ...darkColours],
|
|
78
|
+
description:
|
|
79
|
+
"**Story control, not a prop.** Sets `--glowing-border-surface` (and a light `--glowing-border-text-colour` for dark values). In your CSS: `.promo { --glowing-border-surface: #0f0f23; --glowing-border-text-colour: #f3f4f6; }`",
|
|
80
|
+
table: { category: tokenCategory },
|
|
81
|
+
},
|
|
82
|
+
canvasBackground: {
|
|
83
|
+
control: { type: "select", labels: darkColourLabels },
|
|
84
|
+
options: ["", ...darkColours],
|
|
85
|
+
description:
|
|
86
|
+
"**Story control, not a prop.** Background of the demo stage, so the glow can be checked on a dark page.",
|
|
87
|
+
table: { category: "Story canvas (story only)" },
|
|
51
88
|
},
|
|
52
89
|
styleClassPassthrough: {
|
|
53
90
|
table: { disable: true },
|
|
@@ -66,26 +103,43 @@ export default {
|
|
|
66
103
|
borderWidth: "3px",
|
|
67
104
|
borderRadius: "6px",
|
|
68
105
|
animationDuration: "10s",
|
|
106
|
+
canvasBackground: "",
|
|
107
|
+
surface: "",
|
|
108
|
+
},
|
|
109
|
+
parameters: {
|
|
110
|
+
docs: {
|
|
111
|
+
description: {
|
|
112
|
+
component:
|
|
113
|
+
"Wraps its default slot in an animated conic-gradient glow border. The canvas and surface controls are story-only, for checking the glow against dark themes.",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
69
116
|
},
|
|
70
117
|
} as Meta<StoryArgs>;
|
|
71
118
|
|
|
119
|
+
function useStorySetup(args: StoryArgs) {
|
|
120
|
+
const stageStyle = computed(() => ({
|
|
121
|
+
background: args.canvasBackground || undefined,
|
|
122
|
+
}));
|
|
123
|
+
const tokenStyle = computed(() => ({
|
|
124
|
+
"--glowing-border-width": args.borderWidth,
|
|
125
|
+
"--glowing-border-radius": args.borderRadius,
|
|
126
|
+
"--glowing-border-animation-duration": args.animationDuration,
|
|
127
|
+
"--glowing-border-surface": args.surface || undefined,
|
|
128
|
+
"--glowing-border-text-colour":
|
|
129
|
+
isDark(args.surface) ? "#f3f4f6" : undefined,
|
|
130
|
+
}));
|
|
131
|
+
return { args, stageStyle, tokenStyle };
|
|
132
|
+
}
|
|
133
|
+
|
|
72
134
|
const Template: StoryFn<StoryArgs> = (args) => ({
|
|
73
135
|
components: { StorybookComponent },
|
|
74
136
|
setup() {
|
|
75
|
-
return
|
|
137
|
+
return useStorySetup(args);
|
|
76
138
|
},
|
|
77
139
|
template: `
|
|
78
|
-
<div style="padding: 40px;
|
|
79
|
-
<StorybookComponent
|
|
80
|
-
|
|
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);">
|
|
140
|
+
<div style="padding: 40px;" :style="stageStyle">
|
|
141
|
+
<StorybookComponent :tag="args.tag" :variant="args.variant" :style="tokenStyle">
|
|
142
|
+
<div style="padding: 20px;">
|
|
89
143
|
<h3 style="margin: 0 0 12px 0; font-size: 1.5rem; font-weight: 600;">GlowingBorder</h3>
|
|
90
144
|
<p style="margin: 0; line-height: 1.6;">{{ args.content }}</p>
|
|
91
145
|
</div>
|
|
@@ -127,39 +181,144 @@ Steel.args = {
|
|
|
127
181
|
export const Green = Template.bind({});
|
|
128
182
|
Green.args = {
|
|
129
183
|
variant: "green",
|
|
130
|
-
content:
|
|
131
|
-
|
|
184
|
+
content: "Green variant cycles through vibrant, saturated greens for a fresh, energetic glow effect.",
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const CustomTokens = Template.bind({});
|
|
188
|
+
CustomTokens.args = {
|
|
189
|
+
variant: "vivid",
|
|
190
|
+
content: "Border width, radius, and animation speed all driven by the CSS token controls.",
|
|
191
|
+
borderWidth: "6px",
|
|
192
|
+
borderRadius: "4px",
|
|
193
|
+
animationDuration: "5s",
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const HighlightedControls: StoryFn<StoryArgs> = (args) => ({
|
|
197
|
+
components: { StorybookComponent, InputTextCore, InputButton },
|
|
198
|
+
setup() {
|
|
199
|
+
const { args: storyArgs, stageStyle } = useStorySetup(args);
|
|
200
|
+
const glowStyle = (controlRadius: string) =>
|
|
201
|
+
computed(() => ({
|
|
202
|
+
"--glowing-border-width": args.borderWidth,
|
|
203
|
+
"--glowing-border-radius": `calc(${controlRadius} + ${args.borderWidth})`,
|
|
204
|
+
"--glowing-border-animation-duration": args.animationDuration,
|
|
205
|
+
"--glowing-border-surface": "transparent",
|
|
206
|
+
"--glowing-border-overflow": "visible",
|
|
207
|
+
display: "inline-block",
|
|
208
|
+
}));
|
|
209
|
+
const buttonGlowStyle = glowStyle("var(--button-border-radius)");
|
|
210
|
+
const inputGlowStyle = computed(() => ({
|
|
211
|
+
...glowStyle("var(--form-input-border-radius)").value,
|
|
212
|
+
"--input-text-border": "transparent",
|
|
213
|
+
display: "block",
|
|
214
|
+
}));
|
|
215
|
+
const email = ref("");
|
|
216
|
+
return { args: storyArgs, stageStyle, buttonGlowStyle, inputGlowStyle, email };
|
|
217
|
+
},
|
|
218
|
+
template: `
|
|
219
|
+
<div style="padding: 40px; display: grid; gap: 32px; justify-items: start;" :style="stageStyle">
|
|
220
|
+
<StorybookComponent tag="span" :variant="args.variant" :style="buttonGlowStyle">
|
|
221
|
+
<InputButton type="button" variant="primary" button-text="Book a free consultation"></InputButton>
|
|
222
|
+
</StorybookComponent>
|
|
223
|
+
|
|
224
|
+
<div style="display: grid; gap: 8px; inline-size: min(100%, 40rem);">
|
|
225
|
+
<label for="highlighted-email">Early-access email</label>
|
|
226
|
+
<StorybookComponent :variant="args.variant" :style="inputGlowStyle">
|
|
227
|
+
<InputTextCore
|
|
228
|
+
id="highlighted-email"
|
|
229
|
+
v-model="email"
|
|
230
|
+
type="email"
|
|
231
|
+
name="highlighted-email"
|
|
232
|
+
placeholder="eg. jane@example.com"
|
|
233
|
+
></InputTextCore>
|
|
234
|
+
</StorybookComponent>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
`,
|
|
238
|
+
});
|
|
239
|
+
HighlightedControls.args = {
|
|
240
|
+
variant: "vivid",
|
|
241
|
+
borderWidth: "3px",
|
|
242
|
+
};
|
|
243
|
+
HighlightedControls.parameters = {
|
|
244
|
+
docs: {
|
|
245
|
+
description: {
|
|
246
|
+
story:
|
|
247
|
+
"The glow replaces a single control's border, for when a design calls out something special. No changes to the controls: " +
|
|
248
|
+
"InputButton's border already matches its surface, and the input gets `--input-text-border: transparent`. On the GlowingBorder, " +
|
|
249
|
+
"the radius is the control's radius plus the glow width, the surface is transparent, and `--glowing-border-overflow: visible` " +
|
|
250
|
+
"keeps the controls' hover and focus outlines from being clipped. Tab through to check the focus rings.",
|
|
251
|
+
},
|
|
252
|
+
},
|
|
132
253
|
};
|
|
133
254
|
|
|
134
|
-
const
|
|
255
|
+
export const StressTest: StoryFn<StoryArgs> = (args) => ({
|
|
135
256
|
components: { StorybookComponent },
|
|
136
257
|
setup() {
|
|
137
|
-
|
|
258
|
+
const longUnbroken = "Donaudampfschifffahrtselektrizitätenhauptbetriebswerkbauunterbeamtengesellschaft";
|
|
259
|
+
const longUrl = "https://example.com/" + "a-very-long-path-segment-without-spaces".repeat(4);
|
|
260
|
+
const longCopy =
|
|
261
|
+
"Unsere Behandlungen werden individuell auf Ihre Bedürfnisse abgestimmt, damit Sie sich rundum wohlfühlen und entspannt nach Hause gehen können. ".repeat(
|
|
262
|
+
6
|
|
263
|
+
);
|
|
264
|
+
return { ...useStorySetup(args), longUnbroken, longUrl, longCopy };
|
|
138
265
|
},
|
|
139
266
|
template: `
|
|
140
|
-
<div style="padding:
|
|
141
|
-
<StorybookComponent
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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>
|
|
267
|
+
<div style="padding: 24px 16px; display: grid; gap: 24px;" :style="stageStyle">
|
|
268
|
+
<StorybookComponent :tag="args.tag" :variant="args.variant" :style="tokenStyle">
|
|
269
|
+
<div style="padding: 20px;">
|
|
270
|
+
<h3 style="margin: 0 0 12px 0;">{{ longUnbroken }}</h3>
|
|
271
|
+
<p style="margin: 0 0 12px 0;">{{ longUrl }}</p>
|
|
272
|
+
<p style="margin: 0;">{{ longCopy }}</p>
|
|
152
273
|
</div>
|
|
153
274
|
</StorybookComponent>
|
|
275
|
+
<StorybookComponent :tag="args.tag" :variant="args.variant" :style="tokenStyle"></StorybookComponent>
|
|
276
|
+
<StorybookComponent :tag="args.tag" :variant="args.variant" :style="tokenStyle">A</StorybookComponent>
|
|
277
|
+
<StorybookComponent :tag="args.tag" :variant="args.variant" :style="tokenStyle">
|
|
278
|
+
<p style="margin: 0; padding: 12px;">🎉 Emoji first, then <b>html</b> as text</p>
|
|
279
|
+
</StorybookComponent>
|
|
280
|
+
<StorybookComponent :tag="args.tag" :variant="args.variant" :style="tokenStyle">
|
|
281
|
+
<p dir="rtl" style="margin: 0; padding: 12px;">نص عربي طويل من اليمين إلى اليسار للتحقق من الاتجاه</p>
|
|
282
|
+
</StorybookComponent>
|
|
283
|
+
<p style="margin: 0;">
|
|
284
|
+
Inline use:
|
|
285
|
+
<StorybookComponent tag="span" :variant="args.variant" :style="tokenStyle">{{ longUnbroken }}</StorybookComponent>
|
|
286
|
+
sits in running text.
|
|
287
|
+
</p>
|
|
154
288
|
</div>
|
|
155
289
|
`,
|
|
156
290
|
});
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
CustomTokens.args = {
|
|
160
|
-
variant: "vivid",
|
|
161
|
-
content: "Border width, radius, and animation speed all driven by the controls above.",
|
|
291
|
+
StressTest.storyName = "Stress Test (Worst-Case Data)";
|
|
292
|
+
StressTest.args = {
|
|
162
293
|
borderWidth: "6px",
|
|
163
|
-
borderRadius: "
|
|
164
|
-
|
|
294
|
+
borderRadius: "6px",
|
|
295
|
+
};
|
|
296
|
+
StressTest.decorators = [
|
|
297
|
+
(story, context) => ({
|
|
298
|
+
components: { story, CanvasSwitcher },
|
|
299
|
+
setup() {
|
|
300
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "mobileCanvas");
|
|
301
|
+
return { canvasName };
|
|
302
|
+
},
|
|
303
|
+
template: `
|
|
304
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
305
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
306
|
+
</div>
|
|
307
|
+
<div :class="canvasName" style="margin-inline: auto; outline: 1px dashed currentColor;">
|
|
308
|
+
<story />
|
|
309
|
+
</div>
|
|
310
|
+
`,
|
|
311
|
+
}),
|
|
312
|
+
];
|
|
313
|
+
StressTest.parameters = {
|
|
314
|
+
initialCanvas: "mobileCanvas",
|
|
315
|
+
docs: {
|
|
316
|
+
description: {
|
|
317
|
+
story:
|
|
318
|
+
"Oversized and odd slot content. Long unbroken words and URLs should wrap inside the border rather than being clipped " +
|
|
319
|
+
"(the root has overflow: hidden), long German copy should grow the box, the empty one should collapse to just its border, " +
|
|
320
|
+
"HTML-like text should render as text, RTL text should align right, and the inline span should wrap within running text. " +
|
|
321
|
+
"Check at every canvas width and with each variant.",
|
|
322
|
+
},
|
|
323
|
+
},
|
|
165
324
|
};
|
|
@@ -52,19 +52,30 @@ describe("GlowingBorder", () => {
|
|
|
52
52
|
|
|
53
53
|
// ─── Variant ──────────────────────────────────────────────────────────────
|
|
54
54
|
|
|
55
|
-
it("
|
|
55
|
+
it("sets data-variant='subtle' by default", async () => {
|
|
56
56
|
const wrapper = await mountSuspended(GlowingBorder);
|
|
57
|
-
expect(wrapper.
|
|
57
|
+
expect(wrapper.attributes("data-variant")).toBe("subtle");
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it.each(["subtle", "vivid", "silver", "steel", "green"] as const)(
|
|
61
|
-
"
|
|
61
|
+
"sets data-variant='%s' when variant='%s'",
|
|
62
62
|
async (variant) => {
|
|
63
63
|
const wrapper = await mountSuspended(GlowingBorder, { props: { variant } });
|
|
64
|
-
expect(wrapper.
|
|
64
|
+
expect(wrapper.attributes("data-variant")).toBe(variant);
|
|
65
65
|
}
|
|
66
66
|
);
|
|
67
67
|
|
|
68
|
+
it("does not add the variant name as a class, so consumer classes like .green can't collide", async () => {
|
|
69
|
+
const wrapper = await mountSuspended(GlowingBorder, { props: { variant: "green" } });
|
|
70
|
+
expect(wrapper.classes()).not.toContain("green");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("updates data-variant when the variant prop changes", async () => {
|
|
74
|
+
const wrapper = await mountSuspended(GlowingBorder, { props: { variant: "vivid" } });
|
|
75
|
+
await wrapper.setProps({ variant: "steel" });
|
|
76
|
+
expect(wrapper.attributes("data-variant")).toBe("steel");
|
|
77
|
+
});
|
|
78
|
+
|
|
68
79
|
// ─── styleClassPassthrough ────────────────────────────────────────────────
|
|
69
80
|
|
|
70
81
|
it("applies a single styleClassPassthrough string", async () => {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`GlowingBorder > renders correct HTML structure (default) 1`] = `"<div class="glowing-border subtle">Content</div>"`;
|
|
3
|
+
exports[`GlowingBorder > renders correct HTML structure (default) 1`] = `"<div class="glowing-border" data-variant="subtle">Content</div>"`;
|
|
@@ -17,9 +17,9 @@ the base token restyles every variant, and a variant token still wins for that o
|
|
|
17
17
|
| `--display-pill-background` | `var(--slate-01)` | Background (the `default` variant, and the fallback for every other variant) |
|
|
18
18
|
| `--display-pill-text-colour` | `var(--slate-09)` | Text/icon colour (same fallback role) |
|
|
19
19
|
| `--display-pill-primary-background` / `-text-colour` | `var(--blue-01)` / `var(--blue-09)` | `primary` variant |
|
|
20
|
-
| `--display-pill-success-background` / `-text-colour` | `var(--
|
|
21
|
-
| `--display-pill-warning-background` / `-text-colour` | `var(--
|
|
22
|
-
| `--display-pill-danger-background` / `-text-colour` | `var(--
|
|
20
|
+
| `--display-pill-success-background` / `-text-colour` | `var(--status-success-surface)` / `var(--status-success-text)` | `success` variant |
|
|
21
|
+
| `--display-pill-warning-background` / `-text-colour` | `var(--status-warning-surface)` / `var(--status-warning-text)` | `warning` variant |
|
|
22
|
+
| `--display-pill-danger-background` / `-text-colour` | `var(--status-danger-surface)` / `var(--status-danger-text)` | `danger` variant |
|
|
23
23
|
| `--display-pill-neutral-background` / `-text-colour` | `var(--slate-08)` / `var(--slate-03)` | `neutral` variant (dark) |
|
|
24
24
|
| `--display-pill-focus-ring` | `var(--theme-border-focus)` | `:focus-visible` outline on `button`/`a` pills |
|
|
25
25
|
|
|
@@ -29,16 +29,26 @@ the base token restyles every variant, and a variant token still wins for that o
|
|
|
29
29
|
> `--yellow-*` ramp that doesn't exist, so it always showed its hardcoded hex fallbacks; it now uses
|
|
30
30
|
> the `orange` ramp the warning theme moved to.
|
|
31
31
|
|
|
32
|
+
`success`, `warning` and `danger` default to the global `--status-*` tokens
|
|
33
|
+
(`03.theming/_status.css`), so overriding those restyles pills along with every other status
|
|
34
|
+
indicator; same colours as before (changed 2026-10-07). `primary` (brand) and `neutral` (a dark,
|
|
35
|
+
inverted pill) keep their own defaults and don't read the status set. See
|
|
36
|
+
`.claude/skills/theming-status-tokens.md`.
|
|
37
|
+
|
|
32
38
|
### Border, outline and shape
|
|
33
39
|
|
|
34
40
|
| Token | Default | Controls |
|
|
35
41
|
|---|---|---|
|
|
36
|
-
| `--display-pill-border-colour` | `transparent` | Border colour |
|
|
42
|
+
| `--display-pill-border-colour` | `transparent` | Border colour (every variant's fallback) |
|
|
43
|
+
| `--display-pill-<variant>-border-colour` | the base border colour | Border colour for one variant (`primary`, `success`, `warning`, `danger`, `neutral`) |
|
|
37
44
|
| `--display-pill-border-width` | `0.1rem` | Border width |
|
|
38
45
|
| `--display-pill-border-style` | `solid` | Border style |
|
|
39
46
|
| `--display-pill-border-radius` | `100vw` | Radius (100vw is always a full pill) |
|
|
40
47
|
| `--display-pill-outline` | `none` | Decorative outline (shorthand) on every pill |
|
|
41
48
|
| `--display-pill-outline-offset` | `0` | Offset for that outline |
|
|
49
|
+
| `--display-pill-ring-width` | `0` | Width of a ring drawn just outside the border (a `box-shadow`, so it follows the radius on every browser and takes no layout space) |
|
|
50
|
+
| `--display-pill-<variant>-ring-colour` | the base ring colour | Ring colour for one variant (`primary`, `success`, `warning`, `danger`, `neutral`) |
|
|
51
|
+
| `--display-pill-ring-colour` | the pill's resolved background | Ring colour. The default follows each variant's background, so `border-colour: currentColor` plus a ring gives a two-tone border in every variant's own colours |
|
|
42
52
|
|
|
43
53
|
### Size
|
|
44
54
|
|
|
@@ -50,6 +60,16 @@ the base token restyles every variant, and a variant token still wins for that o
|
|
|
50
60
|
|
|
51
61
|
The unsuffixed token is the `md` value; `-sm`/`-lg` apply to those sizes.
|
|
52
62
|
|
|
63
|
+
### Label
|
|
64
|
+
|
|
65
|
+
| Token | Default | Controls |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `--display-pill-label-line-clamp` | `1` | Lines of label text before an ellipsis. `1` keeps the pill on one line; `none` lets a long label wrap; any other number caps the lines |
|
|
68
|
+
|
|
69
|
+
Applies to the `label` prop and to default-slot content alike. Clamped text stays in the DOM, so
|
|
70
|
+
screen readers still read all of it. If a pill's text must stay fully readable (a price, say) in a
|
|
71
|
+
narrow space, set `none`.
|
|
72
|
+
|
|
53
73
|
> **Changed 2026-09-27**: the icon-size tokens were declared but never used, so icons weren't
|
|
54
74
|
> sized. The icon slot is now wrapped in `.display-pill-icon`, which sets its `font-size`.
|
|
55
75
|
|
|
@@ -70,20 +90,35 @@ keeps the same proportions. Tune a **ratio** to change spacing across all sizes,
|
|
|
70
90
|
> `-lg`, etc., now removed). It's now derived from the icon size; results are within about 0.1rem
|
|
71
91
|
> of the old values, except `lg` block padding (`0.6rem` → `0.48rem`), which was out of proportion.
|
|
72
92
|
|
|
73
|
-
Private tokens (not public API): `--_background`, `--_text-colour`, `--_font-size`, `--_padding-inline`,
|
|
74
|
-
`--_padding-block`, `--_padding-inline-icon` (derived from `--_icon-size`), `--_icon-size`, swapped by the size and variant
|
|
93
|
+
Private tokens (not public API): `--_background`, `--_text-colour`, `--_border-colour`, `--_ring-colour`, `--_font-size`, `--_padding-inline`,
|
|
94
|
+
`--_padding-block`, `--_padding-inline-icon` (derived from `--_icon-size`), `--_icon-size`, swapped by the size and variant attributes.
|
|
95
|
+
|
|
96
|
+
> **Changed 2026-10-06**: a pill is capped at its container's width (`max-inline-size: 100%`), and a
|
|
97
|
+
> label too long for it ends in an ellipsis instead of overflowing. Short labels are unaffected.
|
|
98
|
+
>
|
|
99
|
+
> **Changed 2026-10-08**: default-slot text is now wrapped in `.display-pill-label` too, so it
|
|
100
|
+
> ellipsizes like a `label` prop instead of spilling out of the pill. The ellipsis is now a
|
|
101
|
+
> line clamp (`--display-pill-label-line-clamp`, default `1`) rather than `white-space: nowrap`,
|
|
102
|
+
> so a long label breaks at a space before the ellipsis rather than mid-word. A pill with no text
|
|
103
|
+
> and no icon (empty or whitespace-only `label`, no slots) no longer renders a blank coloured blob.
|
|
75
104
|
|
|
76
105
|
## State hooks
|
|
77
106
|
|
|
78
|
-
-
|
|
79
|
-
|
|
107
|
+
- `data-size`: `sm`, `md`, `lg`. `data-variant`: `default`, `primary`, `success`, `warning`,
|
|
108
|
+
`danger`, `neutral` (an unknown value gets the default colours). `data-reversed` when `reversed`.
|
|
80
109
|
- `data-icon-position`: `start` (icon before the text), `end` (`reversed`), `only` (icon with no
|
|
81
110
|
label or text), or absent (no icon). It picks which side gets `--display-pill-padding-inline-icon`.
|
|
82
111
|
- Classes: `.display-pill` (root), `.display-pill-icon` (icon slot wrapper, only when used),
|
|
83
|
-
`.display-pill-label` (
|
|
112
|
+
`.display-pill-label` (wraps the `label` prop or default-slot content, absent when there's no text).
|
|
84
113
|
- `button`/`a` roots get a pointer cursor and a `:focus-visible` outline.
|
|
85
114
|
|
|
86
115
|
> **Changed 2026-09-27**: `.pill-label` → `.display-pill-label`.
|
|
116
|
+
>
|
|
117
|
+
> **Changed 2026-10-08**: size, variant and reversed were bare classes (`.sm`, `.primary`,
|
|
118
|
+
> `.success`, `.is-reversed`...), which a consumer's own utility classes of the same name could
|
|
119
|
+
> match, since the pill renders inside the consumer's DOM. Replace `.display-pill.primary` with
|
|
120
|
+
> `.display-pill[data-variant="primary"]`, `.display-pill.sm` with `.display-pill[data-size="sm"]`,
|
|
121
|
+
> and `.display-pill.is-reversed` with `.display-pill[data-reversed]`.
|
|
87
122
|
|
|
88
123
|
## Local overrides
|
|
89
124
|
|
|
@@ -97,6 +132,19 @@ component need `:deep()`. Patterns and examples: `.claude/skills/component-local
|
|
|
97
132
|
won't reach those pills. `ServiceSummary` and `ServiceDetail` set `--display-pill-background`,
|
|
98
133
|
`-text-colour` and `-border-colour` on their pill rows; use their own `--service-*-pill-*` tokens.
|
|
99
134
|
|
|
135
|
+
## Recipe: two-tone ringed pills
|
|
136
|
+
|
|
137
|
+
A 1px border in each variant's dark text colour, inside a 2px ring in its light background colour:
|
|
138
|
+
|
|
139
|
+
```css
|
|
140
|
+
.tag-list {
|
|
141
|
+
--display-pill-border-colour: currentColor;
|
|
142
|
+
--display-pill-ring-width: 0.2rem;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The `neutral` variant is inverted (light text on dark), so its border comes out light and its ring dark.
|
|
147
|
+
|
|
100
148
|
## Recipe: outlined pills
|
|
101
149
|
|
|
102
150
|
```css
|
|
@@ -107,6 +155,27 @@ won't reach those pills. `ServiceSummary` and `ServiceDetail` set `--display-pil
|
|
|
107
155
|
}
|
|
108
156
|
```
|
|
109
157
|
|
|
158
|
+
That gives every variant the surrounding text colour. To keep each variant's own colour, leave
|
|
159
|
+
`--display-pill-text-colour` unset and set the per-variant text tokens instead. The default text
|
|
160
|
+
steps (`-09`/`-10`) are near-black in every hue, so lift them to `-07` to tell the variants apart
|
|
161
|
+
(the border follows, being `currentColor`):
|
|
162
|
+
|
|
163
|
+
```css
|
|
164
|
+
.tag-list {
|
|
165
|
+
--display-pill-background: transparent;
|
|
166
|
+
--display-pill-border-colour: currentColor;
|
|
167
|
+
--display-pill-text-colour: var(--slate-07);
|
|
168
|
+
--display-pill-primary-text-colour: var(--blue-07);
|
|
169
|
+
--display-pill-success-text-colour: var(--green-07);
|
|
170
|
+
--display-pill-warning-text-colour: var(--orange-07);
|
|
171
|
+
--display-pill-danger-text-colour: var(--red-07);
|
|
172
|
+
--display-pill-neutral-text-colour: var(--slate-10);
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Add a ring in each variant's light `-02` step with `--display-pill-ring-width: 0.2rem` and
|
|
177
|
+
`--display-pill-<variant>-ring-colour` (`var(--blue-02)`, `var(--green-02)`, and so on).
|
|
178
|
+
|
|
110
179
|
## Class passthrough
|
|
111
180
|
|
|
112
181
|
`style-class-passthrough` adds classes to the root. Reachable in normal use.
|