srcdev-nuxt-components 9.3.0 → 9.3.2
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/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/__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/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/05.forms/input-select/CONSUMER-STYLING.md +42 -0
- package/app/components/05.forms/input-select/InputSelectCore.vue +21 -9
- package/app/components/05.forms/input-text/CONSUMER-STYLING.md +60 -0
- package/app/components/05.forms/input-text/InputTextCore.vue +17 -8
- package/app/layouts/default.vue +64 -67
- package/package.json +4 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SRCDEV ExpandingPanelClassic Basic": {
|
|
3
|
+
"description": "Basic uncontrolled ExpandingPanelClassic with summary and content slots",
|
|
4
|
+
"scope": "vue,html",
|
|
5
|
+
"body": [
|
|
6
|
+
"<ExpandingPanelClassic name=\"$1delivery\">",
|
|
7
|
+
" <template #summary>",
|
|
8
|
+
" <span>$2Delivery & Returns</span>",
|
|
9
|
+
" </template>",
|
|
10
|
+
" <template #content>",
|
|
11
|
+
" <p>$3Free standard delivery on orders over £50.</p>",
|
|
12
|
+
" </template>",
|
|
13
|
+
"</ExpandingPanelClassic>"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"SRCDEV ExpandingPanelClassic Controlled": {
|
|
17
|
+
"description": "ExpandingPanelClassic controlled via v-model with external toggle",
|
|
18
|
+
"scope": "vue,html",
|
|
19
|
+
"body": [
|
|
20
|
+
"<ExpandingPanelClassic name=\"$1faq-1\" v-model=\"$2isOpen\">",
|
|
21
|
+
" <template #summary>",
|
|
22
|
+
" <span>$3What is your returns policy?</span>",
|
|
23
|
+
" </template>",
|
|
24
|
+
" <template #content>",
|
|
25
|
+
" <p>$4You can return any item within 30 days.</p>",
|
|
26
|
+
" </template>",
|
|
27
|
+
"</ExpandingPanelClassic>"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"SRCDEV ExpandingPanelClassic Accordion Group": {
|
|
31
|
+
"description": "Multiple ExpandingPanelClassic panels grouped into a native accordion (shared name, only one open at a time)",
|
|
32
|
+
"scope": "vue,html",
|
|
33
|
+
"body": [
|
|
34
|
+
"<ExpandingPanelClassic name=\"$1faq\">",
|
|
35
|
+
" <template #summary>",
|
|
36
|
+
" <span>$2Question one?</span>",
|
|
37
|
+
" </template>",
|
|
38
|
+
" <template #content>",
|
|
39
|
+
" <p>$3Answer one.</p>",
|
|
40
|
+
" </template>",
|
|
41
|
+
"</ExpandingPanelClassic>",
|
|
42
|
+
"<ExpandingPanelClassic name=\"$1faq\">",
|
|
43
|
+
" <template #summary>",
|
|
44
|
+
" <span>$4Question two?</span>",
|
|
45
|
+
" </template>",
|
|
46
|
+
" <template #content>",
|
|
47
|
+
" <p>$5Answer two.</p>",
|
|
48
|
+
" </template>",
|
|
49
|
+
"</ExpandingPanelClassic>"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"SRCDEV ExpandingPanelClassic Force Opened": {
|
|
53
|
+
"description": "ExpandingPanelClassic locked open, no toggle icon, cannot be collapsed",
|
|
54
|
+
"scope": "vue,html",
|
|
55
|
+
"body": [
|
|
56
|
+
"<ExpandingPanelClassic name=\"$1notice\" :force-opened=\"true\">",
|
|
57
|
+
" <template #summary>",
|
|
58
|
+
" <strong>$2Important notice</strong>",
|
|
59
|
+
" </template>",
|
|
60
|
+
" <template #content>",
|
|
61
|
+
" <p>$3This panel cannot be collapsed.</p>",
|
|
62
|
+
" </template>",
|
|
63
|
+
"</ExpandingPanelClassic>"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"SRCDEV ExpandingPanelClassic Content Is On Top": {
|
|
67
|
+
"description": "ExpandingPanelClassic with content overlaying page content instead of pushing layout down",
|
|
68
|
+
"scope": "vue,html",
|
|
69
|
+
"body": [
|
|
70
|
+
"<ExpandingPanelClassic",
|
|
71
|
+
" name=\"$1overlay\"",
|
|
72
|
+
" :content-is-on-top=\"true\"",
|
|
73
|
+
" :style-class-passthrough=\"['$2my-overlay-panel']\"",
|
|
74
|
+
">",
|
|
75
|
+
" <template #summary>",
|
|
76
|
+
" <span>$3Open me — content overlays what's below</span>",
|
|
77
|
+
" </template>",
|
|
78
|
+
" <template #content>",
|
|
79
|
+
" <div class=\"$2my-overlay-panel-body\">",
|
|
80
|
+
" <p>$4Positioned absolutely below the summary, doesn't push page content down.</p>",
|
|
81
|
+
" </div>",
|
|
82
|
+
" </template>",
|
|
83
|
+
"</ExpandingPanelClassic>"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"SRCDEV ExpandingPanelClassic Custom Icon": {
|
|
87
|
+
"description": "ExpandingPanelClassic with a custom toggle icon via #icon slot",
|
|
88
|
+
"scope": "vue,html",
|
|
89
|
+
"body": [
|
|
90
|
+
"<ExpandingPanelClassic name=\"$1custom-icon\">",
|
|
91
|
+
" <template #summary>",
|
|
92
|
+
" <span>$2Section title</span>",
|
|
93
|
+
" </template>",
|
|
94
|
+
" <template #icon>",
|
|
95
|
+
" <span style=\"font-size:1.2rem\">$3+</span>",
|
|
96
|
+
" </template>",
|
|
97
|
+
" <template #content>",
|
|
98
|
+
" <p>$4Content here.</p>",
|
|
99
|
+
" </template>",
|
|
100
|
+
"</ExpandingPanelClassic>"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
"SRCDEV ExpandingPanelClassic CSS Override": {
|
|
104
|
+
"description": "CSS override scaffold for ExpandingPanelClassic tokens",
|
|
105
|
+
"scope": "css",
|
|
106
|
+
"body": [
|
|
107
|
+
".$1my-panel {",
|
|
108
|
+
" .expanding-panel-classic {",
|
|
109
|
+
" --expanding-panel-classic-summary-gap: $2;",
|
|
110
|
+
" --expanding-panel-classic-summary-padding-block: $3;",
|
|
111
|
+
" --expanding-panel-classic-icon-size: $4;",
|
|
112
|
+
"",
|
|
113
|
+
" &.content-is-on-top {",
|
|
114
|
+
" --expanding-panel-classic-content-gap: $5;",
|
|
115
|
+
" --expanding-panel-classic-content-z-index: $6;",
|
|
116
|
+
" }",
|
|
117
|
+
" }",
|
|
118
|
+
"}"
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
]
|
|
65
65
|
},
|
|
66
66
|
"SRCDEV ExpandingPanel Content Is On Top": {
|
|
67
|
-
"description": "ExpandingPanel with content overlaying page content instead of pushing layout down
|
|
67
|
+
"description": "ExpandingPanel with content overlaying page content instead of pushing layout down",
|
|
68
68
|
"scope": "vue,html",
|
|
69
69
|
"body": [
|
|
70
70
|
"<ExpandingPanel",
|
|
@@ -76,8 +76,6 @@
|
|
|
76
76
|
" <span>$3Open me — content overlays what's below</span>",
|
|
77
77
|
" </template>",
|
|
78
78
|
" <template #content>",
|
|
79
|
-
" <!-- Wrapper INSIDE the slot carries background/padding/shadow — never .inner itself,",
|
|
80
|
-
" which must stay visually unstyled or it renders while the panel is closed. -->",
|
|
81
79
|
" <div class=\"$2my-overlay-panel-body\">",
|
|
82
80
|
" <p>$4Positioned absolutely below the summary, doesn't push page content down.</p>",
|
|
83
81
|
" </div>",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SRCDEV ResponsiveHeader Basic": {
|
|
3
|
+
"description": "ResponsiveHeader with dropdown groups and a secondaryNavigation slot",
|
|
4
|
+
"scope": "vue,html",
|
|
5
|
+
"body": [
|
|
6
|
+
"<ResponsiveHeader",
|
|
7
|
+
" :responsive-nav-links=\"$1responsiveNavLinks\"",
|
|
8
|
+
" :style-class-passthrough=\"['site-header-nav']\"",
|
|
9
|
+
">",
|
|
10
|
+
" <template #secondaryNavigation>",
|
|
11
|
+
" <NuxtLink to=\"$2/settings\" aria-label=\"$3Settings\">",
|
|
12
|
+
" <Icon name=\"$4material-symbols:settings-outline-rounded\" />",
|
|
13
|
+
" </NuxtLink>",
|
|
14
|
+
" </template>",
|
|
15
|
+
"</ResponsiveHeader>"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"SRCDEV ResponsiveHeader Nav Data (with dropdown)": {
|
|
19
|
+
"description": "responsiveNavLinks data with a dropdown group",
|
|
20
|
+
"scope": "typescript,vue",
|
|
21
|
+
"body": [
|
|
22
|
+
"const $1responsiveNavLinks = {",
|
|
23
|
+
" firstNav: [",
|
|
24
|
+
" { name: \"$2Home\", path: \"$3/\" },",
|
|
25
|
+
" {",
|
|
26
|
+
" name: \"$4Components\",",
|
|
27
|
+
" childLinksTitle: \"$5UI Components\",",
|
|
28
|
+
" childLinks: [{ name: \"$6Item\", path: \"$7/path\" }],",
|
|
29
|
+
" },",
|
|
30
|
+
" ],",
|
|
31
|
+
"};"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"SRCDEV ResponsiveHeader CSS Override": {
|
|
35
|
+
"description": "CSS override scaffold for ResponsiveHeader token styling — sets a fixed (non-fluid) link font-size",
|
|
36
|
+
"scope": "css",
|
|
37
|
+
"body": [
|
|
38
|
+
".$1site-header-nav {",
|
|
39
|
+
" --responsive-header-link-color: $2inherit;",
|
|
40
|
+
" --responsive-header-bg: $3transparent;",
|
|
41
|
+
" /* Fixed, NOT var(--step-*) — see responsive-header.md's measurement-pipeline note. */",
|
|
42
|
+
" --responsive-header-link-font-size: ${4:1.4rem};",
|
|
43
|
+
"}"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"SRCDEV SiteHeader Basic": {
|
|
3
|
+
"description": "SiteHeader wrapping PageRow + SkipLinks + ResponsiveHeader, with a branding slot",
|
|
4
|
+
"scope": "vue,html",
|
|
5
|
+
"body": [
|
|
6
|
+
"<SiteHeader",
|
|
7
|
+
" :responsive-nav-links=\"$1responsiveNavLinks\"",
|
|
8
|
+
" :style-class-passthrough=\"['header']\"",
|
|
9
|
+
" :nav-style-class-passthrough=\"['site-header-nav']\"",
|
|
10
|
+
">",
|
|
11
|
+
" <template #branding>",
|
|
12
|
+
" <NuxtLink to=\"/\" class=\"home-link\">$2Brand</NuxtLink>",
|
|
13
|
+
" </template>",
|
|
14
|
+
"</SiteHeader>"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"SRCDEV SiteHeader With Secondary Navigation": {
|
|
18
|
+
"description": "SiteHeader with the secondaryNavigation slot (e.g. a settings icon link)",
|
|
19
|
+
"scope": "vue,html",
|
|
20
|
+
"body": [
|
|
21
|
+
"<SiteHeader :responsive-nav-links=\"$1responsiveNavLinks\">",
|
|
22
|
+
" <template #branding>",
|
|
23
|
+
" <NuxtLink to=\"/\" class=\"home-link\">$2Brand</NuxtLink>",
|
|
24
|
+
" </template>",
|
|
25
|
+
" <template #secondaryNavigation>",
|
|
26
|
+
" <NuxtLink to=\"$3/settings\" aria-label=\"$4Settings\">",
|
|
27
|
+
" <Icon name=\"$5material-symbols:settings-outline-rounded\" />",
|
|
28
|
+
" </NuxtLink>",
|
|
29
|
+
" </template>",
|
|
30
|
+
"</SiteHeader>"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"SRCDEV SiteHeader Nav Data (flat)": {
|
|
34
|
+
"description": "Simple flat responsiveNavLinks data with no dropdowns, for a few-page site",
|
|
35
|
+
"scope": "typescript,vue",
|
|
36
|
+
"body": [
|
|
37
|
+
"const $1responsiveNavLinks = {",
|
|
38
|
+
" main: [",
|
|
39
|
+
" { name: \"$2Home\", path: \"$3/\" },",
|
|
40
|
+
" { name: \"$4About\", path: \"$5/about\" },",
|
|
41
|
+
" { name: \"$6Contact\", path: \"$7/contact\" },",
|
|
42
|
+
" ],",
|
|
43
|
+
"};"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"SRCDEV SiteHeader Nav Data (with dropdown)": {
|
|
47
|
+
"description": "responsiveNavLinks data with a dropdown group, for the full overflow-nav experience",
|
|
48
|
+
"scope": "typescript,vue",
|
|
49
|
+
"body": [
|
|
50
|
+
"const $1responsiveNavLinks = {",
|
|
51
|
+
" firstNav: [",
|
|
52
|
+
" {",
|
|
53
|
+
" name: \"$2Components\",",
|
|
54
|
+
" childLinksTitle: \"$3UI Components\",",
|
|
55
|
+
" childLinks: [{ name: \"$4Item\", path: \"$5/path\" }],",
|
|
56
|
+
" },",
|
|
57
|
+
" { name: \"$6Typography\", path: \"$7/typography\" },",
|
|
58
|
+
" ],",
|
|
59
|
+
"};"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"SRCDEV SiteHeader CSS Override": {
|
|
63
|
+
"description": "CSS override scaffold for SiteHeader token styling",
|
|
64
|
+
"scope": "css",
|
|
65
|
+
"body": [
|
|
66
|
+
".$1header {",
|
|
67
|
+
" --site-header-gap: $2 2.4rem;",
|
|
68
|
+
" --site-header-padding-inline: $3 2.4rem;",
|
|
69
|
+
" --site-header-bg: $4transparent;",
|
|
70
|
+
" /* --site-header-position: sticky; */",
|
|
71
|
+
"}"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="display-accordian" :class="[elementClasses]">
|
|
3
|
-
<
|
|
3
|
+
<component
|
|
4
|
+
:is="panelComponent"
|
|
4
5
|
v-for="(item, key) in itemCount"
|
|
5
6
|
:key="key"
|
|
6
7
|
:name
|
|
@@ -17,15 +18,19 @@
|
|
|
17
18
|
<template #content>
|
|
18
19
|
<slot :name="`accordian-${key}-content`"></slot>
|
|
19
20
|
</template>
|
|
20
|
-
</
|
|
21
|
+
</component>
|
|
21
22
|
</div>
|
|
22
23
|
</template>
|
|
23
24
|
|
|
24
25
|
<script setup lang="ts">
|
|
26
|
+
import ExpandingPanel from "../expanding-panel/ExpandingPanel.vue";
|
|
27
|
+
import ExpandingPanelClassic from "../expanding-panel-classic/ExpandingPanelClassic.vue";
|
|
28
|
+
|
|
25
29
|
interface Props {
|
|
26
30
|
name?: string;
|
|
27
31
|
itemCount?: number;
|
|
28
32
|
animationDuration?: number;
|
|
33
|
+
variant?: "modern" | "classic";
|
|
29
34
|
styleClassPassthrough?: string | string[];
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -33,26 +38,29 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
33
38
|
name: undefined,
|
|
34
39
|
itemCount: 0,
|
|
35
40
|
animationDuration: 300,
|
|
41
|
+
variant: "modern",
|
|
36
42
|
styleClassPassthrough: () => [],
|
|
37
43
|
});
|
|
38
44
|
|
|
39
45
|
const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
40
46
|
const animationDurationStr = computed(() => `${props.animationDuration}ms`);
|
|
47
|
+
const panelComponent = computed(() => (props.variant === "classic" ? ExpandingPanelClassic : ExpandingPanel));
|
|
41
48
|
</script>
|
|
42
49
|
|
|
43
50
|
<style lang="css">
|
|
44
51
|
@layer components {
|
|
45
|
-
.display-accordian {
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
.display-accordian {
|
|
53
|
+
max-width: 600px;
|
|
54
|
+
margin: 0 auto;
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
.accordian-item {
|
|
57
|
+
&.expanding-panel,
|
|
58
|
+
&.expanding-panel-classic {
|
|
59
|
+
transition:
|
|
60
|
+
margin-block-end v-bind(animationDurationStr) ease-in-out,
|
|
61
|
+
border-radius v-bind(animationDurationStr) ease-in-out;
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
|
-
}
|
|
58
66
|
</style>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# AccordianCore — Consumer Styling Guide
|
|
2
|
+
|
|
3
|
+
## No tokens of its own
|
|
4
|
+
|
|
5
|
+
`AccordianCore` doesn't define any `--accordian-*` custom properties. It's a thin wrapper that
|
|
6
|
+
renders `itemCount` copies of a panel component (`ExpandingPanel` by default, or
|
|
7
|
+
`ExpandingPanelClassic` when `variant="classic"`) and forwards `name`/`animationDuration` to each
|
|
8
|
+
one — all real styling surface lives on whichever panel component is active. See that
|
|
9
|
+
component's own `CONSUMER-STYLING.md`:
|
|
10
|
+
|
|
11
|
+
- [../expanding-panel/CONSUMER-STYLING.md](../expanding-panel/CONSUMER-STYLING.md) (default, `variant="modern"`)
|
|
12
|
+
- [../expanding-panel-classic/CONSUMER-STYLING.md](../expanding-panel-classic/CONSUMER-STYLING.md) (`variant="classic"`)
|
|
13
|
+
|
|
14
|
+
`--expanding-panel-*` tokens set at any scope (global, page) apply to `AccordianCore`'s panels
|
|
15
|
+
too, since they're the same underlying component — no `AccordianCore`-specific override is
|
|
16
|
+
needed for summary gap, icon size, etc.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The `.accordian-item` hook
|
|
21
|
+
|
|
22
|
+
Every panel gets `style-class-passthrough="['accordian-item']"` automatically — this is the one
|
|
23
|
+
`AccordianCore`-specific override surface, used to target *all* panels in the group without
|
|
24
|
+
reaching into `ExpandingPanel`/`ExpandingPanelClassic` internals.
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
/* In the consuming page's unscoped <style> block */
|
|
28
|
+
.display-accordian {
|
|
29
|
+
.accordian-item.expanding-panel {
|
|
30
|
+
/* modern variant (default) */
|
|
31
|
+
border-block-end: 1px solid var(--theme-border, #e0e0e0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* If this instance uses variant="classic", target the classic root class instead: */
|
|
35
|
+
.accordian-item.expanding-panel-classic {
|
|
36
|
+
border-block-end: 1px solid var(--theme-border, #e0e0e0);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Note the root class differs by variant (`.expanding-panel` vs `.expanding-panel-classic`) — a
|
|
42
|
+
selector written for one won't match the other. If you switch `variant` on an existing instance,
|
|
43
|
+
check any `.accordian-item.expanding-panel*` overrides still target the right class.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Page-scoped overrides
|
|
48
|
+
|
|
49
|
+
No `:deep()` is required (component styles are unscoped).
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
.faq-section {
|
|
53
|
+
.display-accordian {
|
|
54
|
+
max-width: 480px;
|
|
55
|
+
|
|
56
|
+
.accordian-item.expanding-panel {
|
|
57
|
+
--expanding-panel-summary-padding-block: 1.6rem;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Per-instance overrides via styleClassPassthrough
|
|
66
|
+
|
|
67
|
+
Use sparingly — prefer global or page-scoped CSS. `styleClassPassthrough` on `AccordianCore`
|
|
68
|
+
targets the root `.display-accordian` element, not the individual panels (those already get
|
|
69
|
+
`.accordian-item` automatically):
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<AccordianCore name="faq" :item-count="3" :style-class-passthrough="['compact-faq']">
|
|
73
|
+
...
|
|
74
|
+
</AccordianCore>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```css
|
|
78
|
+
.display-accordian.compact-faq {
|
|
79
|
+
max-width: 400px;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Notes
|
|
86
|
+
|
|
87
|
+
- `variant` is group-wide: every panel in one `AccordianCore` renders the same component, so
|
|
88
|
+
there's no per-panel styling divergence caused by variant — just remember which root class
|
|
89
|
+
(`.expanding-panel` vs `.expanding-panel-classic`) your `.accordian-item` selectors need.
|
|
90
|
+
- `contentIsOnTop` isn't exposed on `AccordianCore` — it isn't meaningful for a grouped accordion
|
|
91
|
+
(see `ExpandingPanel`'s own docs for why: it's for a single panel overlaying trailing page
|
|
92
|
+
content, not stacked/grouped panels). Use `ExpandingPanel`/`ExpandingPanelClassic` directly if
|
|
93
|
+
you need an overlay panel.
|
|
@@ -18,6 +18,14 @@ const meta: Meta<typeof AccordianCore> = {
|
|
|
18
18
|
control: { type: "number", min: 0, step: 50 },
|
|
19
19
|
description: "Expand/collapse animation duration in milliseconds",
|
|
20
20
|
},
|
|
21
|
+
variant: {
|
|
22
|
+
control: { type: "radio" },
|
|
23
|
+
options: ["modern", "classic"],
|
|
24
|
+
description:
|
|
25
|
+
"Which panel implementation to render: 'modern' (ExpandingPanel, ::details-content based) or " +
|
|
26
|
+
"'classic' (ExpandingPanelClassic, grid-template-rows based) — use classic when the open/close " +
|
|
27
|
+
"animation must run identically in every browser today",
|
|
28
|
+
},
|
|
21
29
|
styleClassPassthrough: {
|
|
22
30
|
control: "object",
|
|
23
31
|
description: "Additional CSS classes applied to the root element",
|
|
@@ -27,6 +35,7 @@ const meta: Meta<typeof AccordianCore> = {
|
|
|
27
35
|
name: undefined,
|
|
28
36
|
itemCount: 3,
|
|
29
37
|
animationDuration: 300,
|
|
38
|
+
variant: "modern",
|
|
30
39
|
styleClassPassthrough: [],
|
|
31
40
|
},
|
|
32
41
|
};
|
|
@@ -71,6 +80,28 @@ export const Default: Story = {
|
|
|
71
80
|
}),
|
|
72
81
|
};
|
|
73
82
|
|
|
83
|
+
export const ClassicVariant: Story = {
|
|
84
|
+
name: "Classic Variant",
|
|
85
|
+
args: {
|
|
86
|
+
itemCount: 3,
|
|
87
|
+
animationDuration: 300,
|
|
88
|
+
variant: "classic",
|
|
89
|
+
},
|
|
90
|
+
render: (args) => ({
|
|
91
|
+
components: { AccordianCore },
|
|
92
|
+
setup() {
|
|
93
|
+
return { args, slots: buildGenericSlots(args.itemCount ?? 3) };
|
|
94
|
+
},
|
|
95
|
+
template: `
|
|
96
|
+
<AccordianCore v-bind="args">
|
|
97
|
+
<template v-for="(content, name) in slots" #[name] :key="name">
|
|
98
|
+
<span v-html="content" />
|
|
99
|
+
</template>
|
|
100
|
+
</AccordianCore>
|
|
101
|
+
`,
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
|
|
74
105
|
export const SinglePanel: Story = {
|
|
75
106
|
args: {
|
|
76
107
|
itemCount: 1,
|
|
@@ -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,
|