srcdev-nuxt-components 9.6.12 → 9.6.14
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/rules/consumer/ids-from-useid.md +2 -1
- package/.claude/rules/consumer/story-register-components.md +1 -1
- package/.claude/rules/consumer/vfor-keys-from-data.md +14 -0
- package/.claude/skills/component-compliance-checklist.md +24 -1
- package/.claude/skills/component-naming.md +1 -0
- package/.claude/skills/components/banner-video.md +24 -30
- package/.claude/skills/components/content-docs.md +18 -5
- package/.claude/skills/components/display-dialog.md +16 -6
- package/.claude/skills/components/display-qr-code.md +22 -4
- package/.claude/skills/components/{display-tooltip-defined.md → display-tooltip-rich.md} +40 -42
- package/.claude/skills/components/display-tooltip.md +17 -6
- package/.claude/skills/components/eyebrow-text.md +9 -5
- package/.claude/skills/components/header-block.md +13 -5
- package/.claude/skills/components/hero-text.md +13 -9
- package/.claude/skills/components/link-text.md +8 -5
- package/.claude/skills/components/pop-over.md +17 -3
- package/.claude/skills/components/skip-links.md +16 -0
- package/.claude/skills/composable-tooltips-guide.md +4 -4
- package/.claude/skills/index.md +6 -6
- package/.claude/skills/storybook-add-story.md +5 -2
- package/.claude/skills/testing-add-playwright.md +12 -7
- package/.claude/skills/testing-add-unit-test.md +8 -0
- package/.vscode/srcdev-component-banner-video.code-snippets +38 -25
- package/.vscode/srcdev-component-content-docs.code-snippets +69 -60
- package/.vscode/srcdev-component-display-dialog.code-snippets +7 -5
- package/.vscode/srcdev-component-display-qr-code.code-snippets +12 -1
- package/.vscode/srcdev-component-display-tooltip-rich.code-snippets +50 -0
- package/.vscode/srcdev-component-display-tooltip.code-snippets +31 -0
- package/.vscode/srcdev-component-hero-text.code-snippets +21 -5
- package/.vscode/srcdev-component-link-text.code-snippets +3 -3
- package/.vscode/srcdev-component-pop-over.code-snippets +28 -0
- package/.vscode/srcdev-component-skip-links.code-snippets +1 -1
- package/app/components/01.atoms/banner-video/BannerVideo.vue +51 -19
- package/app/components/01.atoms/banner-video/CONSUMER-STYLING.md +59 -40
- package/app/components/01.atoms/banner-video/stories/BannerVideo.stories.ts +88 -1
- package/app/components/01.atoms/banner-video/tests/BannerVideo.spec.ts +127 -78
- package/app/components/01.atoms/banner-video/tests/__snapshots__/BannerVideo.spec.ts.snap +12 -4
- package/app/components/01.atoms/content-wrappers/docs-pages/CONSUMER-STYLING.md +42 -14
- package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +86 -112
- package/app/components/01.atoms/content-wrappers/docs-pages/stories/ContentDocs.stories.ts +134 -8
- package/app/components/01.atoms/content-wrappers/docs-pages/tests/ContentDocs.spec.ts +131 -65
- package/app/components/01.atoms/display-dialog/CONSUMER-STYLING.md +28 -3
- package/app/components/01.atoms/display-dialog/DisplayDialog.vue +39 -31
- package/app/components/01.atoms/display-dialog/stories/DisplayDialog.stories.ts +92 -1
- package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +87 -41
- package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +5 -1
- package/app/components/01.atoms/display-tooltip/CONSUMER-STYLING.md +70 -24
- package/app/components/01.atoms/display-tooltip/DisplayTooltip.vue +24 -12
- package/app/components/01.atoms/display-tooltip/stories/DisplayTooltip.stories.ts +89 -2
- package/app/components/01.atoms/display-tooltip/tests/DisplayTooltip.spec.ts +65 -18
- package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap +3 -3
- package/app/components/01.atoms/navigation/skip-links/CONSUMER-STYLING.md +65 -19
- package/app/components/01.atoms/navigation/skip-links/SkipLinks.vue +20 -14
- package/app/components/01.atoms/navigation/skip-links/stories/SkipLinks.stories.ts +89 -20
- package/app/components/01.atoms/navigation/skip-links/tests/SkipLinks.spec.ts +70 -10
- package/app/components/01.atoms/pop-over/CONSUMER-STYLING.md +42 -9
- package/app/components/01.atoms/pop-over/PopOver.vue +28 -12
- package/app/components/01.atoms/pop-over/stories/PopOver.stories.ts +114 -2
- package/app/components/01.atoms/pop-over/tests/PopOver.spec.ts +35 -0
- package/app/components/01.atoms/pop-over/tests/__snapshots__/PopOver.spec.ts.snap +5 -1
- package/app/components/01.atoms/qr-code/CONSUMER-STYLING.md +16 -3
- package/app/components/01.atoms/qr-code/DisplayQrCode.vue +31 -0
- package/app/components/01.atoms/qr-code/stories/DisplayQrCode.stories.ts +190 -157
- package/app/components/01.atoms/qr-code/tests/DisplayQrCode.spec.ts +58 -0
- package/app/components/01.atoms/text-blocks/eyebrow-text/CONSUMER-STYLING.md +9 -1
- package/app/components/01.atoms/text-blocks/eyebrow-text/EyebrowText.vue +5 -4
- package/app/components/01.atoms/text-blocks/eyebrow-text/playwright/eyebrow-text.playwright.ts +1 -1
- package/app/components/01.atoms/text-blocks/eyebrow-text/stories/EyebrowText.stories.ts +104 -17
- package/app/components/01.atoms/text-blocks/eyebrow-text/tests/EyebrowText.spec.ts +68 -118
- package/app/components/01.atoms/text-blocks/eyebrow-text/tests/__snapshots__/EyebrowText.spec.ts.snap +8 -8
- package/app/components/01.atoms/text-blocks/header-block/CONSUMER-STYLING.md +49 -24
- package/app/components/01.atoms/text-blocks/header-block/HeaderBlock.vue +9 -1
- package/app/components/01.atoms/text-blocks/header-block/stories/HeaderBlock.stories.ts +96 -26
- package/app/components/01.atoms/text-blocks/header-block/tests/HeaderBlock.spec.ts +64 -48
- package/app/components/01.atoms/text-blocks/header-block/tests/__snapshots__/HeaderBlock.spec.ts.snap +2 -2
- package/app/components/01.atoms/text-blocks/hero-text/CONSUMER-STYLING.md +22 -6
- package/app/components/01.atoms/text-blocks/hero-text/HeroText.vue +32 -23
- package/app/components/01.atoms/text-blocks/hero-text/playwright/hero-text.playwright.ts +1 -1
- package/app/components/01.atoms/text-blocks/hero-text/stories/HeroText.stories.ts +176 -23
- package/app/components/01.atoms/text-blocks/hero-text/tests/HeroText.spec.ts +129 -123
- package/app/components/01.atoms/text-blocks/hero-text/tests/__snapshots__/HeroText.spec.ts.snap +2 -2
- package/app/components/01.atoms/text-blocks/link-text/CONSUMER-STYLING.md +17 -0
- package/app/components/01.atoms/text-blocks/link-text/LinkText.vue +9 -3
- package/app/components/01.atoms/text-blocks/link-text/stories/LinkText.stories.ts +96 -99
- package/app/components/01.atoms/text-blocks/link-text/tests/LinkText.spec.ts +84 -93
- package/app/components/02.molecules/display-tooltip-rich/CONSUMER-STYLING.md +110 -0
- package/app/components/02.molecules/display-tooltip-rich/DisplayTooltipRich.vue +148 -0
- package/app/components/02.molecules/display-tooltip-rich/stories/DisplayTooltipRich.stories.ts +254 -0
- package/app/components/02.molecules/display-tooltip-rich/tests/DisplayTooltipRich.spec.ts +156 -0
- package/app/components/02.molecules/display-tooltip-rich/tests/__snapshots__/DisplayTooltipRich.spec.ts.snap +22 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/DeepExpandingMenu.vue +1 -0
- package/app/components/02.molecules/navigation/deep-expanding-menu/tests/__snapshots__/DeepExpandingMenu.spec.ts.snap +1 -1
- package/app/components/02.molecules/price-list/tests/__snapshots__/PriceList.spec.ts.snap +8 -8
- package/app/components/03.organisms/image-galleries/carousel-flip/CarouselFlip.vue +1 -0
- package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +15 -15
- package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +3 -3
- package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +3 -3
- package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +3 -3
- package/app/components/03.organisms/services/services-grids/tests/ServicesCardGrid.spec.ts +1 -1
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +9 -9
- package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +9 -9
- package/app/composables/tests/useTooltips.spec.ts +3 -3
- package/app/types/components/index.ts +1 -1
- package/package.json +1 -1
- package/.vscode/srcdev-component-display-tooltip-defined.code-snippets +0 -29
- package/app/components/02.molecules/display-tooltip-defined/CONSUMER-STYLING.md +0 -53
- package/app/components/02.molecules/display-tooltip-defined/DisplayTooltipDefined.vue +0 -117
- package/app/components/02.molecules/display-tooltip-defined/stories/DisplayTooltipDefined.stories.ts +0 -129
- package/app/components/02.molecules/display-tooltip-defined/tests/DisplayTooltipDefined.spec.ts +0 -85
- package/app/components/02.molecules/display-tooltip-defined/tests/__snapshots__/DisplayTooltipDefined.spec.ts.snap +0 -17
- /package/app/types/components/{display-tooltip-defined.d.ts → display-tooltip-rich.d.ts} +0 -0
|
@@ -14,7 +14,10 @@ file, scoped to a page wrapper, or per-instance via `styleClassPassthrough`.
|
|
|
14
14
|
| `--banner-video-max-height-md` | `clamp(28rem, 38vw, 56rem)` | Max height at `depth="md"` (default) |
|
|
15
15
|
| `--banner-video-max-height-lg` | `clamp(40rem, 52vw, 72rem)` | Max height at `depth="lg"` |
|
|
16
16
|
| `--banner-video-max-height-xl` | `clamp(52rem, 65vw, 90rem)` | Max height at `depth="xl"` |
|
|
17
|
-
| `--banner-video-max-height` | `clamp(28rem, 38vw, 56rem)` |
|
|
17
|
+
| `--banner-video-max-height` | `clamp(28rem, 38vw, 56rem)` | Only reached if the root has no valid `data-depth`; every typed `depth` value has its own token above |
|
|
18
|
+
|
|
19
|
+
Only the active tier's token matters: `depth="lg"` reads `--banner-video-max-height-lg` and nothing
|
|
20
|
+
else. The `vw` values in the defaults track the viewport, not the banner's container.
|
|
18
21
|
|
|
19
22
|
### Pause/play toggle button
|
|
20
23
|
|
|
@@ -23,14 +26,41 @@ file, scoped to a page wrapper, or per-instance via `styleClassPassthrough`.
|
|
|
23
26
|
| `--banner-video-toggle-size` | `3.2rem` | Width and height of the toggle button |
|
|
24
27
|
| `--banner-video-toggle-offset` | `1.2rem` | Margin from the bottom-right corner of the banner |
|
|
25
28
|
| `--banner-video-toggle-background` | `oklch(0% 0 0 / 0.4)` | Button background |
|
|
26
|
-
| `--banner-video-toggle-background-hover` | `oklch(0% 0 0 / 0.6)` | Button background on hover
|
|
27
|
-
| `--banner-video-toggle-icon-color` | `white` | Icon colour (and
|
|
29
|
+
| `--banner-video-toggle-background-hover` | `oklch(0% 0 0 / 0.6)` | Button background on hover and keyboard focus |
|
|
30
|
+
| `--banner-video-toggle-icon-color` | `white` | Icon colour (and slot text colour, via `color`) |
|
|
31
|
+
| `--banner-video-toggle-focus-outline-width` | `2px` | Focus ring width |
|
|
32
|
+
| `--banner-video-toggle-focus-outline-color` | `currentColor` (the icon colour) | Focus ring colour |
|
|
33
|
+
| `--banner-video-toggle-focus-outline-offset` | `2px` | Focus ring offset |
|
|
34
|
+
|
|
35
|
+
The toggle defaults are a dark scrim with a white icon rather than `--theme-*` colours: they sit
|
|
36
|
+
over video, whose colours the theme doesn't know.
|
|
37
|
+
|
|
38
|
+
Private tokens (not public API): `--_max-height` (the active depth tier's value), and
|
|
39
|
+
`--_aspect-ratio`, `--_align-self`, `--_justify-self`, set inline from the `aspectRatio`,
|
|
40
|
+
`verticalPosition` and `horizontalPosition` props.
|
|
41
|
+
|
|
42
|
+
## State hooks
|
|
43
|
+
|
|
44
|
+
| Hook | Meaning |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `[data-depth="xs" \| "sm" \| "md" \| "lg" \| "xl"]` | The active depth tier, on the root |
|
|
47
|
+
|
|
48
|
+
| Class | Element |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `.banner-video` | Root (tag from the `tag` prop) |
|
|
51
|
+
| `.banner-video-media` | The `<video>` |
|
|
52
|
+
| `.banner-video-fallback` | The poster `NuxtImg`, shown only under `prefers-reduced-motion: reduce` |
|
|
53
|
+
| `.banner-video-toggle` | The pause/play `InputButton` (also carries `.input-button`); hidden under reduced motion |
|
|
28
54
|
|
|
29
|
-
|
|
55
|
+
> **Changed 2026-10-18**: `.video` → `.banner-video-media`, `.fallback` → `.banner-video-fallback`
|
|
56
|
+
> and `.banner-video__toggle` → `.banner-video-toggle`, so they can't collide with consumer CSS.
|
|
57
|
+
> The toggle is now an icon-only `InputButton`: its label is visually hidden text
|
|
58
|
+
> (`.input-button-text.sr-only`, from `playLabel`/`pauseLabel`) instead of an `aria-label`. Token
|
|
59
|
+
> names are unchanged.
|
|
30
60
|
|
|
31
|
-
## Icon and
|
|
61
|
+
## Icon and label overrides: props and slots, not CSS
|
|
32
62
|
|
|
33
|
-
The toggle's icon is prop-driven
|
|
63
|
+
The toggle's icon is prop-driven:
|
|
34
64
|
|
|
35
65
|
```vue
|
|
36
66
|
<BannerVideo
|
|
@@ -41,9 +71,9 @@ The toggle's icon is prop-driven, not hardcoded:
|
|
|
41
71
|
/>
|
|
42
72
|
```
|
|
43
73
|
|
|
44
|
-
For anything beyond swapping the icon name (
|
|
45
|
-
|
|
46
|
-
|
|
74
|
+
For anything beyond swapping the icon name (a custom SVG, or an icon set the `Icon` component
|
|
75
|
+
doesn't cover), use the `toggle-icon` scoped slot, which receives `isPlaying`. Slot content is
|
|
76
|
+
clipped to the round button, so size it to `--banner-video-toggle-size`:
|
|
47
77
|
|
|
48
78
|
```vue
|
|
49
79
|
<BannerVideo src="/videos/hero.mp4" poster="/images/hero-poster.jpg">
|
|
@@ -53,16 +83,13 @@ component doesn't cover), replace it with the `toggle-icon` scoped slot, which r
|
|
|
53
83
|
</BannerVideo>
|
|
54
84
|
```
|
|
55
85
|
|
|
56
|
-
|
|
86
|
+
The button's accessible name comes from `play-label`/`pause-label` (defaults "Play background
|
|
87
|
+
video" / "Pause background video"); pass translated strings from your own i18n.
|
|
57
88
|
|
|
58
89
|
## Global theming
|
|
59
90
|
|
|
60
|
-
Create `assets/styles/setup/07.components/banner-video.css` in the consuming app and set
|
|
61
|
-
tokens on `:root`. This applies to every `BannerVideo` across the site.
|
|
62
|
-
|
|
63
91
|
```css
|
|
64
|
-
|
|
65
|
-
:root {
|
|
92
|
+
:where(html) {
|
|
66
93
|
--banner-video-max-height-md: clamp(32rem, 45vw, 64rem);
|
|
67
94
|
--banner-video-toggle-background: var(--brand-overlay);
|
|
68
95
|
--banner-video-toggle-background-hover: var(--brand-overlay-strong);
|
|
@@ -70,8 +97,6 @@ tokens on `:root`. This applies to every `BannerVideo` across the site.
|
|
|
70
97
|
}
|
|
71
98
|
```
|
|
72
99
|
|
|
73
|
-
---
|
|
74
|
-
|
|
75
100
|
## Local overrides
|
|
76
101
|
|
|
77
102
|
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
@@ -82,46 +107,40 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
82
107
|
|
|
83
108
|
### Page or section
|
|
84
109
|
|
|
85
|
-
Override tokens for a specific banner by scoping them under the page or layout wrapper.
|
|
86
|
-
No `:deep()` is required (component styles are unscoped).
|
|
87
|
-
|
|
88
110
|
```css
|
|
89
|
-
/* In the consuming page's unscoped <style> block */
|
|
90
111
|
.homepage-hero {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
--banner-video-toggle-offset: 2rem;
|
|
95
|
-
}
|
|
112
|
+
--banner-video-max-height-xl: 80rem;
|
|
113
|
+
--banner-video-toggle-size: 4rem;
|
|
114
|
+
--banner-video-toggle-offset: 2rem;
|
|
96
115
|
}
|
|
97
116
|
```
|
|
98
117
|
|
|
99
118
|
### One instance
|
|
100
119
|
|
|
101
|
-
Use sparingly — prefer global or page-scoped CSS. When a single instance needs a distinct
|
|
102
|
-
visual style, pass a modifier class:
|
|
103
|
-
|
|
104
120
|
```vue
|
|
105
121
|
<BannerVideo :style-class-passthrough="['quiet-toggle']" src="…" poster="…" />
|
|
106
122
|
```
|
|
107
123
|
|
|
108
124
|
```css
|
|
109
|
-
.
|
|
125
|
+
.quiet-toggle {
|
|
110
126
|
--banner-video-toggle-background: transparent;
|
|
111
127
|
--banner-video-toggle-background-hover: oklch(0% 0 0 / 0.3);
|
|
112
128
|
}
|
|
113
129
|
```
|
|
114
130
|
|
|
115
|
-
|
|
131
|
+
## Class passthrough
|
|
116
132
|
|
|
117
|
-
|
|
133
|
+
`:style-class-passthrough` (string or string array) adds classes to the root `.banner-video`
|
|
134
|
+
element only, never to the video, image or toggle. Use it for per-instance token overrides or
|
|
135
|
+
layout utilities (`full-bleed`, `mbe-0`).
|
|
118
136
|
|
|
119
|
-
|
|
120
|
-
token isn't set — in practice this only matters if you delete a depth tier's token without
|
|
121
|
-
replacing it. Prefer setting the specific `-{depth}` token you need.
|
|
122
|
-
- The toggle button's `border-radius` (`100vw`, i.e. a full circle) and `transition-duration`
|
|
123
|
-
(`0.2s`) are not tokenized — they're low-variance visual details rather than something
|
|
124
|
-
consuming apps are expected to reskin per-brand.
|
|
125
|
-
- `aspectRatio`, `objectFit`, `verticalPosition`, and `horizontalPosition` are props, not
|
|
126
|
-
tokens — set them directly on the component.
|
|
137
|
+
## Notes
|
|
127
138
|
|
|
139
|
+
- The toggle button's `border-radius` (`100vw`, a full circle) and transition (`0.2s`) are not
|
|
140
|
+
tokenized: low-variance details rather than something consuming apps reskin per brand.
|
|
141
|
+
- `aspectRatio`, `objectFit`, `verticalPosition` and `horizontalPosition` are props, not tokens.
|
|
142
|
+
An invalid `aspectRatio` (empty, zero, negative, not a number or `w/h` ratio) falls back to
|
|
143
|
+
`21/9`.
|
|
144
|
+
- Once the viewport is wide enough, the depth tier's `max-height` wins over `aspect-ratio`, so
|
|
145
|
+
changing `aspectRatio` can look like a no-op on desktop. Raise the active tier's token to let the
|
|
146
|
+
ratio dominate.
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
1
2
|
import BannerVideo from "../BannerVideo.vue";
|
|
3
|
+
import CanvasSwitcher from "../../canvas-switcher/CanvasSwitcher.vue";
|
|
2
4
|
import type { Meta, StoryObj } from "@nuxtjs/storybook";
|
|
5
|
+
import type { MediaCanvas } from "~/types/components";
|
|
3
6
|
|
|
4
7
|
const meta: Meta<typeof BannerVideo> = {
|
|
5
8
|
title: "Atoms/Media/BannerVideo",
|
|
@@ -75,15 +78,43 @@ const meta: Meta<typeof BannerVideo> = {
|
|
|
75
78
|
description: "Icon name for the toggle button once playback starts (any Iconify icon)",
|
|
76
79
|
table: { category: "Appearance" },
|
|
77
80
|
},
|
|
81
|
+
playLabel: {
|
|
82
|
+
control: "text",
|
|
83
|
+
description: "Toggle button label while paused (visually hidden, its accessible name). Override for localisation",
|
|
84
|
+
table: { category: "Content" },
|
|
85
|
+
},
|
|
86
|
+
pauseLabel: {
|
|
87
|
+
control: "text",
|
|
88
|
+
description: "Toggle button label while playing (visually hidden, its accessible name). Override for localisation",
|
|
89
|
+
table: { category: "Content" },
|
|
90
|
+
},
|
|
78
91
|
styleClassPassthrough: {
|
|
79
92
|
table: { disable: true },
|
|
80
93
|
},
|
|
81
94
|
},
|
|
95
|
+
decorators: [
|
|
96
|
+
// The banner is 100% of its container wide and takes its height from aspect-ratio, so a constrained wrapper stands in for a narrower viewport.
|
|
97
|
+
(story, context) => ({
|
|
98
|
+
components: { story, CanvasSwitcher },
|
|
99
|
+
setup() {
|
|
100
|
+
const canvasName = ref<MediaCanvas>(context.parameters.initialCanvas ?? "fullWidthCanvas");
|
|
101
|
+
return { canvasName };
|
|
102
|
+
},
|
|
103
|
+
template: `
|
|
104
|
+
<div style="padding: 1.2rem 1.6rem; border-block-end: 1px solid currentColor;">
|
|
105
|
+
<CanvasSwitcher v-model:canvas-name="canvasName" />
|
|
106
|
+
</div>
|
|
107
|
+
<div :class="canvasName" style="margin-inline: auto; outline: 1px dashed currentColor;">
|
|
108
|
+
<story />
|
|
109
|
+
</div>
|
|
110
|
+
`,
|
|
111
|
+
}),
|
|
112
|
+
],
|
|
82
113
|
parameters: {
|
|
83
114
|
docs: {
|
|
84
115
|
description: {
|
|
85
116
|
component:
|
|
86
|
-
"A full-width banner that plays a muted, looping mp4 video. The poster image is shown before the video loads, when the video fails to play, and whenever the user has `prefers-reduced-motion: reduce` set. The banner is sized via `aspect-ratio` with a responsive `max-height` driven by the `depth` tier (`xs` → `xl`). Each tier uses a `clamp()` scale and exposes a `--banner-video-max-height-{depth}` token for consuming pages to override. `objectFit` controls how media fills the frame. `verticalPosition` and `horizontalPosition` control the crop focal point. The pause/play toggle
|
|
117
|
+
"A full-width banner that plays a muted, looping mp4 video. The poster image is shown before the video loads, when the video fails to play, and whenever the user has `prefers-reduced-motion: reduce` set. The banner is sized via `aspect-ratio` with a responsive `max-height` driven by the `depth` tier (`xs` → `xl`). Each tier uses a `clamp()` scale and exposes a `--banner-video-max-height-{depth}` token for consuming pages to override. `objectFit` controls how media fills the frame. `verticalPosition` and `horizontalPosition` control the crop focal point. The pause/play toggle (WCAG 2.2.2) is an icon-only `InputButton`: its icon is configurable via `playIcon`/`pauseIcon`, or fully replaceable via the `toggle-icon` scoped slot, and its visually hidden label comes from `playLabel`/`pauseLabel`. An invalid `aspectRatio` (empty, zero, negative, not a number or `w/h` ratio) falls back to `21/9`.",
|
|
87
118
|
},
|
|
88
119
|
},
|
|
89
120
|
},
|
|
@@ -229,3 +260,59 @@ export const CustomToggleIconSlot: Story = {
|
|
|
229
260
|
},
|
|
230
261
|
},
|
|
231
262
|
};
|
|
263
|
+
|
|
264
|
+
export const StressTest: Story = {
|
|
265
|
+
name: "Stress Test (Worst-Case Data)",
|
|
266
|
+
args: {
|
|
267
|
+
...Default.args,
|
|
268
|
+
src: "https://example.invalid/missing-video.mp4",
|
|
269
|
+
alt: "Bevorzugtekontaktmethodefürrückfragenundterminvereinbarungen 😀 <b>not bold</b> طريقة الاتصال المفضلة",
|
|
270
|
+
playLabel: "Hintergrundvideoabspielenundweiterlaufenlassen 😀 <b>not bold</b>",
|
|
271
|
+
pauseLabel: "Hintergrundvideoanhaltenundstehenlassen",
|
|
272
|
+
},
|
|
273
|
+
render: (args) => ({
|
|
274
|
+
components: { BannerVideo },
|
|
275
|
+
setup() {
|
|
276
|
+
const lake = Default.args!;
|
|
277
|
+
return { args, lake };
|
|
278
|
+
},
|
|
279
|
+
template: `
|
|
280
|
+
<div style="display: grid; gap: 2.4rem; font-size: 1.4rem;">
|
|
281
|
+
<p style="margin: 0;">Broken video URL, long unbroken localised labels with emoji and HTML-like text, and an alt with right-to-left text. The video never loads, so its poster shows instead.</p>
|
|
282
|
+
<BannerVideo v-bind="args"></BannerVideo>
|
|
283
|
+
|
|
284
|
+
<p style="margin: 0;">Broken video and broken poster URLs: an empty frame at the banner's size, the toggle still in its corner.</p>
|
|
285
|
+
<BannerVideo
|
|
286
|
+
src="https://example.invalid/missing-video.mp4"
|
|
287
|
+
poster="https://example.invalid/missing-poster.jpg"
|
|
288
|
+
:alt="args.alt"
|
|
289
|
+
depth="sm"
|
|
290
|
+
></BannerVideo>
|
|
291
|
+
|
|
292
|
+
<p style="margin: 0;">Invalid <code>aspectRatio</code> values (<code>"abc"</code>, <code>"0"</code>, <code>"-16/9"</code>, <code>""</code>): each falls back to <code>21/9</code> instead of collapsing to the video's intrinsic size.</p>
|
|
293
|
+
<BannerVideo v-for="ratio in ['abc', '0', '-16/9', '']" :key="ratio" v-bind="lake" :aspect-ratio="ratio" depth="xs"></BannerVideo>
|
|
294
|
+
|
|
295
|
+
<p style="margin: 0;">Extreme ratios: a <code>10/1</code> sliver and a <code>1/3</code> portrait (capped by the depth tier's max-height).</p>
|
|
296
|
+
<BannerVideo v-bind="lake" aspect-ratio="10/1"></BannerVideo>
|
|
297
|
+
<BannerVideo v-bind="lake" aspect-ratio="1/3" depth="xs"></BannerVideo>
|
|
298
|
+
|
|
299
|
+
<p style="margin: 0;">Oversized <code>toggle-icon</code> slot content: clipped to the round button, never spilling over the video.</p>
|
|
300
|
+
<BannerVideo v-bind="lake" depth="xs">
|
|
301
|
+
<template #toggle-icon="{ isPlaying }">
|
|
302
|
+
<span style="font-size: 1.4rem;">{{ isPlaying ? 'Anhaltenundstehenlassen' : 'Abspielenundweiterlaufen' }}</span>
|
|
303
|
+
</template>
|
|
304
|
+
</BannerVideo>
|
|
305
|
+
</div>
|
|
306
|
+
`,
|
|
307
|
+
}),
|
|
308
|
+
parameters: {
|
|
309
|
+
initialCanvas: "mobileCanvas",
|
|
310
|
+
docs: {
|
|
311
|
+
story: { inline: false, height: "1600px" },
|
|
312
|
+
description: {
|
|
313
|
+
story:
|
|
314
|
+
"Worst-case data across nine banners. Check at every canvas width: a broken video falls back to its poster, a broken poster leaves an empty frame of the right size (not a 300x150 default box), invalid aspect ratios fall back to 21/9, the toggle stays in the bottom-right corner at every size, oversized slot content is clipped to the button, and the long labels never show on screen (they're the button's visually hidden accessible name: inspect `.input-button-text`). Under `prefers-reduced-motion: reduce` every banner shows its poster image instead, and a broken poster then shows its alt text.",
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
};
|