srcdev-nuxt-components 9.6.5 → 9.6.7

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 (35) hide show
  1. package/.claude/rules/consumer/a11y-no-focus-steal.md +14 -0
  2. package/.claude/skills/component-compliance-checklist.md +5 -1
  3. package/.claude/skills/components/display-toast.md +6 -3
  4. package/.claude/skills/components/page-hero-highlights.md +22 -18
  5. package/.claude/skills/components/social-icons-list.md +32 -5
  6. package/.claude/skills/css-dynamic-border-radius.md +114 -0
  7. package/.claude/skills/index.md +3 -2
  8. package/.claude/skills/storybook-add-story.md +6 -0
  9. package/.vscode/srcdev-component-display-toast.code-snippets +6 -8
  10. package/.vscode/srcdev-component-page-hero-highlights.code-snippets +2 -1
  11. package/.vscode/srcdev-component-social-icons-list.code-snippets +14 -0
  12. package/.vscode/srcdev-utility-dynamic-border-radius.code-snippets +31 -0
  13. package/app/assets/styles/setup/06.utility-classes/_dynamic-border-radius.css +27 -0
  14. package/app/assets/styles/setup/06.utility-classes/index.css +1 -0
  15. package/app/components/02.molecules/social-icons-list/CONSUMER-STYLING.md +97 -8
  16. package/app/components/02.molecules/social-icons-list/SocialIconsList.vue +80 -14
  17. package/app/components/02.molecules/social-icons-list/stories/SocialIconsList.stories.ts +230 -2
  18. package/app/components/02.molecules/social-icons-list/stories/social-icons-list-recipes.css +108 -0
  19. package/app/components/02.molecules/social-icons-list/tests/SocialIconsList.spec.ts +123 -52
  20. package/app/components/02.molecules/social-icons-list/tests/__snapshots__/SocialIconsList.spec.ts.snap +10 -4
  21. package/app/components/02.molecules/toast/CONSUMER-STYLING.md +9 -3
  22. package/app/components/02.molecules/toast/DisplayToast.vue +10 -10
  23. package/app/components/02.molecules/toast/DisplayToastProvider.vue +12 -4
  24. package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +200 -2
  25. package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +33 -12
  26. package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +20 -0
  27. package/app/components/04.templates/page-hero-highlights/CONSUMER-STYLING.md +32 -12
  28. package/app/components/04.templates/page-hero-highlights/PageHeroHighlights.vue +56 -47
  29. package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlights.stories.ts +158 -307
  30. package/app/components/04.templates/page-hero-highlights/stories/PageHeroHighlightsHeader.stories.ts +10 -0
  31. package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlights.spec.ts +49 -27
  32. package/app/components/04.templates/page-hero-highlights/tests/PageHeroHighlightsHeader.spec.ts +42 -7
  33. package/app/components/04.templates/page-hero-highlights/tests/__snapshots__/PageHeroHighlights.spec.ts.snap +12 -10
  34. package/app/types/components/social-icons-list.d.ts +9 -0
  35. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
- import { computed } from "vue";
1
+ import { computed, ref } from "vue";
2
2
  import PageHeroHighlights from "../PageHeroHighlights.vue";
3
+ import CanvasSwitcher from "../../../01.atoms/canvas-switcher/CanvasSwitcher.vue";
3
4
  import type { Meta, StoryObj } from "@nuxtjs/storybook";
5
+ import type { MediaCanvas } from "~/types/components";
4
6
 
