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
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Public token API
|
|
4
4
|
|
|
5
|
+
Private tokens (`--_anchor-name`, `--_trigger-icon-size`, `--_trigger-padding`,
|
|
6
|
+
`--_trigger-border-width`, and the script-written `--_anchor-top`, `--_anchor-right`,
|
|
7
|
+
`--_anchor-left-inverse`) are not public API.
|
|
8
|
+
|
|
9
|
+
### Trigger
|
|
10
|
+
|
|
5
11
|
| Token | Default | Controls |
|
|
6
12
|
|---|---|---|
|
|
7
13
|
| `--display-tooltip-padding-block` | `0.8rem` | Vertical padding around the trigger row |
|
|
@@ -14,10 +20,15 @@
|
|
|
14
20
|
| `--display-tooltip-trigger-border-radius` | `100vw` | Trigger button corner radius (circular by default) |
|
|
15
21
|
| `--display-tooltip-trigger-outline-width` | `0.1rem` | Trigger button focus/hover outline width |
|
|
16
22
|
| `--display-tooltip-trigger-outline-colour-hover` | `var(--theme-ring)` | Trigger button outline colour on hover/focus-visible |
|
|
17
|
-
|
|
23
|
+
|
|
24
|
+
### Popover panel
|
|
25
|
+
|
|
26
|
+
| Token | Default | Controls |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `--display-tooltip-popover-width` | `30rem` | Popover panel width (capped at `100vw - 3.2rem`) |
|
|
18
29
|
| `--display-tooltip-popover-padding` | `1.2rem` | Popover panel content padding |
|
|
19
30
|
| `--display-tooltip-popover-content-gap` | `1.2rem` | Gap between stacked children in the `tooltipContent` slot |
|
|
20
|
-
| `--display-tooltip-popover-font-size` | `1.4rem` | Popover text size (`
|
|
31
|
+
| `--display-tooltip-popover-font-size` | `1.4rem` | Popover text size (`DisplayTooltipRich` sets its own per-element sizes) |
|
|
21
32
|
| `--display-tooltip-popover-line-height` | `1.4` | Popover text line height |
|
|
22
33
|
| `--display-tooltip-popover-outline-width` | `0.1rem` | Popover panel outline (border) width |
|
|
23
34
|
| `--display-tooltip-popover-outline-colour` | `var(--slate-02)` | Popover panel outline (border) colour |
|
|
@@ -27,29 +38,61 @@
|
|
|
27
38
|
| `--display-tooltip-popover-shadow` | `0 0.4rem 1.6rem rgba(0,0,0,.12)` | Popover panel elevation shadow |
|
|
28
39
|
| `--display-tooltip-popover-offset` | `0.1rem` | Gap between the trigger and the popover panel |
|
|
29
40
|
| `--display-tooltip-popover-z-index` | `999999` | Panel stacking order in browsers without CSS anchor positioning. Ignored where the panel renders in the top layer |
|
|
30
|
-
|
|
41
|
+
|
|
42
|
+
The panel uses the neutral `--slate-*` scale rather than `--theme-*`, like `PopOver`: a tooltip
|
|
43
|
+
reads as a neutral overlay whatever the surrounding theme.
|
|
44
|
+
|
|
45
|
+
### Close button (`.display-tooltip-close-button`)
|
|
46
|
+
|
|
47
|
+
| Token | Default | Controls |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `--display-tooltip-close-button-colour` | `var(--slate-09)` | Close button text colour |
|
|
31
50
|
| `--display-tooltip-close-button-border-width` | `0.1rem` | Close button border width |
|
|
32
51
|
| `--display-tooltip-close-button-border-colour` | `var(--slate-03)` | Close button border colour |
|
|
33
52
|
| `--display-tooltip-close-button-outline-width` | `0.1rem` | Close button outline width |
|
|
34
53
|
| `--display-tooltip-close-button-outline-colour` | `transparent` | Close button outline colour at rest (no permanent ring) |
|
|
35
|
-
| `--display-tooltip-close-button-border-colour-hover` | `var(--slate-06)` | Close button border colour on hover/focus |
|
|
36
|
-
| `--display-tooltip-close-button-outline-colour-hover` | `var(--slate-06)` | Close button outline colour on hover/focus |
|
|
54
|
+
| `--display-tooltip-close-button-border-colour-hover` | `var(--slate-06)` | Close button border colour on hover/focus-visible |
|
|
55
|
+
| `--display-tooltip-close-button-outline-colour-hover` | `var(--slate-06)` | Close button outline colour on hover/focus-visible |
|
|
37
56
|
| `--display-tooltip-close-button-padding` | `0.4rem 1rem` | Close button padding |
|
|
38
57
|
|
|
58
|
+
`DisplayTooltipRich` renders this button. A plain `DisplayTooltip` gets the same styling by
|
|
59
|
+
giving its own close button the `display-tooltip-close-button` class inside `tooltipContent`.
|
|
60
|
+
|
|
61
|
+
> Changed 2026-10-18: `--display-tooltip-close-button-outline-colour` was documented but never read
|
|
62
|
+
> (the resting outline was hardcoded `transparent`); it now works. The hover styles apply on
|
|
63
|
+
> `:focus-visible` instead of `:focus`, so a mouse click no longer leaves the ring on.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## State hooks
|
|
68
|
+
|
|
69
|
+
| Hook | Element | When |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `[data-hidden]` | `.display-tooltip-trigger-button` | `hide-trigger` is set: the button collapses to zero width, leaves the tab order and is `aria-hidden` |
|
|
72
|
+
| `:popover-open` | `.display-tooltip-popover` | Panel open (Popover API) |
|
|
73
|
+
| `.display-tooltip-popover-open` | `.display-tooltip-popover` | Panel open without the Popover API (Safari 16) |
|
|
74
|
+
| `[data-placement="left"]` | `.display-tooltip-popover` | Fallback placement flipped to the left of the trigger |
|
|
75
|
+
|
|
76
|
+
Inner classes: `.display-tooltip` (root), `.display-tooltip-trigger-wrapper`,
|
|
77
|
+
`.display-tooltip-trigger-button`, `.display-tooltip-trigger-icon`, `.display-tooltip-popover`,
|
|
78
|
+
`.display-tooltip-popover-content`.
|
|
79
|
+
|
|
80
|
+
> Changed 2026-10-18: the root class was `.display-tooltip-core` and is now `.display-tooltip`, and
|
|
81
|
+
> the hidden trigger's `.hide` class is now `[data-hidden]` (a bare `.hide` collided with consumers'
|
|
82
|
+
> utility classes). Update overrides written against the old names. Token names are unchanged.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Global theming
|
|
87
|
+
|
|
39
88
|
```css
|
|
40
|
-
|
|
89
|
+
:where(html) {
|
|
41
90
|
--display-tooltip-popover-background-colour: #1a1a1a;
|
|
42
91
|
--display-tooltip-popover-text-colour: white;
|
|
43
92
|
--display-tooltip-popover-outline-colour: transparent;
|
|
44
93
|
}
|
|
45
94
|
```
|
|
46
95
|
|
|
47
|
-
Or scope to a single instance via `styleClassPassthrough`:
|
|
48
|
-
|
|
49
|
-
```vue
|
|
50
|
-
<DisplayTooltip style-class-passthrough="promo-tooltip">...</DisplayTooltip>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
96
|
---
|
|
54
97
|
|
|
55
98
|
## Local overrides
|
|
@@ -62,12 +105,24 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
62
105
|
|
|
63
106
|
---
|
|
64
107
|
|
|
108
|
+
## Class passthrough
|
|
109
|
+
|
|
110
|
+
`style-class-passthrough` adds classes to the root `.display-tooltip`, so tokens set on a
|
|
111
|
+
passthrough class reach the trigger and the panel:
|
|
112
|
+
|
|
113
|
+
```vue
|
|
114
|
+
<DisplayTooltip style-class-passthrough="promo-tooltip">...</DisplayTooltip>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
65
119
|
## Notes
|
|
66
120
|
|
|
67
121
|
- Direct children of the `tooltipContent` slot have their block margins reset to `0` (inside
|
|
68
122
|
`:where()`, so any margin you set on them still wins): the gap token is the only spacing between
|
|
69
123
|
them. Before 2026-10-05 a slotted `<p>` kept its browser `1em` margins on top of the padding.
|
|
70
|
-
|
|
124
|
+
- Long unbroken text wraps (`overflow-wrap: anywhere`) in both the trigger row and the panel, and
|
|
125
|
+
the trigger button never shrinks, so a long label stays beside it in a narrow column.
|
|
71
126
|
- The trigger button is vertically centred on the first line of `triggerContent` text, using
|
|
72
127
|
`--display-tooltip-trigger-icon-box-size`, `-padding` and `-border-width` to work out its height.
|
|
73
128
|
Give `--display-tooltip-trigger-padding` a single length (e.g. `0.4rem`): a two-value shorthand
|
|
@@ -80,15 +135,6 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
80
135
|
containing `:popover-open` is dropped whole where unsupported), isn't in the top layer (so the
|
|
81
136
|
z-index token applies), and an ancestor with `transform`, `filter` or `contain` can misplace it.
|
|
82
137
|
The trigger's `aria-expanded` tracks the open state in every browser.
|
|
83
|
-
- The `.display-tooltip-close-button` styled here is rendered by `DisplayTooltipDefined`, not this
|
|
84
|
-
component directly — `DisplayTooltip` only provides the `tooltipContent` slot, so a plain
|
|
85
|
-
`DisplayTooltip` usage supplies its own close affordance if it wants one (give it
|
|
86
|
-
`style="align-self: flex-end;"` or a class to right-align it within the content's flex column,
|
|
87
|
-
same as `DisplayTooltipDefined` does internally).
|
|
88
138
|
- 2026-09-07: default colours switched from raw `light-dark(black, white)` to the library's
|
|
89
|
-
neutral `--slate-*` scale, and
|
|
90
|
-
|
|
91
|
-
flush against the panel edges) plus the popover gained a default elevation `box-shadow`
|
|
92
|
-
(previously none) — the out-of-the-box look was flat and under-styled before this. All of these
|
|
93
|
-
remain overridable via the tokens above.
|
|
94
|
-
|
|
139
|
+
neutral `--slate-*` scale, and the panel gained default padding, a flex column with a gap, and a
|
|
140
|
+
default elevation shadow.
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
ref="rootRef"
|
|
4
|
-
class="display-tooltip
|
|
4
|
+
class="display-tooltip"
|
|
5
5
|
:class="[elementClasses]"
|
|
6
6
|
:style="{ '--_anchor-name': tooltipAnchorName }"
|
|
7
7
|
>
|
|
8
|
-
<div class="display-tooltip-trigger-wrapper
|
|
8
|
+
<div class="display-tooltip-trigger-wrapper">
|
|
9
9
|
<slot v-if="$slots.triggerContent" name="triggerContent"></slot>
|
|
10
10
|
<button
|
|
11
11
|
ref="triggerRef"
|
|
12
12
|
:popovertarget="tooltipId"
|
|
13
13
|
popovertargetaction="toggle"
|
|
14
|
+
type="button"
|
|
14
15
|
class="display-tooltip-trigger-button"
|
|
15
|
-
:
|
|
16
|
+
:data-hidden="hideTrigger ? '' : undefined"
|
|
17
|
+
:tabindex="hideTrigger ? -1 : undefined"
|
|
18
|
+
:aria-hidden="hideTrigger ? 'true' : undefined"
|
|
16
19
|
:aria-label="triggerAriaLabel"
|
|
17
20
|
:aria-expanded="isOpen"
|
|
18
21
|
@click="handleTriggerClick"
|
|
19
22
|
>
|
|
20
|
-
<Icon name="
|
|
23
|
+
<Icon :name="triggerIcon" class="display-tooltip-trigger-icon" aria-hidden="true" />
|
|
21
24
|
</button>
|
|
22
25
|
</div>
|
|
23
26
|
<div
|
|
@@ -44,6 +47,8 @@ interface Props {
|
|
|
44
47
|
hideTrigger?: boolean;
|
|
45
48
|
/** aria-label on the trigger button — override for localisation. */
|
|
46
49
|
triggerAriaLabel?: string;
|
|
50
|
+
/** Iconify name for the trigger button icon. */
|
|
51
|
+
triggerIcon?: string;
|
|
47
52
|
styleClassPassthrough?: string | string[];
|
|
48
53
|
}
|
|
49
54
|
|
|
@@ -51,10 +56,12 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
51
56
|
tooltipId: "",
|
|
52
57
|
hideTrigger: false,
|
|
53
58
|
triggerAriaLabel: "Toggle the popover",
|
|
59
|
+
triggerIcon: "fa7-solid:circle-question",
|
|
54
60
|
styleClassPassthrough: () => [],
|
|
55
61
|
});
|
|
56
62
|
|
|
57
|
-
const
|
|
63
|
+
const generatedTooltipId = `nuxt-tooltip-${useId()}`;
|
|
64
|
+
const tooltipId = computed(() => props.tooltipId || generatedTooltipId);
|
|
58
65
|
const tooltipAnchorName = `--tooltip-anchor-${useId()}`;
|
|
59
66
|
|
|
60
67
|
const rootRef = ref<HTMLElement | null>(null);
|
|
@@ -84,18 +91,22 @@ watch(
|
|
|
84
91
|
|
|
85
92
|
<style lang="css">
|
|
86
93
|
@layer components {
|
|
87
|
-
.display-tooltip
|
|
94
|
+
.display-tooltip {
|
|
88
95
|
padding-block: var(--display-tooltip-padding-block, 0.8rem);
|
|
89
96
|
position: relative;
|
|
90
97
|
|
|
91
98
|
.display-tooltip-trigger-wrapper {
|
|
92
99
|
display: inline-flex;
|
|
93
100
|
align-items: flex-start;
|
|
101
|
+
max-inline-size: 100%;
|
|
102
|
+
overflow-wrap: anywhere;
|
|
94
103
|
|
|
95
104
|
.display-tooltip-trigger-button {
|
|
96
|
-
|
|
105
|
+
flex: none;
|
|
106
|
+
margin-inline-start: var(--display-tooltip-trigger-gap, 0.8rem);
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
&[data-hidden] {
|
|
109
|
+
margin-inline-start: 0;
|
|
99
110
|
width: 0;
|
|
100
111
|
padding: 0;
|
|
101
112
|
border-width: 0;
|
|
@@ -132,7 +143,6 @@ watch(
|
|
|
132
143
|
|
|
133
144
|
&:hover {
|
|
134
145
|
cursor: pointer;
|
|
135
|
-
text-decoration: underline;
|
|
136
146
|
}
|
|
137
147
|
|
|
138
148
|
&:hover,
|
|
@@ -146,7 +156,8 @@ watch(
|
|
|
146
156
|
display: none;
|
|
147
157
|
position: absolute;
|
|
148
158
|
border: none;
|
|
149
|
-
width: var(--display-tooltip-popover-width, 30rem);
|
|
159
|
+
width: min(var(--display-tooltip-popover-width, 30rem), calc(100vw - 3.2rem));
|
|
160
|
+
overflow-wrap: anywhere;
|
|
150
161
|
|
|
151
162
|
outline: var(--display-tooltip-popover-outline-width, 0.1rem) solid
|
|
152
163
|
var(--display-tooltip-popover-outline-colour, var(--slate-02));
|
|
@@ -188,13 +199,14 @@ watch(
|
|
|
188
199
|
color: var(--display-tooltip-close-button-colour, var(--slate-09));
|
|
189
200
|
border: var(--display-tooltip-close-button-border-width, 0.1rem) solid
|
|
190
201
|
var(--display-tooltip-close-button-border-colour, var(--slate-03));
|
|
191
|
-
outline: var(--display-tooltip-close-button-outline-width, 0.1rem) solid
|
|
202
|
+
outline: var(--display-tooltip-close-button-outline-width, 0.1rem) solid
|
|
203
|
+
var(--display-tooltip-close-button-outline-colour, transparent);
|
|
192
204
|
|
|
193
205
|
font-weight: 600;
|
|
194
206
|
padding: var(--display-tooltip-close-button-padding, 0.4rem 1rem);
|
|
195
207
|
|
|
196
208
|
&:hover,
|
|
197
|
-
&:focus {
|
|
209
|
+
&:focus-visible {
|
|
198
210
|
text-decoration: underline;
|
|
199
211
|
border-color: var(--display-tooltip-close-button-border-colour-hover, var(--slate-06));
|
|
200
212
|
outline-color: var(--display-tooltip-close-button-outline-colour-hover, var(--slate-06));
|
|
@@ -12,19 +12,31 @@ const meta: Meta<typeof DisplayTooltip> = {
|
|
|
12
12
|
},
|
|
13
13
|
hideTrigger: {
|
|
14
14
|
control: "boolean",
|
|
15
|
-
description: "Visually hides the trigger button
|
|
15
|
+
description: "Visually hides the trigger button and removes it from the tab order and accessibility tree. It stays in the DOM as the panel's anchor, for a panel opened by your own `popovertarget` button.",
|
|
16
|
+
table: { category: "Basic" },
|
|
17
|
+
},
|
|
18
|
+
triggerAriaLabel: {
|
|
19
|
+
control: "text",
|
|
20
|
+
description: "aria-label on the trigger button (override for localisation)",
|
|
21
|
+
table: { category: "Accessibility" },
|
|
22
|
+
},
|
|
23
|
+
triggerIcon: {
|
|
24
|
+
control: "text",
|
|
25
|
+
description: "Iconify name for the trigger button icon",
|
|
16
26
|
table: { category: "Basic" },
|
|
17
27
|
},
|
|
18
28
|
styleClassPassthrough: { table: { disable: true } },
|
|
19
29
|
},
|
|
20
30
|
args: {
|
|
21
31
|
hideTrigger: false,
|
|
32
|
+
triggerAriaLabel: "Toggle the popover",
|
|
33
|
+
triggerIcon: "fa7-solid:circle-question",
|
|
22
34
|
},
|
|
23
35
|
parameters: {
|
|
24
36
|
docs: {
|
|
25
37
|
description: {
|
|
26
38
|
component:
|
|
27
|
-
"A trigger button that opens an anchor-positioned popover panel, built on the native Popover API and CSS anchor
|
|
39
|
+
"A trigger button that opens an anchor-positioned popover panel, built on the native Popover API and CSS anchor positioning (with fallbacks for Safari 16-18). Slots: `triggerContent` (a label before the button) and `tooltipContent` (the panel, with a scoped `close()`). Props set the trigger's icon (`triggerIcon`) and accessible name (`triggerAriaLabel`). DisplayTooltipRich builds on this with a structured title, body and close button: see its stories.",
|
|
28
40
|
},
|
|
29
41
|
},
|
|
30
42
|
},
|
|
@@ -92,3 +104,78 @@ export const CustomColours: Story = {
|
|
|
92
104
|
`,
|
|
93
105
|
}),
|
|
94
106
|
};
|
|
107
|
+
|
|
108
|
+
export const StressTest: Story = {
|
|
109
|
+
name: "Stress Test (Worst-Case Data)",
|
|
110
|
+
args: {
|
|
111
|
+
triggerAriaLabel: "Weitereinformationenzudieserangabeanzeigenundausblenden 😀",
|
|
112
|
+
},
|
|
113
|
+
render: (args) => ({
|
|
114
|
+
components: { DisplayTooltip },
|
|
115
|
+
setup() {
|
|
116
|
+
return { args };
|
|
117
|
+
},
|
|
118
|
+
template: `
|
|
119
|
+
<div style="display: grid; gap: 2.4rem; padding: 2.4rem; font-size: 1.4rem;">
|
|
120
|
+
<p style="margin: 0;">A long unbroken label in a narrow column: the label wraps and the button stays beside its first line.</p>
|
|
121
|
+
<div style="inline-size: 16rem; outline: 0.1rem dashed currentColor;">
|
|
122
|
+
<DisplayTooltip v-bind="args">
|
|
123
|
+
<template #triggerContent>
|
|
124
|
+
<span>Lieferkostenundversandbedingungenfürauslandsbestellungen</span>
|
|
125
|
+
</template>
|
|
126
|
+
<template #tooltipContent>
|
|
127
|
+
<p>Short content.</p>
|
|
128
|
+
</template>
|
|
129
|
+
</DisplayTooltip>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<p style="margin: 0;">Long unbroken popover content with emoji, HTML-like text and right-to-left text: the panel stays within the viewport width and wraps.</p>
|
|
133
|
+
<DisplayTooltip v-bind="args">
|
|
134
|
+
<template #triggerContent>
|
|
135
|
+
<span>😀 <b>not bold</b></span>
|
|
136
|
+
</template>
|
|
137
|
+
<template #tooltipContent>
|
|
138
|
+
<p>https://example.invalid/a/very/long/unbroken/url/that/never/offers/a/break/opportunity/anywhere</p>
|
|
139
|
+
<p>Kostenloselieferungbeibestellungenübereinenbestimmtenmindestbestellwert 😀</p>
|
|
140
|
+
<p><script>alert("x")</script> renders as text.</p>
|
|
141
|
+
<p dir="rtl">التوصيل مجاني للطلبات التي تزيد عن خمسين جنيهًا.</p>
|
|
142
|
+
</template>
|
|
143
|
+
</DisplayTooltip>
|
|
144
|
+
|
|
145
|
+
<p style="margin: 0;">Many paragraphs: the panel grows to its content's height.</p>
|
|
146
|
+
<DisplayTooltip v-bind="args">
|
|
147
|
+
<template #tooltipContent>
|
|
148
|
+
<p v-for="n in 8" :key="n">Paragraph {{ n }}: Lorem ipsum odor amet, consectetuer adipiscing elit.</p>
|
|
149
|
+
</template>
|
|
150
|
+
</DisplayTooltip>
|
|
151
|
+
|
|
152
|
+
<p style="margin: 0;">Empty <code>tooltipContent</code> and a single-character label: the panel is just its padding.</p>
|
|
153
|
+
<DisplayTooltip v-bind="args">
|
|
154
|
+
<template #triggerContent><span>x</span></template>
|
|
155
|
+
<template #tooltipContent></template>
|
|
156
|
+
</DisplayTooltip>
|
|
157
|
+
|
|
158
|
+
<p style="margin: 0;">A broken <code>triggerIcon</code> name: the button keeps its size and stays focusable.</p>
|
|
159
|
+
<DisplayTooltip v-bind="args" trigger-icon="not-a-set:not-an-icon">
|
|
160
|
+
<template #tooltipContent><p>Opened from an empty button.</p></template>
|
|
161
|
+
</DisplayTooltip>
|
|
162
|
+
|
|
163
|
+
<p style="margin: 0;">Right-aligned at the edge of the canvas: the panel flips to the left of the trigger.</p>
|
|
164
|
+
<div style="display: flex; justify-content: flex-end;">
|
|
165
|
+
<DisplayTooltip v-bind="args">
|
|
166
|
+
<template #tooltipContent><p>Flipped to fit.</p></template>
|
|
167
|
+
</DisplayTooltip>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
`,
|
|
171
|
+
}),
|
|
172
|
+
parameters: {
|
|
173
|
+
docs: {
|
|
174
|
+
story: { inline: false, height: "900px" },
|
|
175
|
+
description: {
|
|
176
|
+
story:
|
|
177
|
+
"Worst-case content. Open each tooltip and check: long unbroken labels wrap without pushing the button out of a narrow column, panel content wraps and the panel never gets wider than the viewport, HTML-like text renders as text, an empty panel collapses to its padding, a broken icon name leaves an empty but focusable button, and a trigger at the right edge opens its panel to the left. Tab through: every trigger is reachable and shows a focus ring. The long `triggerAriaLabel` is only the buttons' accessible name, so it never shows on screen.",
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
@@ -1,69 +1,116 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
3
3
|
import DisplayTooltip from "../DisplayTooltip.vue";
|
|
4
4
|
|
|
5
|
+
const mounted: { unmount: () => void }[] = [];
|
|
6
|
+
|
|
7
|
+
const mountTooltip = async (options: Parameters<typeof mountSuspended<typeof DisplayTooltip>>[1] = {}) => {
|
|
8
|
+
const wrapper = await mountSuspended(DisplayTooltip, options);
|
|
9
|
+
mounted.push(wrapper);
|
|
10
|
+
return wrapper;
|
|
11
|
+
};
|
|
12
|
+
|
|
5
13
|
describe("DisplayTooltip", () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
mounted.splice(0).forEach((wrapper) => wrapper.unmount());
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
6
19
|
it("mounts without error", async () => {
|
|
7
|
-
const wrapper = await
|
|
20
|
+
const wrapper = await mountTooltip();
|
|
8
21
|
expect(wrapper.vm).toBeTruthy();
|
|
9
22
|
});
|
|
10
23
|
|
|
11
24
|
it("renders correct HTML structure", async () => {
|
|
12
|
-
const wrapper = await
|
|
25
|
+
const wrapper = await mountTooltip({ props: { tooltipId: "fixed-id" } });
|
|
13
26
|
expect(wrapper.html()).toMatchSnapshot();
|
|
14
27
|
});
|
|
15
28
|
|
|
16
29
|
it("generates a tooltip id when none is provided", async () => {
|
|
17
|
-
const wrapper = await
|
|
30
|
+
const wrapper = await mountTooltip();
|
|
18
31
|
const button = wrapper.find(".display-tooltip-trigger-button");
|
|
19
32
|
expect(button.attributes("popovertarget")).toMatch(/^nuxt-tooltip-/);
|
|
20
33
|
});
|
|
21
34
|
|
|
22
35
|
it("uses the given tooltipId", async () => {
|
|
23
|
-
const wrapper = await
|
|
36
|
+
const wrapper = await mountTooltip({ props: { tooltipId: "my-tooltip" } });
|
|
24
37
|
const button = wrapper.find(".display-tooltip-trigger-button");
|
|
25
38
|
expect(button.attributes("popovertarget")).toBe("my-tooltip");
|
|
26
39
|
expect(wrapper.find(".display-tooltip-popover").attributes("id")).toBe("my-tooltip");
|
|
27
40
|
});
|
|
28
41
|
|
|
29
42
|
it("does not render triggerContent slot content when unused", async () => {
|
|
30
|
-
const wrapper = await
|
|
43
|
+
const wrapper = await mountTooltip();
|
|
31
44
|
expect(wrapper.find(".slot-trigger").exists()).toBe(false);
|
|
32
45
|
});
|
|
33
46
|
|
|
34
47
|
it("renders the triggerContent slot", async () => {
|
|
35
|
-
const wrapper = await
|
|
48
|
+
const wrapper = await mountTooltip({
|
|
36
49
|
slots: { triggerContent: "<span class='slot-trigger'>Label</span>" },
|
|
37
50
|
});
|
|
38
51
|
expect(wrapper.find(".slot-trigger").text()).toBe("Label");
|
|
39
52
|
});
|
|
40
53
|
|
|
41
54
|
it("renders the tooltipContent slot", async () => {
|
|
42
|
-
const wrapper = await
|
|
55
|
+
const wrapper = await mountTooltip({
|
|
43
56
|
slots: { tooltipContent: "<p class='slot-content'>Helpful info</p>" },
|
|
44
57
|
});
|
|
45
58
|
expect(wrapper.find(".display-tooltip-popover .slot-content").text()).toBe("Helpful info");
|
|
46
59
|
});
|
|
47
60
|
|
|
48
|
-
it("
|
|
49
|
-
const wrapper = await
|
|
50
|
-
|
|
61
|
+
it("hides the trigger from view, the tab order and the accessibility tree when hideTrigger is true", async () => {
|
|
62
|
+
const wrapper = await mountTooltip({ props: { hideTrigger: true } });
|
|
63
|
+
const button = wrapper.find(".display-tooltip-trigger-button");
|
|
64
|
+
expect(button.attributes("data-hidden")).toBeDefined();
|
|
65
|
+
expect(button.attributes("tabindex")).toBe("-1");
|
|
66
|
+
expect(button.attributes("aria-hidden")).toBe("true");
|
|
51
67
|
});
|
|
52
68
|
|
|
53
|
-
it("
|
|
54
|
-
const wrapper = await
|
|
55
|
-
|
|
69
|
+
it("leaves the trigger visible and focusable by default", async () => {
|
|
70
|
+
const wrapper = await mountTooltip();
|
|
71
|
+
const button = wrapper.find(".display-tooltip-trigger-button");
|
|
72
|
+
expect(button.attributes("data-hidden")).toBeUndefined();
|
|
73
|
+
expect(button.attributes("tabindex")).toBeUndefined();
|
|
74
|
+
expect(button.attributes("aria-hidden")).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("renders the trigger as type=\"button\" so it never submits a surrounding form", async () => {
|
|
78
|
+
const wrapper = await mountTooltip();
|
|
79
|
+
expect(wrapper.find(".display-tooltip-trigger-button").attributes("type")).toBe("button");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("uses the triggerIcon prop for the trigger icon", async () => {
|
|
83
|
+
const wrapper = await mountTooltip({ props: { triggerIcon: "lucide:info" } });
|
|
84
|
+
expect(wrapper.find(".display-tooltip-trigger-icon").classes().join(" ")).toContain("lucide:info");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("uses triggerAriaLabel for the trigger's accessible name", async () => {
|
|
88
|
+
const wrapper = await mountTooltip({ props: { triggerAriaLabel: "Plus d'informations" } });
|
|
89
|
+
expect(wrapper.find(".display-tooltip-trigger-button").attributes("aria-label")).toBe("Plus d'informations");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("keeps the generated id stable and follows tooltipId changes", async () => {
|
|
93
|
+
const wrapper = await mountTooltip();
|
|
94
|
+
const button = wrapper.find(".display-tooltip-trigger-button");
|
|
95
|
+
const generated = button.attributes("popovertarget");
|
|
96
|
+
|
|
97
|
+
await wrapper.setProps({ tooltipId: "custom" });
|
|
98
|
+
expect(button.attributes("popovertarget")).toBe("custom");
|
|
99
|
+
expect(wrapper.find(".display-tooltip-popover").attributes("id")).toBe("custom");
|
|
100
|
+
|
|
101
|
+
await wrapper.setProps({ tooltipId: "" });
|
|
102
|
+
expect(button.attributes("popovertarget")).toBe(generated);
|
|
56
103
|
});
|
|
57
104
|
|
|
58
105
|
it("applies styleClassPassthrough classes", async () => {
|
|
59
|
-
const wrapper = await
|
|
106
|
+
const wrapper = await mountTooltip({
|
|
60
107
|
props: { styleClassPassthrough: ["custom-class"] },
|
|
61
108
|
});
|
|
62
109
|
expect(wrapper.classes()).toContain("custom-class");
|
|
63
110
|
});
|
|
64
111
|
|
|
65
112
|
it("resets classes when styleClassPassthrough prop changes", async () => {
|
|
66
|
-
const wrapper = await
|
|
113
|
+
const wrapper = await mountTooltip({
|
|
67
114
|
props: { styleClassPassthrough: ["initial-class"] },
|
|
68
115
|
});
|
|
69
116
|
expect(wrapper.classes()).toContain("initial-class");
|
|
@@ -83,7 +130,7 @@ describe("DisplayTooltip", () => {
|
|
|
83
130
|
});
|
|
84
131
|
|
|
85
132
|
it("opens and closes from the trigger", async () => {
|
|
86
|
-
const wrapper = await
|
|
133
|
+
const wrapper = await mountTooltip();
|
|
87
134
|
const trigger = wrapper.find(".display-tooltip-trigger-button");
|
|
88
135
|
const popover = wrapper.find(".display-tooltip-popover");
|
|
89
136
|
|
|
@@ -97,7 +144,7 @@ describe("DisplayTooltip", () => {
|
|
|
97
144
|
});
|
|
98
145
|
|
|
99
146
|
it("passes a close function to the tooltipContent slot", async () => {
|
|
100
|
-
const wrapper = await
|
|
147
|
+
const wrapper = await mountTooltip({
|
|
101
148
|
slots: {
|
|
102
149
|
tooltipContent: `<template #tooltipContent="{ close }"><button class="custom-close" @click="close">Done</button></template>`,
|
|
103
150
|
},
|
package/app/components/01.atoms/display-tooltip/tests/__snapshots__/DisplayTooltip.spec.ts.snap
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
3
|
exports[`DisplayTooltip > renders correct HTML structure 1`] = `
|
|
4
|
-
"<div class="display-tooltip
|
|
5
|
-
<div class="display-tooltip-trigger-wrapper
|
|
6
|
-
<!--v-if--><button popovertarget="fixed-id" popovertargetaction="toggle" class="display-tooltip-trigger-button" aria-label="Toggle the popover" aria-expanded="false"><span class="iconify i-fa7-solid:circle-question display-tooltip-trigger-icon" aria-hidden="true"></span></button>
|
|
4
|
+
"<div class="display-tooltip" style="--_anchor-name: --tooltip-anchor-v-0-1;">
|
|
5
|
+
<div class="display-tooltip-trigger-wrapper">
|
|
6
|
+
<!--v-if--><button popovertarget="fixed-id" popovertargetaction="toggle" type="button" class="display-tooltip-trigger-button" aria-label="Toggle the popover" aria-expanded="false"><span class="iconify i-fa7-solid:circle-question display-tooltip-trigger-icon" aria-hidden="true"></span></button>
|
|
7
7
|
</div>
|
|
8
8
|
<div id="fixed-id" popover="" class="display-tooltip-popover">
|
|
9
9
|
<div class="display-tooltip-popover-content"></div>
|
|
@@ -2,23 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
## Public token API
|
|
4
4
|
|
|
5
|
+
No private `--_` tokens.
|
|
6
|
+
|
|
7
|
+
### Panel (`.skip-links__nav`)
|
|
8
|
+
|
|
5
9
|
| Token | Default | Controls |
|
|
6
10
|
|---|---|---|
|
|
7
|
-
| `--skip-links-z-index` | `
|
|
11
|
+
| `--skip-links-z-index` | `999999` | Stacking order of the revealed panel |
|
|
8
12
|
| `--skip-links-gap` | `0.2rem` | Gap between stacked skip links |
|
|
9
|
-
| `--skip-links-background-colour` | `
|
|
10
|
-
| `--skip-links-border-colour` | `
|
|
11
|
-
| `--skip-links-border-width` | `1px` |
|
|
12
|
-
| `--skip-links-padding` | `0.2rem` |
|
|
13
|
-
| `--skip-links-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
|
18
|
-
|
|
13
|
+
| `--skip-links-background-colour` | `var(--theme-surface-inverted)` | Panel background |
|
|
14
|
+
| `--skip-links-border-colour` | `var(--theme-on-surface)` | Panel border colour |
|
|
15
|
+
| `--skip-links-border-width` | `1px` | Panel border width |
|
|
16
|
+
| `--skip-links-padding` | `0.2rem` | Panel padding |
|
|
17
|
+
| `--skip-links-transition-duration` | `0.3s` | Duration of the reveal transition (off under `prefers-reduced-motion`) |
|
|
18
|
+
|
|
19
|
+
### Links (`.skip-links__link`)
|
|
20
|
+
|
|
21
|
+
| Token | Default | Controls |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `--skip-links-text-colour` | `var(--theme-on-surface)` | Link text colour |
|
|
24
|
+
| `--skip-links-link-padding-block` | `0.8rem` | Link vertical padding |
|
|
25
|
+
| `--skip-links-link-padding-inline` | `1.2rem` | Link horizontal padding |
|
|
26
|
+
| `--skip-links-focus-outline-width` | `2px` | Focus outline width |
|
|
27
|
+
| `--skip-links-focus-outline-colour` | `var(--theme-on-surface)` | Focus outline colour |
|
|
28
|
+
|
|
29
|
+
> Changed 2026-10-18: the colours used to be hardcoded `black` (background) and `white` (text,
|
|
30
|
+
> border, focus ring). They now default to the darkest theme step (`--theme-surface-inverted`) and
|
|
31
|
+
> `--theme-on-surface`, keeping the high contrast while following the site's `data-theme` palette.
|
|
32
|
+
> Set the four colour tokens to `black`/`white` to get the old look back. `--skip-links-z-index`
|
|
33
|
+
> went from `1000` to `999999`, the library's overlay default, so the panel shows above sticky
|
|
34
|
+
> headers.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## State hooks
|
|
39
|
+
|
|
40
|
+
| Hook | Element | When |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `:focus-within` | `.skip-links__nav` | A skip link has focus: the panel is revealed |
|
|
43
|
+
|
|
44
|
+
Inner classes: `.skip-links` (root), `.skip-links__home` (only when the `homeLink` slot is used),
|
|
45
|
+
`.skip-links__nav`, `.skip-links__link`.
|
|
46
|
+
|
|
47
|
+
The panel is hidden with `opacity`, `transform` and `pointer-events`, not `display` or
|
|
48
|
+
`visibility`, so the links stay in the tab order. It's positioned below the root
|
|
49
|
+
(`top: 100%; inset-inline-start: 0`), so in a right-to-left page it drops from the right edge. It
|
|
50
|
+
sizes to its longest link but never exceeds the viewport width (`100vw - 3.2rem`); longer labels
|
|
51
|
+
wrap. The `<nav>` isn't rendered at all when no link has both an `href` and a `label`.
|
|
52
|
+
|
|
53
|
+
> Changed 2026-10-18: links used to be `white-space: nowrap`, so a long label ran off-screen, and
|
|
54
|
+
> the panel was anchored with a physical `left: 0`.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Global theming
|
|
19
59
|
|
|
20
60
|
```css
|
|
21
|
-
|
|
61
|
+
:where(html) {
|
|
22
62
|
--skip-links-background-colour: #111;
|
|
23
63
|
--skip-links-border-colour: #ffd700;
|
|
24
64
|
--skip-links-focus-outline-colour: #ffd700;
|
|
@@ -27,13 +67,6 @@
|
|
|
27
67
|
|
|
28
68
|
---
|
|
29
69
|
|
|
30
|
-
## Links are prop-driven
|
|
31
|
-
|
|
32
|
-
The set of skip links is provided via the `links` prop (`{ href, label }[]`), not slots — see the
|
|
33
|
-
skill doc for the default value and localisation notes.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
70
|
## Local overrides
|
|
38
71
|
|
|
39
72
|
Set the tokens above on an element you own (a page or section class, or a class added with
|
|
@@ -42,3 +75,16 @@ Set the tokens above on an element you own (a page or section class, or a class
|
|
|
42
75
|
`<style scoped>`, tokens set on your element still work, but selectors that reach inside the component need
|
|
43
76
|
`:deep()`. Patterns and examples: `.claude/skills/component-local-style-override.md`.
|
|
44
77
|
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Class passthrough
|
|
81
|
+
|
|
82
|
+
`style-class-passthrough` adds classes to the root `.skip-links`, so tokens set on a passthrough
|
|
83
|
+
class reach the panel and the links.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Notes
|
|
88
|
+
|
|
89
|
+
- The links are prop-driven (`links: { href, label }[]`), not slots: see the skill doc for the
|
|
90
|
+
default value and localisation.
|