srcdev-nuxt-components 9.2.11 → 9.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/check-component-update.sh +66 -0
- package/.claude/settings.json +1 -1
- package/.claude/skills/component-local-style-override.md +45 -0
- package/.claude/skills/components/accordian-core.md +26 -5
- package/.claude/skills/components/expanding-panel-classic.md +221 -0
- package/.claude/skills/components/expanding-panel.md +8 -8
- package/.claude/skills/components/navigation-items.md +51 -0
- package/.claude/skills/components/responsive-header.md +161 -0
- package/.claude/skills/components/site-header.md +98 -0
- package/.claude/skills/index.md +6 -1
- package/.claude/skills/page-transitions.md +116 -0
- package/.claude/skills/theming-form-geometry-tokens.md +12 -0
- package/.claude/skills/theming-override-default.md +14 -0
- package/.claude/skills/theming-partial-override.md +17 -0
- package/.vscode/srcdev-component-accordian-core.code-snippets +74 -0
- package/.vscode/srcdev-component-expanding-panel-classic.code-snippets +121 -0
- package/.vscode/srcdev-component-expanding-panel.code-snippets +1 -3
- package/.vscode/srcdev-component-responsive-header.code-snippets +46 -0
- package/.vscode/srcdev-component-site-header.code-snippets +74 -0
- package/app/app.config.ts +1 -1
- package/app/assets/styles/setup/01.config/index.css +2 -2
- package/app/assets/styles/setup/02.colours/index.css +8 -8
- package/app/assets/styles/setup/03.theming/index.css +6 -6
- package/app/assets/styles/setup/04.elements/forms/index.css +2 -2
- package/app/assets/styles/setup/04.elements/index.css +1 -1
- package/app/assets/styles/setup/05.typography/01.tokens/index.css +3 -3
- package/app/assets/styles/setup/05.typography/02.utility-classes/index.css +6 -6
- package/app/assets/styles/setup/05.typography/index.css +2 -2
- package/app/assets/styles/setup/06.utility-classes/animations/index.css +4 -4
- package/app/assets/styles/setup/06.utility-classes/index.css +3 -3
- package/app/assets/styles/setup/06.utility-classes/spacing/index.css +3 -3
- package/app/assets/styles/setup/a11y/index.css +1 -1
- package/app/assets/styles/setup/index.css +7 -7
- package/app/components/01.atoms/content-wrappers/docs-pages/tests/ContentDocs.spec.ts +3 -1
- package/app/components/02.molecules/alert-masked-content/tests/AlertMaskedContent.spec.ts +9 -5
- package/app/components/02.molecules/expandable/accordian/AccordianCore.vue +19 -11
- package/app/components/02.molecules/expandable/accordian/CONSUMER-STYLING.md +93 -0
- package/app/components/02.molecules/expandable/accordian/stories/AccordianCore.stories.ts +31 -0
- package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +31 -0
- package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +7 -21
- package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +10 -10
- package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +35 -37
- package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts +5 -6
- package/app/components/02.molecules/expandable/expanding-panel/tests/ExpandingPanel.spec.ts +6 -0
- package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +5 -13
- package/app/components/02.molecules/expandable/expanding-panel-classic/CONSUMER-STYLING.md +103 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue +191 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/stories/ExpandingPanelClassic.stories.ts +293 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/ExpandingPanelClassic.spec.ts +514 -0
- package/app/components/02.molecules/expandable/expanding-panel-classic/tests/__snapshots__/ExpandingPanelClassic.spec.ts.snap +59 -0
- package/app/components/02.molecules/navigation/site-navigation/tests/SiteNavigation.spec.ts +9 -5
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +9 -5
- package/app/components/03.organisms/image-galleries/slider-gallery/tests/SliderGallery.spec.ts +5 -1
- package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +66 -0
- package/app/components/{responsive-header → 03.organisms/responsive-header}/NavigationItems.vue +21 -28
- package/app/components/{responsive-header → 03.organisms/responsive-header}/ResponsiveHeader.vue +141 -43
- package/app/components/03.organisms/responsive-header/stories/NavigationItems.stories.ts +80 -0
- package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +122 -0
- package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +155 -0
- package/app/components/03.organisms/responsive-header/tests/ResponsiveHeader.spec.ts +319 -0
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +34 -0
- package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +72 -0
- package/app/components/03.organisms/site-header/CONSUMER-STYLING.md +60 -0
- package/app/components/03.organisms/site-header/SiteHeader.vue +96 -0
- package/app/components/03.organisms/site-header/stories/SiteHeader.stories.ts +135 -0
- package/app/components/03.organisms/site-header/tests/SiteHeader.spec.ts +103 -0
- package/app/components/03.organisms/site-header/tests/__snapshots__/SiteHeader.spec.ts.snap +15 -0
- package/app/components/alert-mask/tests/AlertMaskCore.spec.ts +9 -5
- package/app/composables/tests/useContainerBreakpoints.spec.ts +27 -7
- package/app/composables/tests/useWhatsApp.spec.ts +10 -4
- package/app/layouts/default.vue +64 -67
- package/nuxt.config.ts +9 -0
- package/package.json +29 -21
|
@@ -8,6 +8,7 @@ interface AccordianCoreInstance extends ComponentPublicInstance {
|
|
|
8
8
|
name: string | undefined;
|
|
9
9
|
itemCount: number;
|
|
10
10
|
animationDuration: number;
|
|
11
|
+
variant: "modern" | "classic";
|
|
11
12
|
styleClassPassthrough: string | string[];
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -92,6 +93,35 @@ describe("AccordianCore", () => {
|
|
|
92
93
|
expect(wrapper.findAllComponents({ name: "ExpandingPanel" })).toHaveLength(0);
|
|
93
94
|
});
|
|
94
95
|
|
|
96
|
+
// ─── variant ──────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
it("renders ExpandingPanel by default (variant: modern)", async () => {
|
|
99
|
+
const wrapper = await mountSuspended(AccordianCore, {
|
|
100
|
+
props: { itemCount: 2 },
|
|
101
|
+
});
|
|
102
|
+
expect(wrapper.findAllComponents({ name: "ExpandingPanel" })).toHaveLength(2);
|
|
103
|
+
expect(wrapper.findAllComponents({ name: "ExpandingPanelClassic" })).toHaveLength(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("renders ExpandingPanelClassic when variant is classic", async () => {
|
|
107
|
+
const wrapper = await mountSuspended(AccordianCore, {
|
|
108
|
+
props: { itemCount: 2, variant: "classic" },
|
|
109
|
+
});
|
|
110
|
+
expect(wrapper.findAllComponents({ name: "ExpandingPanelClassic" })).toHaveLength(2);
|
|
111
|
+
expect(wrapper.findAllComponents({ name: "ExpandingPanel" })).toHaveLength(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("forwards props identically to ExpandingPanelClassic when variant is classic", async () => {
|
|
115
|
+
const wrapper = await mountSuspended(AccordianCore, {
|
|
116
|
+
props: { itemCount: 2, name: "classic-accordian", animationDuration: 500, variant: "classic" },
|
|
117
|
+
});
|
|
118
|
+
wrapper.findAllComponents({ name: "ExpandingPanelClassic" }).forEach((panel) => {
|
|
119
|
+
expect(panel.props("name")).toBe("classic-accordian");
|
|
120
|
+
expect(panel.props("animationDuration")).toBe(500);
|
|
121
|
+
expect(panel.props("styleClassPassthrough")).toEqual(["accordian-item"]);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
95
125
|
// ─── Props forwarded to ExpandingPanel ───────────────────────────────────
|
|
96
126
|
|
|
97
127
|
it("forwards animationDuration to every ExpandingPanel", async () => {
|
|
@@ -137,6 +167,7 @@ describe("AccordianCore", () => {
|
|
|
137
167
|
expect(vm.name).toBeUndefined();
|
|
138
168
|
expect(vm.itemCount).toBe(0);
|
|
139
169
|
expect(vm.animationDuration).toBe(300);
|
|
170
|
+
expect(vm.variant).toBe("modern");
|
|
140
171
|
expect(vm.styleClassPassthrough).toEqual([]);
|
|
141
172
|
});
|
|
142
173
|
|
|
@@ -8,10 +8,8 @@ exports[`AccordianCore > renders correct HTML structure with all props set 1`] =
|
|
|
8
8
|
<div class="label-wrapper"></div>
|
|
9
9
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
10
10
|
</summary>
|
|
11
|
+
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"></div>
|
|
11
12
|
</details>
|
|
12
|
-
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region">
|
|
13
|
-
<div class="inner"></div>
|
|
14
|
-
</div>
|
|
15
13
|
</div>
|
|
16
14
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
17
15
|
<details class="expanding-panel-details" name="snapshot-accordian">
|
|
@@ -19,10 +17,8 @@ exports[`AccordianCore > renders correct HTML structure with all props set 1`] =
|
|
|
19
17
|
<div class="label-wrapper"></div>
|
|
20
18
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
21
19
|
</summary>
|
|
20
|
+
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"></div>
|
|
22
21
|
</details>
|
|
23
|
-
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region">
|
|
24
|
-
<div class="inner"></div>
|
|
25
|
-
</div>
|
|
26
22
|
</div>
|
|
27
23
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
28
24
|
<details class="expanding-panel-details" name="snapshot-accordian">
|
|
@@ -30,10 +26,8 @@ exports[`AccordianCore > renders correct HTML structure with all props set 1`] =
|
|
|
30
26
|
<div class="label-wrapper"></div>
|
|
31
27
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
32
28
|
</summary>
|
|
29
|
+
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"></div>
|
|
33
30
|
</details>
|
|
34
|
-
<div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region">
|
|
35
|
-
<div class="inner"></div>
|
|
36
|
-
</div>
|
|
37
31
|
</div>
|
|
38
32
|
</div>"
|
|
39
33
|
`;
|
|
@@ -46,10 +40,8 @@ exports[`AccordianCore > renders correct HTML structure with default props 1`] =
|
|
|
46
40
|
<div class="label-wrapper"></div>
|
|
47
41
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
48
42
|
</summary>
|
|
43
|
+
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region"></div>
|
|
49
44
|
</details>
|
|
50
|
-
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region">
|
|
51
|
-
<div class="inner"></div>
|
|
52
|
-
</div>
|
|
53
45
|
</div>
|
|
54
46
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
55
47
|
<details class="expanding-panel-details" name="v-0-0-1">
|
|
@@ -57,10 +49,8 @@ exports[`AccordianCore > renders correct HTML structure with default props 1`] =
|
|
|
57
49
|
<div class="label-wrapper"></div>
|
|
58
50
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
59
51
|
</summary>
|
|
52
|
+
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region"></div>
|
|
60
53
|
</details>
|
|
61
|
-
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region">
|
|
62
|
-
<div class="inner"></div>
|
|
63
|
-
</div>
|
|
64
54
|
</div>
|
|
65
55
|
</div>"
|
|
66
56
|
`;
|
|
@@ -73,10 +63,8 @@ exports[`AccordianCore > renders correct HTML structure with populated slots 1`]
|
|
|
73
63
|
<div class="label-wrapper"><span>Summary 0</span></div>
|
|
74
64
|
<div class="icon-wrapper"><span>Icon 0</span></div>
|
|
75
65
|
</summary>
|
|
66
|
+
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region"><span>Content 0</span></div>
|
|
76
67
|
</details>
|
|
77
|
-
<div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region">
|
|
78
|
-
<div class="inner"><span>Content 0</span></div>
|
|
79
|
-
</div>
|
|
80
68
|
</div>
|
|
81
69
|
<div class="expanding-panel accordian-item" icon-size="medium">
|
|
82
70
|
<details class="expanding-panel-details" name="v-0-0-1">
|
|
@@ -84,10 +72,8 @@ exports[`AccordianCore > renders correct HTML structure with populated slots 1`]
|
|
|
84
72
|
<div class="label-wrapper"><span>Summary 1</span></div>
|
|
85
73
|
<div class="icon-wrapper"><span>Icon 1</span></div>
|
|
86
74
|
</summary>
|
|
75
|
+
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region"><span>Content 1</span></div>
|
|
87
76
|
</details>
|
|
88
|
-
<div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region">
|
|
89
|
-
<div class="inner"><span>Content 1</span></div>
|
|
90
|
-
</div>
|
|
91
77
|
</div>
|
|
92
78
|
</div>"
|
|
93
79
|
`;
|
|
@@ -13,9 +13,10 @@ All `--expanding-panel-*` tokens are the stable override surface. Set them at an
|
|
|
13
13
|
| `--expanding-panel-content-z-index` | `10` | Stacking order of the content region when `contentIsOnTop` is `true` |
|
|
14
14
|
| `--expanding-panel-content-gap` | `0px` | Space between the summary and the content region when `contentIsOnTop` is `true` |
|
|
15
15
|
|
|
16
|
-
Note: `background-color`, `padding`, and shadow are **not** tokenised
|
|
17
|
-
to `.
|
|
18
|
-
|
|
16
|
+
Note: `background-color`, `padding`, and shadow are **not** tokenised. They can be applied directly
|
|
17
|
+
to `.expanding-panel-content`, or to a wrapper inside the `#content` slot — see
|
|
18
|
+
[expanding-panel.md](../../../../.claude/skills/components/expanding-panel.md#styling-the-content-when-contentisontop)
|
|
19
|
+
for why both are safe.
|
|
19
20
|
|
|
20
21
|
---
|
|
21
22
|
|
|
@@ -64,7 +65,6 @@ visual style, pass a modifier class:
|
|
|
64
65
|
>
|
|
65
66
|
<template #summary>...</template>
|
|
66
67
|
<template #content>
|
|
67
|
-
<!-- Wrapper INSIDE the slot carries the visual styling — never .inner itself -->
|
|
68
68
|
<div class="promo-panel-body">...</div>
|
|
69
69
|
</template>
|
|
70
70
|
</ExpandingPanel>
|
|
@@ -89,12 +89,12 @@ visual style, pass a modifier class:
|
|
|
89
89
|
|
|
90
90
|
- `--expanding-panel-content-gap` and `--expanding-panel-content-z-index` only take effect when
|
|
91
91
|
`contentIsOnTop` is `true` — they're no-ops for the default (in-flow) layout.
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
while closed.
|
|
97
|
-
|
|
92
|
+
- `background-color`, `padding`, `border`, and shadow are all safe to set directly on
|
|
93
|
+
`.expanding-panel-content`. The open/close animation clips via `overflow: clip` on
|
|
94
|
+
`::details-content` (the native anonymous box wrapping everything after `<summary>`), and
|
|
95
|
+
`.expanding-panel-content` is a child of that box — so its own box is correctly clipped to the
|
|
96
|
+
animated height while closed/closing. A wrapper inside the `#content` slot works equally well
|
|
97
|
+
if you prefer to keep the styling scoped to your own markup.
|
|
98
98
|
- Don't stack multiple `contentIsOnTop` panels as direct siblings (linked via a shared `name` or
|
|
99
99
|
not). The overlay is absolutely positioned so it doesn't push the next element down — which
|
|
100
100
|
means a sibling `ExpandingPanel` placed right after it sits exactly where the overlay renders,
|
|
@@ -17,17 +17,15 @@
|
|
|
17
17
|
</slot>
|
|
18
18
|
</div>
|
|
19
19
|
</summary>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
>
|
|
27
|
-
<div class="inner">
|
|
20
|
+
<div
|
|
21
|
+
:id="`id-${name}-content`"
|
|
22
|
+
class="expanding-panel-content"
|
|
23
|
+
:aria-labelledby="`id-${name}-trigger`"
|
|
24
|
+
role="region"
|
|
25
|
+
>
|
|
28
26
|
<slot name="content"></slot>
|
|
29
27
|
</div>
|
|
30
|
-
</
|
|
28
|
+
</details>
|
|
31
29
|
</div>
|
|
32
30
|
</template>
|
|
33
31
|
|
|
@@ -58,10 +56,6 @@ const open = computed(() => props.forceOpened || isPanelOpen.value);
|
|
|
58
56
|
|
|
59
57
|
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
60
58
|
|
|
61
|
-
// contentIsOnTop panels behave like a dropdown/overlay (e.g. ContentDocs' mobile nav) rather
|
|
62
|
-
// than an inline accordion — clicking outside is the expected way to dismiss those, matching
|
|
63
|
-
// native <select>/menu behaviour. Not wanted for ordinary accordions (forceOpened is always
|
|
64
|
-
// false here too — a forceOpened panel isn't meant to be dismissible by any interaction).
|
|
65
59
|
const rootEl = ref<HTMLElement | null>(null);
|
|
66
60
|
onClickOutside(rootEl, () => {
|
|
67
61
|
if (props.contentIsOnTop && !props.forceOpened && isPanelOpen.value) {
|
|
@@ -94,9 +88,6 @@ const handleToggle = (event: Event) => {
|
|
|
94
88
|
};
|
|
95
89
|
|
|
96
90
|
const onDetailsToggle = (event: Event) => {
|
|
97
|
-
// forceOpened drives the native `open` attribute directly, which fires its own
|
|
98
|
-
// 'toggle' event — ignore it here or it leaks into isPanelOpen and gets stuck
|
|
99
|
-
// once forceOpened later reverts to false.
|
|
100
91
|
if (props.forceOpened) return;
|
|
101
92
|
isPanelOpen.value = (event.target as HTMLDetailsElement).open;
|
|
102
93
|
};
|
|
@@ -129,16 +120,20 @@ const onDetailsToggle = (event: Event) => {
|
|
|
129
120
|
.label-wrapper {
|
|
130
121
|
display: inline-block;
|
|
131
122
|
}
|
|
123
|
+
/* inline-size is reserved explicitly — the grid's align-items:center
|
|
124
|
+
doesn't stretch this item to row height, so with only aspect-ratio:1
|
|
125
|
+
and no content yet (icon SVG not loaded), the wrapper has nothing to
|
|
126
|
+
size itself against and collapses to 0 until the icon arrives, then
|
|
127
|
+
pops in and shifts the "auto" grid column's width. */
|
|
132
128
|
.icon-wrapper {
|
|
133
129
|
display: flex;
|
|
134
130
|
align-items: center;
|
|
135
131
|
justify-content: space-between;
|
|
136
132
|
|
|
133
|
+
inline-size: var(--expanding-panel-icon-size, 1.2rem);
|
|
137
134
|
aspect-ratio: 1;
|
|
138
135
|
overflow: hidden;
|
|
139
136
|
|
|
140
|
-
/* Space is always reserved (not v-if'd away) so a forceOpened panel's summary
|
|
141
|
-
row stays the same height as a toggleable sibling's — only the glyph hides. */
|
|
142
137
|
&.icon-wrapper--hidden {
|
|
143
138
|
visibility: hidden;
|
|
144
139
|
}
|
|
@@ -161,32 +156,35 @@ const onDetailsToggle = (event: Event) => {
|
|
|
161
156
|
}
|
|
162
157
|
}
|
|
163
158
|
}
|
|
164
|
-
+ .expanding-panel-content {
|
|
165
|
-
grid-template-rows: 1fr;
|
|
166
|
-
}
|
|
167
159
|
}
|
|
168
|
-
}
|
|
169
160
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
161
|
+
&::details-content {
|
|
162
|
+
overflow: clip;
|
|
163
|
+
height: 0;
|
|
164
|
+
transition:
|
|
165
|
+
height v-bind(animationDurationStr) ease-in-out,
|
|
166
|
+
content-visibility v-bind(animationDurationStr) ease-in-out allow-discrete;
|
|
167
|
+
}
|
|
175
168
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
@supports (interpolate-size: allow-keywords) {
|
|
170
|
+
interpolate-size: allow-keywords;
|
|
171
|
+
|
|
172
|
+
&[open]::details-content {
|
|
173
|
+
height: auto;
|
|
174
|
+
}
|
|
179
175
|
}
|
|
180
176
|
}
|
|
181
177
|
|
|
182
178
|
&.content-is-on-top {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
179
|
+
.expanding-panel-details {
|
|
180
|
+
position: relative;
|
|
181
|
+
|
|
182
|
+
&::details-content {
|
|
183
|
+
position: absolute;
|
|
184
|
+
top: calc(100% + var(--expanding-panel-content-gap, 0px));
|
|
185
|
+
inset-inline: 0;
|
|
186
|
+
z-index: var(--expanding-panel-content-z-index, 10);
|
|
187
|
+
}
|
|
190
188
|
}
|
|
191
189
|
}
|
|
192
190
|
}
|
package/app/components/02.molecules/expandable/expanding-panel/stories/ExpandingPanel.stories.ts
CHANGED
|
@@ -242,12 +242,11 @@ export const ContentIsOnTop: Story = {
|
|
|
242
242
|
</template>
|
|
243
243
|
<template #content>
|
|
244
244
|
<!--
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
a wrapper *inside* the slot keeps it clipped correctly by .inner's overflow:hidden.
|
|
245
|
+
Background/border/shadow can go directly on .expanding-panel-content (or, as
|
|
246
|
+
here, a wrapper inside the slot — either works). ::details-content (the native
|
|
247
|
+
anonymous box wrapping this content) is what clips to 0 height when collapsed,
|
|
248
|
+
via overflow:clip on the parent — so this element's own box, including padding
|
|
249
|
+
and background, is clipped correctly regardless of where the styling lives.
|
|
251
250
|
-->
|
|
252
251
|
<div style="color: #808080; background-color:#fff;border:1px solid #ddd;border-radius:0.4rem;padding:1rem;box-shadow:0 4px 12px rgb(0 0 0 / 15%)">
|
|
253
252
|
<p style="margin:0">
|
|
@@ -437,6 +437,12 @@ describe("ExpandingPanel", () => {
|
|
|
437
437
|
await wrapper.find("summary").trigger("click");
|
|
438
438
|
expect(vm.isPanelOpen).toBe(true);
|
|
439
439
|
|
|
440
|
+
// vueuse's onClickOutside debounces re-entrant clicks via a real setTimeout(0) that
|
|
441
|
+
// flips an internal "isProcessingClick" flag back off. With fake timers active
|
|
442
|
+
// (test/vitest.setup.ts) that flag never resets on its own, so the summary click
|
|
443
|
+
// above would otherwise permanently suppress the very next click. Advance past it.
|
|
444
|
+
vi.advanceTimersByTime(0);
|
|
445
|
+
|
|
440
446
|
document.body.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
441
447
|
await nextTick();
|
|
442
448
|
|
|
@@ -7,10 +7,8 @@ exports[`ExpandingPanel > renders correct HTML structure with all props set 1`]
|
|
|
7
7
|
<div class="label-wrapper"></div>
|
|
8
8
|
<div class="icon-wrapper icon-wrapper--hidden"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
9
9
|
</summary>
|
|
10
|
+
<div id="id-snap-all-content" class="expanding-panel-content" aria-labelledby="id-snap-all-trigger" role="region"></div>
|
|
10
11
|
</details>
|
|
11
|
-
<div id="id-snap-all-content" class="expanding-panel-content" aria-labelledby="id-snap-all-trigger" role="region">
|
|
12
|
-
<div class="inner"></div>
|
|
13
|
-
</div>
|
|
14
12
|
</div>"
|
|
15
13
|
`;
|
|
16
14
|
|
|
@@ -21,10 +19,8 @@ exports[`ExpandingPanel > renders correct HTML structure with contentIsOnTop tru
|
|
|
21
19
|
<div class="label-wrapper"></div>
|
|
22
20
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
23
21
|
</summary>
|
|
22
|
+
<div id="id-snap-content-on-top-content" class="expanding-panel-content" aria-labelledby="id-snap-content-on-top-trigger" role="region"></div>
|
|
24
23
|
</details>
|
|
25
|
-
<div id="id-snap-content-on-top-content" class="expanding-panel-content" aria-labelledby="id-snap-content-on-top-trigger" role="region">
|
|
26
|
-
<div class="inner"></div>
|
|
27
|
-
</div>
|
|
28
24
|
</div>"
|
|
29
25
|
`;
|
|
30
26
|
|
|
@@ -35,10 +31,8 @@ exports[`ExpandingPanel > renders correct HTML structure with default props 1`]
|
|
|
35
31
|
<div class="label-wrapper"></div>
|
|
36
32
|
<div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon mi-12" aria-hidden="true"></span></div>
|
|
37
33
|
</summary>
|
|
34
|
+
<div id="id-snap-default-content" class="expanding-panel-content" aria-labelledby="id-snap-default-trigger" role="region"></div>
|
|
38
35
|
</details>
|
|
39
|
-
<div id="id-snap-default-content" class="expanding-panel-content" aria-labelledby="id-snap-default-trigger" role="region">
|
|
40
|
-
<div class="inner"></div>
|
|
41
|
-
</div>
|
|
42
36
|
</div>"
|
|
43
37
|
`;
|
|
44
38
|
|
|
@@ -49,11 +43,9 @@ exports[`ExpandingPanel > renders correct HTML structure with populated slots 1`
|
|
|
49
43
|
<div class="label-wrapper"><span>Summary text</span></div>
|
|
50
44
|
<div class="icon-wrapper"><span>▸</span></div>
|
|
51
45
|
</summary>
|
|
52
|
-
|
|
53
|
-
<div id="id-snap-slots-content" class="expanding-panel-content" aria-labelledby="id-snap-slots-trigger" role="region">
|
|
54
|
-
<div class="inner">
|
|
46
|
+
<div id="id-snap-slots-content" class="expanding-panel-content" aria-labelledby="id-snap-slots-trigger" role="region">
|
|
55
47
|
<p>Content text</p>
|
|
56
48
|
</div>
|
|
57
|
-
</
|
|
49
|
+
</details>
|
|
58
50
|
</div>"
|
|
59
51
|
`;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# ExpandingPanelClassic — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## Public token API
|
|
4
|
+
|
|
5
|
+
All `--expanding-panel-classic-*` tokens are the stable override surface. Set them at any scope
|
|
6
|
+
(global, page, or instance) without touching the component itself.
|
|
7
|
+
|
|
8
|
+
| Token | Default | Controls |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `--expanding-panel-classic-summary-gap` | `1rem` | Gap between the summary label and the toggle icon |
|
|
11
|
+
| `--expanding-panel-classic-summary-padding-block` | `0.5rem` | Vertical padding on the clickable summary row |
|
|
12
|
+
| `--expanding-panel-classic-icon-size` | `1.2rem` | Toggle icon `font-size` |
|
|
13
|
+
| `--expanding-panel-classic-content-z-index` | `10` | Stacking order of the content region when `contentIsOnTop` is `true` |
|
|
14
|
+
| `--expanding-panel-classic-content-gap` | `0px` | Space between the summary and the content region when `contentIsOnTop` is `true` |
|
|
15
|
+
|
|
16
|
+
Note: `background-color`, `padding`, and shadow are **not** tokenised, and must never be applied
|
|
17
|
+
to `.inner` itself — see [expanding-panel-classic.md](../../../../.claude/skills/components/expanding-panel-classic.md#styling-the-content-when-contentisontop)
|
|
18
|
+
for why, and style a wrapper *inside* the `#content` slot instead.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Global theming — recommended approach
|
|
23
|
+
|
|
24
|
+
Create `assets/styles/setup/07.components/expanding-panel-classic.css` in the consuming app and set
|
|
25
|
+
tokens on `:root`. This applies to every `ExpandingPanelClassic` across the site.
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
/* assets/styles/setup/07.components/expanding-panel-classic.css */
|
|
29
|
+
:root {
|
|
30
|
+
--expanding-panel-classic-summary-gap: 1.6rem;
|
|
31
|
+
--expanding-panel-classic-summary-padding-block: 1.2rem;
|
|
32
|
+
--expanding-panel-classic-icon-size: 1.4rem;
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Page-scoped overrides
|
|
39
|
+
|
|
40
|
+
Override tokens for a specific section by scoping them under the page or layout wrapper.
|
|
41
|
+
No `:deep()` is required (component styles are unscoped).
|
|
42
|
+
|
|
43
|
+
```css
|
|
44
|
+
/* In the consuming page's unscoped <style> block */
|
|
45
|
+
.faq-section {
|
|
46
|
+
.expanding-panel-classic {
|
|
47
|
+
--expanding-panel-classic-summary-padding-block: 1.6rem;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Per-instance overrides via styleClassPassthrough
|
|
55
|
+
|
|
56
|
+
Use sparingly — prefer global or page-scoped CSS. When a single instance needs a distinct
|
|
57
|
+
visual style, pass a modifier class:
|
|
58
|
+
|
|
59
|
+
```vue
|
|
60
|
+
<ExpandingPanelClassic
|
|
61
|
+
name="promo"
|
|
62
|
+
:content-is-on-top="true"
|
|
63
|
+
:style-class-passthrough="['promo-panel']"
|
|
64
|
+
>
|
|
65
|
+
<template #summary>...</template>
|
|
66
|
+
<template #content>
|
|
67
|
+
<!-- Wrapper INSIDE the slot carries the visual styling — never .inner itself -->
|
|
68
|
+
<div class="promo-panel-body">...</div>
|
|
69
|
+
</template>
|
|
70
|
+
</ExpandingPanelClassic>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```css
|
|
74
|
+
.expanding-panel-classic.promo-panel {
|
|
75
|
+
--expanding-panel-classic-content-gap: 0.4rem;
|
|
76
|
+
--expanding-panel-classic-content-z-index: 20;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.promo-panel-body {
|
|
80
|
+
background-color: white;
|
|
81
|
+
padding: 1rem;
|
|
82
|
+
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Notes
|
|
89
|
+
|
|
90
|
+
- `--expanding-panel-classic-content-gap` and `--expanding-panel-classic-content-z-index` only take effect when
|
|
91
|
+
`contentIsOnTop` is `true` — they're no-ops for the default (in-flow) layout.
|
|
92
|
+
- Never set `background-color`, `padding`, `border`, or a shadow directly on `.inner`. `.inner`
|
|
93
|
+
has no explicit height when collapsed (`grid-template-rows: 0fr`) and relies on
|
|
94
|
+
`overflow: hidden` to clip its *children* to 0px — but padding/border/background on `.inner`'s
|
|
95
|
+
own box are not "overflow content", so they'd still render as a visible gap under the summary
|
|
96
|
+
while closed. Always style a wrapper element placed *inside* the `#content` slot instead; that
|
|
97
|
+
wrapper is a child of `.inner` and gets clipped correctly.
|
|
98
|
+
- Don't stack multiple `contentIsOnTop` panels as direct siblings (linked via a shared `name` or
|
|
99
|
+
not). The overlay is absolutely positioned so it doesn't push the next element down — which
|
|
100
|
+
means a sibling `ExpandingPanelClassic` placed right after it sits exactly where the overlay renders,
|
|
101
|
+
and gets visually covered when the first panel opens. `contentIsOnTop` is for a single panel
|
|
102
|
+
overlaying unrelated trailing page content, not for grouped/stacked accordion panels — use the
|
|
103
|
+
default in-flow layout for those.
|