5
7
  type StoryArgs = {
6
8
  tag?: "div" | "section" | "main";
@@ -12,7 +14,7 @@ type StoryArgs = {
12
14
  highlightTitleBaseline?: boolean;
13
15
  headerBackground?: string;
14
16
  contentBackground?: string;
15
- highlightCount?: 1 | 2 | 3;
17
+ highlightCount?: number;
16
18
  styleClassPassthrough?: string | string[];
17
19
  };
18
20
 
@@ -23,11 +25,12 @@ const meta: Meta<StoryArgs> = {
23
25
  tag: {
24
26
  control: { type: "select" },
25
27
  options: ["div", "section", "main"],
26
- description: "HTML element to render as the root",
28
+ description: "HTML element to render as the root. `section` wires `aria-labelledby` to the heading bound to the `header` slot's `headingId`.",
27
29
  },
28
30
  highlightsEqualWidths: {
29
31
  control: "boolean",
30
- description: "When true, highlight items share equal widths (grid). When false, items size to content (flex).",
32
+ description:
33
+ "When true, highlight items share equal widths and wrap onto new rows below `--page-hero-highlights-item-min-width` (default 12rem). When false, items size to content (flex).",
31
34
  },
32
35
  highlightsJustify: {
33
36
  control: { type: "select" },
@@ -37,36 +40,38 @@ const meta: Meta<StoryArgs> = {
37
40
  widthConstrained: {
38
41
  control: "boolean",
39
42
  description:
40
- "When true, caps the central column at --page-hero-highlights-max-width (default 1064px). Gutters grow responsively to enforce the constraint. Override --page-hero-highlights-max-width via styleClassPassthrough to change the cap value.",
43
+ "When true, caps the central column at `--page-hero-highlights-max-width` (default 1064px); the gutters grow to enforce it. When false, the column fills the width between the gutters.",
41
44
  },
42
45
  contentAlign: {
43
46
  control: { type: "inline-radio" },
44
47
  options: ["start", "center"],
45
48
  description:
46
- "Align the content column to the start (left gutter stays 16px, right takes remaining space) or center (equal gutters). Only meaningful when maxWidth is set.",
49
+ "With `widthConstrained`: `center` grows both gutters equally, `start` keeps the start gutter and gives the remaining space to the end gutter. No effect otherwise.",
47
50
  },
48
51
  contentPanel: {
49
52
  control: "boolean",
50
53
  description:
51
- "When true (default), renders a decorative panel behind the content slot and offsets the highlights strip. Set to false for a flat layout with no backdrop.",
54
+ "When true (default), renders a decorative panel behind the content slot and insets the highlights strip. Set to false for a flat layout with no backdrop.",
52
55
  },
53
56
  highlightTitleBaseline: {
54
57
  control: "boolean",
55
58
  description:
56
- "When true, fixes the highlight title row to a set height so titles align to the header baseline. Override --page-hero-highlights-title-height in the consuming page to tune the value.",
59
+ "When true, fixes the highlight title row to `--page-hero-highlights-title-height-baseline` so titles line up on the header's baseline.",
57
60
  },
58
61
  headerBackground: {
59
62
  control: "color",
60
- description: "Background colour of the header zone (sets --page-hero-highlights-header-background)",
63
+ description: "**Story control, not a prop.** Sets `--page-hero-highlights-header-background`.",
64
+ table: { category: "Story only" },
61
65
  },
62
66
  contentBackground: {
63
67
  control: "color",
64
- description: "Background colour of the content zone (sets --page-hero-highlights-content-background)",
68
+ description: "**Story control, not a prop.** Sets `--page-hero-highlights-content-background`.",
69
+ table: { category: "Story only" },
65
70
  },
66
71
  highlightCount: {
67
- control: { type: "select" },
68
- options: [1, 2, 3],
69
- description: "Number of highlight items to display in the highlights slot",
72
+ control: { type: "range", min: 1, max: 6, step: 1 },
73
+ description: "**Story control, not a prop.** Number of highlight items rendered in the `highlights` slot.",
74
+ table: { category: "Story only" },
70
75
  },
71
76
  styleClassPassthrough: {
72
77
  control: "object",
@@ -83,252 +88,137 @@ const meta: Meta<StoryArgs> = {
83
88
  highlightTitleBaseline: false,
84
89
  headerBackground: "",
85
90
  contentBackground: "",
86
- highlightCount: 1,
91
+ highlightCount: 3,
87
92
  styleClassPassthrough: [],
88
93
  },
94
+ decorators: [
95
+ (story, context) => ({
96
+ components: { story, CanvasSwitcher },
97
+ setup() {
98
+ const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
99
+ return { canvasName };
100
+ },
101
+ template: `
102
+ <div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
103
+ <CanvasSwitcher v-model:canvas-name="canvasName" />
104
+ </div>
105
+ <div :class="canvasName" style="margin-inline: auto; outline: 1px dashed currentColor;">
106
+ <story />
107
+ </div>
108
+ `,
109
+ }),
110
+ ],
111
+ parameters: {
112
+ layout: "fullscreen",
113
+ docs: {
114
+ description: {
115
+ component:
116
+ "A page-level template: a full-bleed header zone (`header` slot, scoped `headingId`), a highlights strip that straddles the header/content boundary (`highlights` slot), " +
117
+ "and a content zone with an optional decorative panel (`content` slot). Give each highlight the `page-hero-highlights-item` class with `-item-title` and `-item-body` children for the card styling. " +
118
+ "Gutters step at 768px and 1024px of the component's own width. Every colour and spacing value is a `--page-hero-highlights-*` token (see CONSUMER-STYLING.md). " +
119
+ "Pair it with `PageHeroHighlightsHeader` for a title plus actions header.",
120
+ },
121
+ story: { inline: false, height: "640px" },
122
+ },
123
+ },
89
124
  };
90
125
 
91
126
  export default meta;
92
- type Story = StoryObj<typeof PageHeroHighlights>;
127
+ type Story = StoryObj<StoryArgs>;
93
128
 
94
129
  // ─── Shared setup helper ──────────────────────────────────────────────────────
95
130
 
131
+ const sampleHighlights = [
132
+ { title: "Total revenue", body: "£24,500" },
133
+ { title: "Active users", body: "1,284" },
134
+ { title: "Open tasks", body: "37" },
135
+ { title: "Bookings this week", body: "112" },
136
+ { title: "Average rating", body: "4.8" },
137
+ { title: "Repeat clients", body: "62%" },
138
+ ];
139
+
96
140
  function useStorySetup(args: StoryArgs) {
97
141
  const bgStyles = computed(() => ({
98
142
  ...(args.headerBackground ? { "--page-hero-highlights-header-background": args.headerBackground } : {}),
99
143
  ...(args.contentBackground ? { "--page-hero-highlights-content-background": args.contentBackground } : {}),
100
144
  }));
101
- const highlightCount = computed(() => args.highlightCount ?? 3);
145
+ const highlights = computed(() => sampleHighlights.slice(0, args.highlightCount ?? 3));
102
146
  const componentArgs = computed(() => {
103
147
  const { headerBackground: _h, contentBackground: _c, highlightCount: _n, ...rest } = args;
104
148
  return rest;
105
149
  });
106
- return { bgStyles, componentArgs, highlightCount };
150
+ return { bgStyles, componentArgs, highlights };
107
151
  }
108
152
 
109
- // ─── Stories ─────────────────────────────────────────────────────────────────
110
-
111
- /** Default — header, highlights strip, and page content with all three slots filled. */
112
- export const Default: Story = {
113
- parameters: {
114
- docs: {
115
- description: {
116
- story: `
117
- All layout and visual properties are customisable via CSS custom properties. Set them on \`.page-hero-highlights\` (or a scoped class via \`styleClassPassthrough\`):
118
-
119
- \`\`\`css
120
- .page-hero-highlights {
121
- /* Grid layout */
122
- --page-hero-highlights-max-width: 1064px;
123
- --page-hero-highlights-gutter-mobile: 16px;
124
- --page-hero-highlights-gutter-tablet: 40px;
125
- --page-hero-highlights-gutter-desktop: 32px;
126
-
127
- /* Header zone */
128
- --page-hero-highlights-header-background: darkblue;
129
-
130
- /* Highlights strip */
131
- --page-hero-highlights-strip-gap: 1rem;
132
- --page-hero-highlights-strip-inset: 1.2rem;
133
-
134
- /* Highlight cards */
135
- --page-hero-highlights-item-rows-gap: 1.2rem;
136
- --page-hero-highlights-title-height: 1fr; /* see: highlight-title-baseline prop */
137
- --page-hero-highlights-item-padding-block-start: 1.2rem;
138
- --page-hero-highlights-item-padding: 1.2rem;
139
- --page-hero-highlights-item-background: white;
140
- --page-hero-highlights-item-border: 1px solid black;
141
- --page-hero-highlights-item-border-radius: 8px;
142
- --page-hero-highlights-item-colour: black;
143
-
144
- /* Content zone */
145
- --page-hero-highlights-content-background: var(--slate-01); /* transparent */
146
- --page-hero-highlights-content-start-gap: 1.2rem;
147
- --page-hero-highlights-content-end-gap: 1.2rem;
148
-
149
- /* Content slot decorative border */
150
- --page-hero-highlights-content-margin-block-start: 2.4rem;
151
- --page-hero-highlights-content-margin: var(--page-hero-highlights-strip-inset);
152
- --page-hero-highlights-panel-background: var(--slate-00);
153
- --page-hero-highlights-panel-border: 1px solid var(--slate-06);
154
- --page-hero-highlights-panel-border-radius: 0.8rem;
155
- --page-hero-highlights-panel-outline: 1px solid var(--slate-02);
156
-
157
- /* When using :highlight-title-baseline="true" */
158
- --page-hero-highlights-title-height-baseline: 4rem; /* proportional value preferred */
159
- --page-hero-highlights-item-padding-block-start-baseline: 0;
160
- }
161
- \`\`\`
162
- `,
163
- },
164
- },
165
- },
166
- render: (args: StoryArgs) => ({
153
+ const highlightsTemplate = `
154
+ <template #highlights>
155
+ <div v-for="item in highlights" :key="item.title" class="page-hero-highlights-item">
156
+ <p class="page-hero-highlights-item-title page-heading-4">{{ item.title }}</p>
157
+ <p class="page-hero-highlights-item-body page-body-normal">{{ item.body }}</p>
158
+ </div>
159
+ </template>
160
+ `;
161
+
162
+ const contentTemplate = `
163
+ <template #content>
164
+ <p class="page-heading-2">Recent activity</p>
165
+ <p class="page-body-normal">Your most recent transactions and events will appear here.</p>
166
+ </template>
167
+ `;
168
+
169
+ const render =
170
+ (header: string): Story["render"] =>
171
+ (args) => ({
167
172
  components: { PageHeroHighlights },
168
173
  setup() {
169
174
  return useStorySetup(args);
170
175
  },
171
176
  template: `
172
177
  <PageHeroHighlights v-bind="componentArgs" :style="bgStyles">
173
- <template #header>
174
- <div style="color: white; padding-block: 1.6rem;">
175
- <h1 class="page-heading-1" style="color: white;">Dashboard</h1>
176
- <p class="page-body-normal" style="color: white;">Overview of your account activity and key metrics.</p>
177
- </div>
178
- </template>
179
-
180
- <template #highlights>
181
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
182
- <p class="page-heading-2">Total Revenue</p>
183
- <p class="page-body-normal">£24,500</p>
184
- </div>
185
- <div v-if="highlightCount >= 2" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
186
- <p class="page-heading-2">Active Users</p>
187
- <p class="page-body-normal">1,284</p>
188
- </div>
189
- <div v-if="highlightCount >= 3" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
190
- <p class="page-heading-2">Open Tasks</p>
191
- <p class="page-body-normal">37</p>
192
- </div>
193
- </template>
194
-
195
- <template #content>
196
- <pre style="padding: 1.6rem; font-size: 1.2rem; line-height: 1.6; overflow-x: auto;">/* ─── PageHeroHighlights CSS tokens ─────────────────────────────────
197
- Set on .page-hero-highlights (or a scoped styleClassPassthrough class).
198
- Update values as needed. Delete tokens you are not overriding.
199
- ─────────────────────────────────────────────────────────────────── */
200
- .page-hero-highlights {
201
-
202
- /* Grid layout */
203
- --page-hero-highlights-max-width: 1064px;
204
- --page-hero-highlights-gutter-mobile: 16px;
205
- --page-hero-highlights-gutter-tablet: 40px;
206
- --page-hero-highlights-gutter-desktop: 32px;
207
-
208
- /* Header zone */
209
- --page-hero-highlights-header-background: darkblue;
210
-
211
- /* Highlights strip */
212
- --page-hero-highlights-strip-gap: 1rem;
213
- --page-hero-highlights-strip-inset: 1.2rem;
214
-
215
- /* Highlight cards */
216
- --page-hero-highlights-item-rows-gap: 1.2rem;
217
- --page-hero-highlights-title-height: 1fr; /* see: highlight-title-baseline prop */
218
- --page-hero-highlights-item-padding-block-start: 1.2rem;
219
- --page-hero-highlights-item-padding: 1.2rem;
220
- --page-hero-highlights-item-background: white;
221
- --page-hero-highlights-item-border: 1px solid black;
222
- --page-hero-highlights-item-border-radius: 8px;
223
- --page-hero-highlights-item-colour: black;
178
+ ${header}
179
+ ${highlightsTemplate}
180
+ ${contentTemplate}
181
+ </PageHeroHighlights>
182
+ `,
183
+ });
224
184
 
225
- /* Content zone */
226
- --page-hero-highlights-content-background: var(--slate-01); /* transparent */
227
- --page-hero-highlights-content-start-gap: 1.2rem;
228
- --page-hero-highlights-content-end-gap: 1.2rem;
185
+ const plainHeader = (copy: string) => `
186
+ <template #header>
187
+ <p class="page-heading-1">Dashboard</p>
188
+ <p class="page-body-normal">${copy}</p>
189
+ </template>
190
+ `;
229
191
 
230
- /* Content slot decorative border */
231
- --page-hero-highlights-content-margin-block-start: 2.4rem;
232
- --page-hero-highlights-content-margin: var(--page-hero-highlights-strip-inset);
233
- --page-hero-highlights-panel-background: var(--slate-00);
234
- --page-hero-highlights-panel-border: 1px solid var(--slate-06);
235
- --page-hero-highlights-panel-border-radius: 0.8rem;
236
- --page-hero-highlights-panel-outline: 1px solid var(--slate-02);
192
+ // ─── Stories ─────────────────────────────────────────────────────────────────
237
193
 
238
- /* When using :highlight-title-baseline="true" */
239
- --page-hero-highlights-title-height-baseline: 4rem; /* proportional value preferred */
240
- --page-hero-highlights-item-padding-block-start-baseline: 0;
241
- }</pre>
242
- </template>
243
- </PageHeroHighlights>
244
- `,
245
- }),
194
+ /** Default — header, highlights strip and page content, with the library's light defaults. */
195
+ export const Default: Story = {
196
+ render: render(plainHeader("Overview of your account activity and key metrics.")),
246
197
  };
247
198
 
248
199
  /** As section tag — root renders as a semantic section with aria-labelledby wired to the heading. */
249
200
  export const AsSectionTag: Story = {
250
201
  name: "As section Tag",
251
202
  args: { tag: "section" },
252
- render: (args: StoryArgs) => ({
253
- components: { PageHeroHighlights },
254
- setup() {
255
- return useStorySetup(args);
256
- },
257
- template: `
258
- <PageHeroHighlights v-bind="componentArgs" :style="bgStyles">
259
- <template #header="{ headingId }">
260
- <h1 :id="headingId" class="page-heading-1">Dashboard</h1>
261
- <p class="page-body-normal">Overview of your account activity and key metrics.</p>
262
- </template>
263
-
264
- <template #highlights>
265
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
266
- <p class="page-heading-2">Total Revenue</p>
267
- <p class="page-body-normal">£24,500</p>
268
- </div>
269
- <div v-if="highlightCount >= 2" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
270
- <p class="page-heading-2">Active Users</p>
271
- <p class="page-body-normal">1,284</p>
272
- </div>
273
- <div v-if="highlightCount >= 3" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
274
- <p class="page-heading-2">Open Tasks</p>
275
- <p class="page-body-normal">37</p>
276
- </div>
277
- </template>
278
-
279
- <template #content>
280
- <p class="page-heading-2">Recent Activity</p>
281
- <p class="page-body-normal">Your most recent transactions and events will appear here.</p>
282
- </template>
283
- </PageHeroHighlights>
284
- `,
285
- }),
203
+ render: render(`
204
+ <template #header="{ headingId }">
205
+ <h1 :id="headingId" class="page-heading-1">Dashboard</h1>
206
+ <p class="page-body-normal">The section is labelled by this heading through the header slot's headingId.</p>
207
+ </template>
208
+ `),
286
209
  };
287
210
 
288
- /** Equal widths — highlights items share equal column widths via grid. */
211
+ /** Equal widths — highlight items share equal widths and wrap below their minimum width. */
289
212
  export const EqualWidthHighlights: Story = {
290
213
  name: "Equal Width Highlights",
291
- args: { highlightsEqualWidths: true },
292
- render: (args: StoryArgs) => ({
293
- components: { PageHeroHighlights },
294
- setup() {
295
- return useStorySetup(args);
296
- },
297
- template: `
298
- <PageHeroHighlights v-bind="componentArgs" :style="bgStyles">
299
- <template #header>
300
- <p class="page-heading-1">Dashboard</p>
301
- <p class="page-body-normal">Overview of your account activity and key metrics.</p>
302
- </template>
303
-
304
- <template #highlights>
305
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
306
- <p class="page-heading-2">Total Revenue</p>
307
- <p class="page-body-normal">£24,500</p>
308
- </div>
309
- <div v-if="highlightCount >= 2" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
310
- <p class="page-heading-2">Active Users</p>
311
- <p class="page-body-normal">1,284</p>
312
- </div>
313
- <div v-if="highlightCount >= 3" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
314
- <p class="page-heading-2">Open Tasks</p>
315
- <p class="page-body-normal">37</p>
316
- </div>
317
- </template>
318
-
319
- <template #content>
320
- <p class="page-heading-2">Recent Activity</p>
321
- <p class="page-body-normal">Your most recent transactions and events will appear here.</p>
322
- </template>
323
- </PageHeroHighlights>
324
- `,
325
- }),
214
+ args: { highlightsEqualWidths: true, highlightCount: 4 },
215
+ render: render(plainHeader("Four equal-width highlights. Switch to the mobile canvas to see them wrap two by two, then one per row.")),
326
216
  };
327
217
 
328
218
  /** Minimal — renders with no slot content to show the bare grid structure. */
329
219
  export const NoSlotContent: Story = {
330
220
  name: "No Slot Content",
331
- render: (args: StoryArgs) => ({
221
+ render: (args) => ({
332
222
  components: { PageHeroHighlights },
333
223
  setup() {
334
224
  return useStorySetup(args);
@@ -337,122 +227,83 @@ export const NoSlotContent: Story = {
337
227
  }),
338
228
  };
339
229
 
340
- /** Max width centered — content column capped at --page-hero-highlights-max-width (1064px) with equal growing gutters either side. */
230
+ /** Width constrained, centred — content column capped at --page-hero-highlights-max-width with equal growing gutters. */
341
231
  export const MaxWidthCentered: Story = {
342
232
  name: "Max Width — Centered",
343
233
  args: { widthConstrained: true, contentAlign: "center" },
344
- render: (args: StoryArgs) => ({
345
- components: { PageHeroHighlights },
346
- setup() {
347
- return useStorySetup(args);
348
- },
349
- template: `
350
- <PageHeroHighlights v-bind="componentArgs" :style="bgStyles">
351
- <template #header>
352
- <p class="page-heading-1">Dashboard</p>
353
- <p class="page-body-normal">Content column is capped at --page-hero-highlights-max-width (1064px by default) — gutters grow equally on both sides.</p>
354
- </template>
355
-
356
- <template #highlights>
357
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
358
- <p class="page-heading-2">Total Revenue</p>
359
- <p class="page-body-normal">£24,500</p>
360
- </div>
361
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
362
- <p class="page-heading-2">Active Users</p>
363
- <p class="page-body-normal">1,284</p>
364
- </div>
365
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
366
- <p class="page-heading-2">Open Tasks</p>
367
- <p class="page-body-normal">37</p>
368
- </div>
369
- </template>
370
-
371
- <template #content>
372
- <p class="page-heading-2">Recent Activity</p>
373
- <p class="page-body-normal">Your most recent transactions and events will appear here.</p>
374
- </template>
375
- </PageHeroHighlights>
376
- `,
377
- }),
234
+ render: render(
235
+ plainHeader("The central column is capped at --page-hero-highlights-max-width (1064px by default); both gutters grow equally. Use the full-width canvas on a wide screen.")
236
+ ),
378
237
  };
379
238
 
380
- /** Max width start — content column capped at --page-hero-highlights-max-width (1064px), pinned to the left. */
239
+ /** Width constrained, start — content column capped at --page-hero-highlights-max-width and pinned to the start. */
381
240
  export const MaxWidthStart: Story = {
382
241
  name: "Max Width — Start",
383
242
  args: { widthConstrained: true, contentAlign: "start" },
384
- render: (args: StoryArgs) => ({
385
- components: { PageHeroHighlights },
386
- setup() {
387
- return useStorySetup(args);
388
- },
389
- template: `
390
- <PageHeroHighlights v-bind="componentArgs" :style="bgStyles">
391
- <template #header>
392
- <p class="page-heading-1">Dashboard</p>
393
- <p class="page-body-normal">Content column is capped at --page-hero-highlights-max-width (1064px by default), aligned to the start — right side takes the remaining space.</p>
394
- </template>
395
-
396
- <template #highlights>
397
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
398
- <p class="page-heading-2">Total Revenue</p>
399
- <p class="page-body-normal">£24,500</p>
400
- </div>
401
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
402
- <p class="page-heading-2">Active Users</p>
403
- <p class="page-body-normal">1,284</p>
404
- </div>
405
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
406
- <p class="page-heading-2">Open Tasks</p>
407
- <p class="page-body-normal">37</p>
408
- </div>
409
- </template>
410
-
411
- <template #content>
412
- <p class="page-heading-2">Recent Activity</p>
413
- <p class="page-body-normal">Your most recent transactions and events will appear here.</p>
414
- </template>
415
- </PageHeroHighlights>
416
- `,
417
- }),
243
+ render: render(
244
+ plainHeader("The central column is capped at --page-hero-highlights-max-width (1064px by default) and pinned to the start; the end gutter takes the remaining space.")
245
+ ),
418
246
  };
419
247
 
420
- /** No content panel — flat layout with no backdrop or highlight offset behind the content slot. */
248
+ /** No content panel — flat layout with no backdrop or highlight inset behind the content slot. */
421
249
  export const NoContentPanel: Story = {
422
250
  name: "No Content Panel",
423
251
  args: { contentPanel: false },
424
- render: (args: StoryArgs) => ({
252
+ render: render(plainHeader("Flat layout: no decorative panel behind the content slot.")),
253
+ };
254
+
255
+ const stressHighlights = [
256
+ { title: "Donaudampfschifffahrtselektrizitätenhauptbetriebswerkbauunterbeamtengesellschaft", body: "Unbroken German compound in the title." },
257
+ { title: "Monatlicher Gesamtumsatz aller Filialen im Vergleich zum Vorjahreszeitraum", body: "€1.234.567,89" },
258
+ { title: "😀 Emoji first", body: "🎉🎉🎉" },
259
+ { title: "<script>alert('xss')</script>", body: "HTML-like text must render as text." },
260
+ { title: "إجمالي الإيرادات", body: "٢٤٬٥٠٠ ج.م", dir: "rtl" },
261
+ { title: "", body: "Empty title." },
262
+ { title: "A", body: "" },
263
+ { title: "Link", body: "https://example.com/a-very-long-path-segment-without-any-spaces-at-all-in-it" },
264
+ ];
265
+
266
+ /**
267
+ * Stress test — unbroken and long translated text in the header, eight hostile highlights, a
268
+ * long URL in the content, inside the CanvasSwitcher (starts on mobile).
269
+ */
270
+ export const StressTest: Story = {
271
+ name: "Stress Test (Worst-Case Data)",
272
+ args: { tag: "section", highlightsEqualWidths: true, widthConstrained: true },
273
+ render: (args) => ({
425
274
  components: { PageHeroHighlights },
426
275
  setup() {
427
- return useStorySetup(args);
276
+ return { ...useStorySetup(args), stressHighlights };
428
277
  },
429
278
  template: `
430
279
  <PageHeroHighlights v-bind="componentArgs" :style="bgStyles">
431
- <template #header>
432
- <p class="page-heading-1">Dashboard</p>
433
- <p class="page-body-normal">Flat layout — no decorative panel behind the content slot.</p>
280
+ <template #header="{ headingId }">
281
+ <h1 :id="headingId" class="page-heading-1" style="overflow-wrap: anywhere;">Kundendienstmitarbeiterkontaktaufnahmeformularübersicht</h1>
282
+ <p class="page-body-normal">Übersicht über die Aktivitäten Ihres Kontos, die wichtigsten Kennzahlen und alle offenen Vorgänge der letzten dreißig Tage.</p>
434
283
  </template>
435
-
436
284
  <template #highlights>
437
- <div style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
438
- <p class="page-heading-2">Total Revenue</p>
439
- <p class="page-body-normal">£24,500</p>
440
- </div>
441
- <div v-if="highlightCount >= 2" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
442
- <p class="page-heading-2">Active Users</p>
443
- <p class="page-body-normal">1,284</p>
444
- </div>
445
- <div v-if="highlightCount >= 3" style="border-radius: 12px; background: #1a1a2e; color: white; padding: 1.6rem;">
446
- <p class="page-heading-2">Open Tasks</p>
447
- <p class="page-body-normal">37</p>
285
+ <div v-for="(item, i) in stressHighlights" :key="i" class="page-hero-highlights-item" :dir="item.dir">
286
+ <p class="page-hero-highlights-item-title page-heading-4">{{ item.title }}</p>
287
+ <p class="page-hero-highlights-item-body page-body-normal">{{ item.body }}</p>
448
288
  </div>
449
289
  </template>
450
-
451
290
  <template #content>
452
- <p class="page-heading-2">Recent Activity</p>
453
- <p class="page-body-normal">Your most recent transactions and events will appear here.</p>
291
+ <p class="page-heading-2">Ergebnisse</p>
292
+ <p class="page-body-normal" style="overflow-wrap: anywhere;">https://example.com/another-very-long-path-segment-without-any-spaces-that-must-wrap-inside-the-panel</p>
454
293
  </template>
455
294
  </PageHeroHighlights>
456
295
  `,
457
296
  }),
297
+ parameters: {
298
+ initialCanvas: "mobileCanvas",
299
+ docs: {
300
+ description: {
301
+ story:
302
+ "Check every canvas width, with `highlightsEqualWidths` on and off: no horizontal scroll, the strip still straddles the header and content zones, " +
303
+ "cards wrap onto new rows rather than squeezing (equal widths) or overflowing (flexible), and the unbroken title and URL wrap inside their card. " +
304
+ "Gutters should step from 16px to 40px at 768px and to 32px at 1024px of the component's width. Slot text is the consumer's own markup, so the header and " +
305
+ "content paragraphs set their own `overflow-wrap`; highlight items get `overflow-wrap: anywhere` from the item class.",
306
+ },
307
+ },
308
+ },
458
309
  };
@@ -18,6 +18,16 @@ const meta: Meta<StoryArgs> = {
18
18
  args: {
19
19
  styleClassPassthrough: [],
20
20
  },
21
+ parameters: {
22
+ docs: {
23
+ description: {
24
+ component:
25
+ "Lays out the `header` slot of [PageHeroHighlights](?path=/docs/templates-pageherohighlights--docs): a `start` slot (title and description) and an optional `end` slot (actions). " +
26
+ "They stack in a narrow header and sit side by side from 768px of the header slot's own width. `.page-hero-highlights-header-end` only renders when `end` is filled. Tokens: `--page-hero-highlights-header-*`. " +
27
+ "These stories set a dark header background to show light text; the library default is light.",
28
+ },
29
+ },
30
+ },
21
31
  };
22
32
 
23
33
  export default meta;