srcdev-nuxt-components 9.3.6 → 9.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/.claude/hooks/check-component-update.sh +30 -1
  2. package/.claude/skills/components/accordian-core.md +7 -7
  3. package/.claude/skills/components/breadcrumb.md +49 -0
  4. package/.claude/skills/components/content-docs.md +2 -1
  5. package/.claude/skills/components/expanding-panel.md +5 -5
  6. package/.claude/skills/components/navigation-items.md +1 -0
  7. package/.claude/skills/components/responsive-header.md +1 -0
  8. package/.claude/skills/components/service-detail.md +156 -0
  9. package/.claude/skills/components/services-card-grid.md +9 -9
  10. package/.claude/skills/components/services-card.md +82 -30
  11. package/.claude/skills/index.md +3 -1
  12. package/.claude/skills/qa-panel.md +2 -2
  13. package/.vscode/srcdev-component-breadcrumb.code-snippets +14 -0
  14. package/.vscode/srcdev-component-service-detail.code-snippets +49 -0
  15. package/.vscode/srcdev-component-services-card.code-snippets +108 -0
  16. package/app/components/01.atoms/content-wrappers/docs-pages/ContentDocs.vue +22 -16
  17. package/app/components/01.atoms/navigation/breadcrumb/Breadcrumb.vue +94 -0
  18. package/app/components/01.atoms/navigation/breadcrumb/CONSUMER-STYLING.md +32 -0
  19. package/app/components/01.atoms/navigation/breadcrumb/stories/Breadcrumb.stories.ts +87 -0
  20. package/app/components/01.atoms/navigation/breadcrumb/tests/Breadcrumb.spec.ts +87 -0
  21. package/app/components/01.atoms/navigation/breadcrumb/tests/__snapshots__/Breadcrumb.spec.ts.snap +13 -0
  22. package/app/components/02.molecules/expandable/accordian/AccordianCore.vue +5 -1
  23. package/app/components/02.molecules/expandable/accordian/tests/AccordianCore.spec.ts +22 -14
  24. package/app/components/02.molecules/expandable/accordian/tests/__snapshots__/AccordianCore.spec.ts.snap +42 -28
  25. package/app/components/02.molecules/expandable/expanding-panel/CONSUMER-STYLING.md +8 -10
  26. package/app/components/02.molecules/expandable/expanding-panel/ExpandingPanel.vue +15 -47
  27. package/app/components/02.molecules/expandable/expanding-panel/tests/__snapshots__/ExpandingPanel.spec.ts.snap +4 -12
  28. package/app/components/03.organisms/responsive-header/NavigationItems.vue +91 -4
  29. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +5 -1
  30. package/app/components/03.organisms/responsive-header/tests/NavigationItems.spec.ts +2 -2
  31. package/app/components/03.organisms/responsive-header/tests/__snapshots__/NavigationItems.spec.ts.snap +13 -13
  32. package/app/components/03.organisms/responsive-header/tests/__snapshots__/ResponsiveHeader.spec.ts.snap +13 -13
  33. package/app/components/03.organisms/services/service-detail/CONSUMER-STYLING.md +181 -0
  34. package/app/components/03.organisms/services/service-detail/ServiceDetail.vue +480 -0
  35. package/app/components/03.organisms/services/service-detail/stories/ServiceDetail.stories.ts +282 -0
  36. package/app/components/03.organisms/services/service-detail/tests/ServiceDetail.spec.ts +270 -0
  37. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +99 -0
  38. package/app/components/03.organisms/services/services-card/CONSUMER-STYLING.md +129 -0
  39. package/app/components/03.organisms/services/services-card/ServicesCard.vue +153 -39
  40. package/app/components/03.organisms/services/services-card/stories/ServicesCard.stories.ts +65 -0
  41. package/app/components/03.organisms/services/services-card/tests/ServicesCard.spec.ts +82 -0
  42. package/app/components/03.organisms/services/services-card/tests/__snapshots__/ServicesCard.spec.ts.snap +13 -5
  43. package/app/components/03.organisms/services/services-grids/ServicesCardGrid.vue +3 -7
  44. package/app/components/03.organisms/services/services-grids/stories/ServicesCardGrid.stories.ts +126 -2
  45. package/app/components/03.organisms/services/services-grids/stories/ServicesSectionGrid.stories.ts +6 -0
  46. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServicesCardGrid.spec.ts.snap +51 -27
  47. package/app/components/03.organisms/services/services-section/ServicesSection.vue +19 -0
  48. package/app/components/03.organisms/services/services-section/tests/__snapshots__/ServicesSection.spec.ts.snap +8 -8
  49. package/app/types/components/breadcrumb.ts +4 -0
  50. package/app/types/components/index.ts +1 -0
  51. package/package.json +2 -2
  52. package/app/pages/auto-grid.vue +0 -328
  53. package/app/pages/banner-video.vue +0 -238
  54. package/app/pages/forms/examples/buttons/index.vue +0 -259
  55. package/app/pages/forms/examples/material/checkbox-radio-panels.vue +0 -244
  56. package/app/pages/forms/examples/material/cssbattle.vue +0 -60
  57. package/app/pages/forms/examples/material/text-fields.vue +0 -910
  58. package/app/pages/grid-stack.vue +0 -252
  59. package/app/pages/index.vue +0 -127
  60. package/app/pages/page-hero-highlights.vue +0 -204
  61. package/app/pages/samaritan.vue +0 -327
  62. package/app/pages/typography/hero-text.vue +0 -97
  63. package/app/pages/typography/index.vue +0 -39
  64. package/app/pages/typography/page-body.vue +0 -171
  65. package/app/pages/typography/page-heading.vue +0 -76
  66. package/app/pages/typography/page-link.vue +0 -103
  67. package/app/pages/ui/accordian.vue +0 -211
  68. package/app/pages/ui/anchor-scroll-tab-navigation.vue +0 -191
  69. package/app/pages/ui/anchor-scroll.vue +0 -367
  70. package/app/pages/ui/animated-svg-text.vue +0 -85
  71. package/app/pages/ui/block-decorators.vue +0 -142
  72. package/app/pages/ui/card-core.vue +0 -82
  73. package/app/pages/ui/carousel-basic.vue +0 -292
  74. package/app/pages/ui/carousel-flip.vue +0 -449
  75. package/app/pages/ui/carousel-infinite.vue +0 -260
  76. package/app/pages/ui/clipped-panels.vue +0 -101
  77. package/app/pages/ui/contact-section.vue +0 -323
  78. package/app/pages/ui/container-glow.vue +0 -106
  79. package/app/pages/ui/content-width.vue +0 -126
  80. package/app/pages/ui/deep-expanding-menu.vue +0 -138
  81. package/app/pages/ui/display-avatar.vue +0 -253
  82. package/app/pages/ui/display-banner.vue +0 -78
  83. package/app/pages/ui/display-chip.vue +0 -367
  84. package/app/pages/ui/display-details.vue +0 -171
  85. package/app/pages/ui/display-dialog.vue +0 -344
  86. package/app/pages/ui/display-pill.vue +0 -396
  87. package/app/pages/ui/display-prompt.vue +0 -284
  88. package/app/pages/ui/display-toast.vue +0 -311
  89. package/app/pages/ui/expanding-panel.vue +0 -326
  90. package/app/pages/ui/glowing-border.vue +0 -111
  91. package/app/pages/ui/layout-content-docs.vue +0 -72
  92. package/app/pages/ui/layout-grid-a.vue +0 -96
  93. package/app/pages/ui/layout-grid-b.vue +0 -132
  94. package/app/pages/ui/magnetic-navigation.vue +0 -50
  95. package/app/pages/ui/marquee-scroller.vue +0 -99
  96. package/app/pages/ui/mask-element.vue +0 -104
  97. package/app/pages/ui/masonry-columns.vue +0 -55
  98. package/app/pages/ui/masonry-grid-ordered-experiment.vue +0 -79
  99. package/app/pages/ui/masonry-grid-ordered.vue +0 -56
  100. package/app/pages/ui/masonry-grid-sorted.vue +0 -55
  101. package/app/pages/ui/masonry-grid.vue +0 -55
  102. package/app/pages/ui/navigation/navigation-horizontal.vue +0 -484
  103. package/app/pages/ui/navigation/site-navigation/about.vue +0 -36
  104. package/app/pages/ui/navigation/site-navigation/contact.vue +0 -39
  105. package/app/pages/ui/navigation/site-navigation/index.vue +0 -38
  106. package/app/pages/ui/navigation/site-navigation/portfolio.vue +0 -43
  107. package/app/pages/ui/navigation/site-navigation/services.vue +0 -42
  108. package/app/pages/ui/navigation/tab-navigation.vue +0 -493
  109. package/app/pages/ui/page-row.vue +0 -127
  110. package/app/pages/ui/price-list.vue +0 -65
  111. package/app/pages/ui/profile-section.vue +0 -142
  112. package/app/pages/ui/qr-code/[componentName].vue +0 -218
  113. package/app/pages/ui/rotating-carousel.vue +0 -154
  114. package/app/pages/ui/scroll-reveal-image.vue +0 -483
  115. package/app/pages/ui/section-parallax.vue +0 -54
  116. package/app/pages/ui/services/colour-finder.vue +0 -43
  117. package/app/pages/ui/services/services-cards.vue +0 -57
  118. package/app/pages/ui/services/services-section/[slug].vue +0 -61
  119. package/app/pages/ui/services/services-sections-compact.vue +0 -64
  120. package/app/pages/ui/settings.vue +0 -54
  121. package/app/pages/ui/simple-grid.vue +0 -55
  122. package/app/pages/ui/slider-gallery.vue +0 -261
  123. package/app/pages/ui/tabs-y.vue +0 -107
  124. package/app/pages/ui/tabs.vue +0 -189
  125. package/app/pages/ui/tooltips.vue +0 -104
  126. package/app/pages/ui/wipe-away-vertical.vue +0 -129
@@ -74,11 +74,11 @@ describe("AccordianCore", () => {
74
74
 
75
75
  // ─── Item count ───────────────────────────────────────────────────────────
76
76
 
77
- it("renders the correct number of ExpandingPanel components", async () => {
77
+ it("renders the correct number of panel components", async () => {
78
78
  const wrapper = await mountSuspended(AccordianCore, {
79
79
  props: { itemCount: 4 },
80
80
  });
81
- expect(wrapper.findAll(".expanding-panel")).toHaveLength(4);
81
+ expect(wrapper.findAll(".accordian-item")).toHaveLength(4);
82
82
  });
83
83
 
84
84
  it("renders no ExpandingPanel components when itemCount is 0", async () => {
@@ -95,15 +95,15 @@ describe("AccordianCore", () => {
95
95
 
96
96
  // ─── variant ──────────────────────────────────────────────────────────────
97
97
 
98
- it("renders ExpandingPanel by default (variant: modern)", async () => {
98
+ it("renders ExpandingPanelClassic by default (variant: classic)", async () => {
99
99
  const wrapper = await mountSuspended(AccordianCore, {
100
100
  props: { itemCount: 2 },
101
101
  });
102
- expect(wrapper.findAllComponents({ name: "ExpandingPanel" })).toHaveLength(2);
103
- expect(wrapper.findAllComponents({ name: "ExpandingPanelClassic" })).toHaveLength(0);
102
+ expect(wrapper.findAllComponents({ name: "ExpandingPanelClassic" })).toHaveLength(2);
103
+ expect(wrapper.findAllComponents({ name: "ExpandingPanel" })).toHaveLength(0);
104
104
  });
105
105
 
106
- it("renders ExpandingPanelClassic when variant is classic", async () => {
106
+ it("renders ExpandingPanelClassic when variant is classic (explicit)", async () => {
107
107
  const wrapper = await mountSuspended(AccordianCore, {
108
108
  props: { itemCount: 2, variant: "classic" },
109
109
  });
@@ -111,6 +111,14 @@ describe("AccordianCore", () => {
111
111
  expect(wrapper.findAllComponents({ name: "ExpandingPanel" })).toHaveLength(0);
112
112
  });
113
113
 
114
+ it("renders ExpandingPanel when variant is modern", async () => {
115
+ const wrapper = await mountSuspended(AccordianCore, {
116
+ props: { itemCount: 2, variant: "modern" },
117
+ });
118
+ expect(wrapper.findAllComponents({ name: "ExpandingPanel" })).toHaveLength(2);
119
+ expect(wrapper.findAllComponents({ name: "ExpandingPanelClassic" })).toHaveLength(0);
120
+ });
121
+
114
122
  it("forwards props identically to ExpandingPanelClassic when variant is classic", async () => {
115
123
  const wrapper = await mountSuspended(AccordianCore, {
116
124
  props: { itemCount: 2, name: "classic-accordian", animationDuration: 500, variant: "classic" },
@@ -122,11 +130,11 @@ describe("AccordianCore", () => {
122
130
  });
123
131
  });
124
132
 
125
- // ─── Props forwarded to ExpandingPanel ───────────────────────────────────
133
+ // ─── Props forwarded to ExpandingPanel (variant: modern) ─────────────────
126
134
 
127
135
  it("forwards animationDuration to every ExpandingPanel", async () => {
128
136
  const wrapper = await mountSuspended(AccordianCore, {
129
- props: { itemCount: 3, animationDuration: 500 },
137
+ props: { itemCount: 3, animationDuration: 500, variant: "modern" },
130
138
  });
131
139
  wrapper.findAllComponents({ name: "ExpandingPanel" }).forEach((panel) => {
132
140
  expect(panel.props("animationDuration")).toBe(500);
@@ -135,7 +143,7 @@ describe("AccordianCore", () => {
135
143
 
136
144
  it("forwards name to every ExpandingPanel", async () => {
137
145
  const wrapper = await mountSuspended(AccordianCore, {
138
- props: { itemCount: 2, name: "test-accordian" },
146
+ props: { itemCount: 2, name: "test-accordian", variant: "modern" },
139
147
  });
140
148
  wrapper.findAllComponents({ name: "ExpandingPanel" }).forEach((panel) => {
141
149
  expect(panel.props("name")).toBe("test-accordian");
@@ -144,7 +152,7 @@ describe("AccordianCore", () => {
144
152
 
145
153
  it("passes accordian-item styleClassPassthrough to every ExpandingPanel", async () => {
146
154
  const wrapper = await mountSuspended(AccordianCore, {
147
- props: { itemCount: 2 },
155
+ props: { itemCount: 2, variant: "modern" },
148
156
  });
149
157
  wrapper.findAllComponents({ name: "ExpandingPanel" }).forEach((panel) => {
150
158
  expect(panel.props("styleClassPassthrough")).toEqual(["accordian-item"]);
@@ -153,11 +161,11 @@ describe("AccordianCore", () => {
153
161
 
154
162
  // ─── Default prop values ──────────────────────────────────────────────────
155
163
 
156
- it("defaults animationDuration to 300 on ExpandingPanel when not provided", async () => {
164
+ it("defaults animationDuration to 300 on ExpandingPanelClassic when not provided", async () => {
157
165
  const wrapper = await mountSuspended(AccordianCore, {
158
166
  props: { itemCount: 1 },
159
167
  });
160
- const panel = wrapper.findComponent({ name: "ExpandingPanel" });
168
+ const panel = wrapper.findComponent({ name: "ExpandingPanelClassic" });
161
169
  expect(panel.props("animationDuration")).toBe(300);
162
170
  });
163
171
 
@@ -167,7 +175,7 @@ describe("AccordianCore", () => {
167
175
  expect(vm.name).toBeUndefined();
168
176
  expect(vm.itemCount).toBe(0);
169
177
  expect(vm.animationDuration).toBe(300);
170
- expect(vm.variant).toBe("modern");
178
+ expect(vm.variant).toBe("classic");
171
179
  expect(vm.styleClassPassthrough).toEqual([]);
172
180
  });
173
181
 
@@ -241,7 +249,7 @@ describe("AccordianCore", () => {
241
249
  "accordian-2-summary": "<span>Gamma</span>",
242
250
  },
243
251
  });
244
- const panels = wrapper.findAllComponents({ name: "ExpandingPanel" });
252
+ const panels = wrapper.findAllComponents({ name: "ExpandingPanelClassic" });
245
253
  expect(panels[0]?.text()).toContain("Alpha");
246
254
  expect(panels[1]?.text()).toContain("Beta");
247
255
  expect(panels[2]?.text()).toContain("Gamma");
@@ -2,78 +2,92 @@
2
2
 
3
3
  exports[`AccordianCore > renders correct HTML structure with all props set 1`] = `
4
4
  "<div class="display-accordian extra-class">
5
- <div class="expanding-panel accordian-item" icon-size="medium">
6
- <details class="expanding-panel-details" name="snapshot-accordian">
7
- <summary id="id-snapshot-accordian-trigger" class="expanding-panel-summary" aria-controls="id-snapshot-accordian-content" aria-expanded="false">
5
+ <div class="expanding-panel-classic accordian-item" icon-size="medium">
6
+ <details class="expanding-panel-classic-details" name="snapshot-accordian">
7
+ <summary id="id-snapshot-accordian-trigger" class="expanding-panel-classic-summary" aria-controls="id-snapshot-accordian-content" aria-expanded="false">
8
8
  <div class="label-wrapper"></div>
9
9
  <div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" 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 class="inner"></div></div>
12
11
  </details>
12
+ <div id="id-snapshot-accordian-content" class="expanding-panel-classic-content" aria-labelledby="id-snapshot-accordian-trigger" role="region">
13
+ <div class="inner"></div>
14
+ </div>
13
15
  </div>
14
- <div class="expanding-panel accordian-item" icon-size="medium">
15
- <details class="expanding-panel-details" name="snapshot-accordian">
16
- <summary id="id-snapshot-accordian-trigger" class="expanding-panel-summary" aria-controls="id-snapshot-accordian-content" aria-expanded="false">
16
+ <div class="expanding-panel-classic accordian-item" icon-size="medium">
17
+ <details class="expanding-panel-classic-details" name="snapshot-accordian">
18
+ <summary id="id-snapshot-accordian-trigger" class="expanding-panel-classic-summary" aria-controls="id-snapshot-accordian-content" aria-expanded="false">
17
19
  <div class="label-wrapper"></div>
18
20
  <div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
19
21
  </summary>
20
- <div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"><div class="inner"></div></div>
21
22
  </details>
23
+ <div id="id-snapshot-accordian-content" class="expanding-panel-classic-content" aria-labelledby="id-snapshot-accordian-trigger" role="region">
24
+ <div class="inner"></div>
25
+ </div>
22
26
  </div>
23
- <div class="expanding-panel accordian-item" icon-size="medium">
24
- <details class="expanding-panel-details" name="snapshot-accordian">
25
- <summary id="id-snapshot-accordian-trigger" class="expanding-panel-summary" aria-controls="id-snapshot-accordian-content" aria-expanded="false">
27
+ <div class="expanding-panel-classic accordian-item" icon-size="medium">
28
+ <details class="expanding-panel-classic-details" name="snapshot-accordian">
29
+ <summary id="id-snapshot-accordian-trigger" class="expanding-panel-classic-summary" aria-controls="id-snapshot-accordian-content" aria-expanded="false">
26
30
  <div class="label-wrapper"></div>
27
31
  <div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
28
32
  </summary>
29
- <div id="id-snapshot-accordian-content" class="expanding-panel-content" aria-labelledby="id-snapshot-accordian-trigger" role="region"><div class="inner"></div></div>
30
33
  </details>
34
+ <div id="id-snapshot-accordian-content" class="expanding-panel-classic-content" aria-labelledby="id-snapshot-accordian-trigger" role="region">
35
+ <div class="inner"></div>
36
+ </div>
31
37
  </div>
32
38
  </div>"
33
39
  `;
34
40
 
35
41
  exports[`AccordianCore > renders correct HTML structure with default props 1`] = `
36
42
  "<div class="display-accordian">
37
- <div class="expanding-panel accordian-item" icon-size="medium">
38
- <details class="expanding-panel-details" name="v-0-0-0">
39
- <summary id="id-v-0-0-0-trigger" class="expanding-panel-summary" aria-controls="id-v-0-0-0-content" aria-expanded="false">
43
+ <div class="expanding-panel-classic accordian-item" icon-size="medium">
44
+ <details class="expanding-panel-classic-details" name="v-0-0-0">
45
+ <summary id="id-v-0-0-0-trigger" class="expanding-panel-classic-summary" aria-controls="id-v-0-0-0-content" aria-expanded="false">
40
46
  <div class="label-wrapper"></div>
41
47
  <div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
42
48
  </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 class="inner"></div></div>
44
49
  </details>
50
+ <div id="id-v-0-0-0-content" class="expanding-panel-classic-content" aria-labelledby="id-v-0-0-0-trigger" role="region">
51
+ <div class="inner"></div>
52
+ </div>
45
53
  </div>
46
- <div class="expanding-panel accordian-item" icon-size="medium">
47
- <details class="expanding-panel-details" name="v-0-0-1">
48
- <summary id="id-v-0-0-1-trigger" class="expanding-panel-summary" aria-controls="id-v-0-0-1-content" aria-expanded="false">
54
+ <div class="expanding-panel-classic accordian-item" icon-size="medium">
55
+ <details class="expanding-panel-classic-details" name="v-0-0-1">
56
+ <summary id="id-v-0-0-1-trigger" class="expanding-panel-classic-summary" aria-controls="id-v-0-0-1-content" aria-expanded="false">
49
57
  <div class="label-wrapper"></div>
50
58
  <div class="icon-wrapper"><span class="iconify i-bi:caret-down-fill icon" aria-hidden="true"></span></div>
51
59
  </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 class="inner"></div></div>
53
60
  </details>
61
+ <div id="id-v-0-0-1-content" class="expanding-panel-classic-content" aria-labelledby="id-v-0-0-1-trigger" role="region">
62
+ <div class="inner"></div>
63
+ </div>
54
64
  </div>
55
65
  </div>"
56
66
  `;
57
67
 
58
68
  exports[`AccordianCore > renders correct HTML structure with populated slots 1`] = `
59
69
  "<div class="display-accordian">
60
- <div class="expanding-panel accordian-item" icon-size="medium">
61
- <details class="expanding-panel-details" name="v-0-0-0">
62
- <summary id="id-v-0-0-0-trigger" class="expanding-panel-summary" aria-controls="id-v-0-0-0-content" aria-expanded="false">
70
+ <div class="expanding-panel-classic accordian-item" icon-size="medium">
71
+ <details class="expanding-panel-classic-details" name="v-0-0-0">
72
+ <summary id="id-v-0-0-0-trigger" class="expanding-panel-classic-summary" aria-controls="id-v-0-0-0-content" aria-expanded="false">
63
73
  <div class="label-wrapper"><span>Summary 0</span></div>
64
74
  <div class="icon-wrapper"><span>Icon 0</span></div>
65
75
  </summary>
66
- <div id="id-v-0-0-0-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-0-trigger" role="region"><div class="inner"><span>Content 0</span></div></div>
67
76
  </details>
77
+ <div id="id-v-0-0-0-content" class="expanding-panel-classic-content" aria-labelledby="id-v-0-0-0-trigger" role="region">
78
+ <div class="inner"><span>Content 0</span></div>
79
+ </div>
68
80
  </div>
69
- <div class="expanding-panel accordian-item" icon-size="medium">
70
- <details class="expanding-panel-details" name="v-0-0-1">
71
- <summary id="id-v-0-0-1-trigger" class="expanding-panel-summary" aria-controls="id-v-0-0-1-content" aria-expanded="false">
81
+ <div class="expanding-panel-classic accordian-item" icon-size="medium">
82
+ <details class="expanding-panel-classic-details" name="v-0-0-1">
83
+ <summary id="id-v-0-0-1-trigger" class="expanding-panel-classic-summary" aria-controls="id-v-0-0-1-content" aria-expanded="false">
72
84
  <div class="label-wrapper"><span>Summary 1</span></div>
73
85
  <div class="icon-wrapper"><span>Icon 1</span></div>
74
86
  </summary>
75
- <div id="id-v-0-0-1-content" class="expanding-panel-content" aria-labelledby="id-v-0-0-1-trigger" role="region"><div class="inner"><span>Content 1</span></div></div>
76
87
  </details>
88
+ <div id="id-v-0-0-1-content" class="expanding-panel-classic-content" aria-labelledby="id-v-0-0-1-trigger" role="region">
89
+ <div class="inner"><span>Content 1</span></div>
90
+ </div>
77
91
  </div>
78
92
  </div>"
79
93
  `;
@@ -90,16 +90,14 @@ visual style, pass a modifier class:
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
92
  - `background-color`, `padding`, `border`, and shadow are all safe to set directly on
93
- `.expanding-panel-content`. Where `::details-content` is supported (not WebKit see below), the
94
- open/close animation clips via `overflow: clip` on that pseudo-element, and
95
- `.expanding-panel-content` is a child of it, so its own box is correctly clipped to the animated
96
- height while closed/closing. A wrapper inside the `#content` slot works equally well if you
97
- prefer to keep the styling scoped to your own markup.
98
- - WebKit (Safari/iOS) doesn't support `::details-content``ExpandingPanel` falls back to
99
- `ExpandingPanelClassic`'s `grid-template-rows` technique there automatically, no consumer action
100
- needed. That path wraps the `#content` slot in an internal `.inner` div (an implementation
101
- detail of the collapse mechanism, not a styling hook) but styling on `.expanding-panel-content`
102
- itself still works correctly, same as the `::details-content` path.
93
+ `.expanding-panel-content`. The open/close animation clips via `overflow: clip` on
94
+ `::details-content`, and `.expanding-panel-content` is a child of it, so its own box is
95
+ correctly clipped to the animated height while closed/closing. A wrapper inside the `#content`
96
+ slot works equally well if you prefer to keep the styling scoped to your own markup.
97
+ - **WebKit (Safari/iOS) doesn't support `::details-content` at all**, and `ExpandingPanel` has no
98
+ fallback for it (an `@supports`-based CSS fallback was tried and reverted 2026-08-22it didn't
99
+ reliably work). If this panel needs to work correctly on real Apple devices, use
100
+ `ExpandingPanelClassic` instead see its own `CONSUMER-STYLING.md`.
103
101
  - Don't stack multiple `contentIsOnTop` panels as direct siblings (linked via a shared `name` or
104
102
  not). The overlay is absolutely positioned so it doesn't push the next element down — which
105
103
  means a sibling `ExpandingPanel` placed right after it sits exactly where the overlay renders,
@@ -23,9 +23,7 @@
23
23
  :aria-labelledby="`id-${name}-trigger`"
24
24
  role="region"
25
25
  >
26
- <div class="inner">
27
- <slot name="content"></slot>
28
- </div>
26
+ <slot name="content"></slot>
29
27
  </div>
30
28
  </details>
31
29
  </div>
@@ -66,6 +64,20 @@ onClickOutside(rootEl, () => {
66
64
  });
67
65
 
68
66
  if (import.meta.dev) {
67
+ // ::details-content (the animation/positioning mechanism this component relies on) is not
68
+ // supported in WebKit (Safari/iOS) as of 2026-08 — confirmed via real-device testing, not
69
+ // reproducible in Chrome's device-emulation mode since that's still the Blink engine. An
70
+ // earlier @supports-based fallback attempt (2026-08-22) turned out not to reliably kick in
71
+ // either. Prefer ExpandingPanelClassic (or a consumer's own `classic` prop, where offered —
72
+ // see AccordianCore) for anything that needs to work correctly on real Apple devices today.
73
+ console.warn(
74
+ `ExpandingPanel${props.name ? ` "${props.name}"` : ""}: this variant depends on ` +
75
+ "::details-content, which WebKit (Safari/iOS) does not support — the animation and, more " +
76
+ "seriously, contentIsOnTop's overlay positioning silently fail there with no console error. " +
77
+ "Use ExpandingPanelClassic instead if this panel needs to work correctly on real Apple " +
78
+ "devices."
79
+ );
80
+
69
81
  watch(
70
82
  () => props.contentIsOnTop,
71
83
  (value) => {
@@ -189,50 +201,6 @@ const onDetailsToggle = (event: Event) => {
189
201
  }
190
202
  }
191
203
  }
192
-
193
- /* ::details-content isn't supported in WebKit (Safari/iOS) as of 2026-08 — the two rule
194
- blocks above are silently dropped there entirely (no error, no animation, and crucially
195
- content-is-on-top's position: absolute never applies, so the overlay renders in normal
196
- document flow instead). This reproduces both using ExpandingPanelClassic's
197
- grid-template-rows technique (broadly supported, including Safari), targeting the real
198
- .expanding-panel-content element (and its .inner child, matching Classic's structure —
199
- .inner's own overflow:hidden is what lets the grid row actually collapse to 0, since a
200
- plain block child's default min-height:auto would otherwise floor it at content height)
201
- instead of the pseudo-element. */
202
- @supports not selector(::details-content) {
203
- .expanding-panel-details {
204
- .expanding-panel-content {
205
- display: grid;
206
- grid-template-rows: 0fr;
207
- transition: grid-template-rows v-bind(animationDurationStr) ease-in-out;
208
- will-change: grid-template-rows;
209
-
210
- .inner {
211
- overflow: hidden;
212
- margin-top: 0;
213
- }
214
- }
215
-
216
- &[open] .expanding-panel-content {
217
- grid-template-rows: 1fr;
218
- }
219
- }
220
- }
221
-
222
- &.content-is-on-top {
223
- @supports not selector(::details-content) {
224
- .expanding-panel-details {
225
- position: relative;
226
-
227
- .expanding-panel-content {
228
- position: absolute;
229
- top: calc(100% + var(--expanding-panel-content-gap, 0px));
230
- inset-inline: 0;
231
- z-index: var(--expanding-panel-content-z-index, 10);
232
- }
233
- }
234
- }
235
- }
236
204
  }
237
205
  }
238
206
  </style>
@@ -7,9 +7,7 @@ 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" 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">
11
- <div class="inner"></div>
12
- </div>
10
+ <div id="id-snap-all-content" class="expanding-panel-content" aria-labelledby="id-snap-all-trigger" role="region"></div>
13
11
  </details>
14
12
  </div>"
15
13
  `;
@@ -21,9 +19,7 @@ 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" aria-hidden="true"></span></div>
23
21
  </summary>
24
- <div id="id-snap-content-on-top-content" class="expanding-panel-content" aria-labelledby="id-snap-content-on-top-trigger" role="region">
25
- <div class="inner"></div>
26
- </div>
22
+ <div id="id-snap-content-on-top-content" class="expanding-panel-content" aria-labelledby="id-snap-content-on-top-trigger" role="region"></div>
27
23
  </details>
28
24
  </div>"
29
25
  `;
@@ -35,9 +31,7 @@ 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" aria-hidden="true"></span></div>
37
33
  </summary>
38
- <div id="id-snap-default-content" class="expanding-panel-content" aria-labelledby="id-snap-default-trigger" role="region">
39
- <div class="inner"></div>
40
- </div>
34
+ <div id="id-snap-default-content" class="expanding-panel-content" aria-labelledby="id-snap-default-trigger" role="region"></div>
41
35
  </details>
42
36
  </div>"
43
37
  `;
@@ -50,9 +44,7 @@ exports[`ExpandingPanel > renders correct HTML structure with populated slots 1`
50
44
  <div class="icon-wrapper"><span>▸</span></div>
51
45
  </summary>
52
46
  <div id="id-snap-slots-content" class="expanding-panel-content" aria-labelledby="id-snap-slots-trigger" role="region">
53
- <div class="inner">
54
- <p>Content text</p>
55
- </div>
47
+ <p>Content text</p>
56
48
  </div>
57
49
  </details>
58
50
  </div>"
@@ -56,7 +56,8 @@
56
56
  role="none"
57
57
  @mouseenter="hoveredItemKey = `${String(groupKey)}-${localIndex}`"
58
58
  >
59
- <ExpandingPanel
59
+ <component
60
+ :is="panelComponent"
60
61
  v-model="panelOpenStates[`${String(groupKey)}-${localIndex}`]"
61
62
  name="overflow-navigation-group"
62
63
  :animation-duration="DETAILS_ANIMATION_DURATION"
@@ -96,7 +97,7 @@
96
97
  <div aria-hidden="true" class="overflow-sub-nav-indicator-hovered"></div>
97
98
  </div>
98
99
  </template>
99
- </ExpandingPanel>
100
+ </component>
100
101
  </li>
101
102
  </template>
102
103
  </ul>
@@ -107,17 +108,26 @@
107
108
 
108
109
  <script setup lang="ts">
109
110
  import type { ResponsiveHeaderState, ResponsiveHeaderNavItem } from "../../../types/components";
111
+ import ExpandingPanel from "../../02.molecules/expandable/expanding-panel/ExpandingPanel.vue";
112
+ import ExpandingPanelClassic from "../../02.molecules/expandable/expanding-panel-classic/ExpandingPanelClassic.vue";
110
113
 
111
114
  interface Props {
112
115
  mainNavigationState?: ResponsiveHeaderState;
116
+ panelVariant?: "modern" | "classic";
113
117
  styleClassPassthrough?: string | string[];
114
118
  }
115
119
 
116
120
  const props = withDefaults(defineProps<Props>(), {
117
121
  mainNavigationState: () => ({ clonedNavLinks: {}, navListVisibility: {}, hasSecondNav: false }),
122
+ // Defaults to "classic" — see ExpandingPanel's own dev-mode warning and CLAUDE.md pitfall #19:
123
+ // its ::details-content-based implementation doesn't work correctly on WebKit (Safari/iOS).
124
+ // "modern" is opt-in.
125
+ panelVariant: "classic",
118
126
  styleClassPassthrough: () => [],
119
127
  });
120
128
 
129
+ const panelComponent = computed(() => (props.panelVariant === "modern" ? ExpandingPanel : ExpandingPanelClassic));
130
+
121
131
  const hoveredItemKey = ref<string | null>(null);
122
132
  const hoveredChildKey = ref<string | null>(null);
123
133
 
@@ -231,6 +241,12 @@ watch(
231
241
  }
232
242
 
233
243
  .overflow-navigation-details {
244
+ /* Two variants share this styling, one full block each rather than a merged
245
+ selector list — their DOM shapes differ (modern nests .expanding-panel-content
246
+ *inside* .expanding-panel-details; classic renders .expanding-panel-classic-content
247
+ as a *sibling* of .expanding-panel-classic-details), so the [open] + ... sibling
248
+ combinator below is only structurally correct for the classic block. See
249
+ ExpandingPanel vs ExpandingPanelClassic's own templates. */
234
250
  &.expanding-panel {
235
251
  margin-block-end: 0;
236
252
 
@@ -293,6 +309,69 @@ watch(
293
309
  }
294
310
  }
295
311
  }
312
+
313
+ &.expanding-panel-classic {
314
+ margin-block-end: 0;
315
+
316
+ .expanding-panel-classic-details {
317
+ .expanding-panel-classic-summary {
318
+ padding-block: var(--overflow-nav-items-padding-block, 0.8rem);
319
+ padding-inline: var(--overflow-nav-padding-inline, 0.8rem);
320
+ gap: 1rem;
321
+ color: var(--overflow-nav-link-color, inherit);
322
+ border-bottom: 0.1rem solid var(--overflow-nav-link-border-color, #efefef75);
323
+
324
+ .label-wrapper {
325
+ .overflow-navigation-text {
326
+ text-wrap: nowrap;
327
+ }
328
+ }
329
+ .icon-wrapper {
330
+ padding: 0;
331
+ }
332
+ }
333
+
334
+ &[open] {
335
+ .expanding-panel-classic-summary {
336
+ border-bottom: 0.1rem solid transparent;
337
+ }
338
+ + .expanding-panel-classic-content {
339
+ border-bottom: 0.1rem solid var(--overflow-nav-link-border-color, #efefef75);
340
+ .overflow-navigation-sub-nav-inner {
341
+ margin-top: var(--overflow-nav-items-gap, 0px);
342
+ }
343
+ }
344
+ }
345
+ }
346
+
347
+ .expanding-panel-classic-content {
348
+ border-bottom: 0.1rem solid transparent;
349
+
350
+ .overflow-navigation-sub-nav-inner {
351
+ margin-top: 0;
352
+ position: relative;
353
+
354
+ .overflow-navigation-sub-nav-list {
355
+ display: flex;
356
+ flex-direction: column;
357
+ gap: 2px;
358
+
359
+ .overflow-navigation-sub-nav-item {
360
+ padding-block: var(--overflow-nav-items-padding-block, 0.8rem);
361
+ padding-inline: var(--overflow-nav-padding-inline, 0.8rem);
362
+ font-size: var(--overflow-nav-sub-item-font-size, inherit);
363
+ color: var(--overflow-nav-sub-item-color, inherit);
364
+
365
+ .overflow-navigation-sub-nav-link {
366
+ display: block;
367
+ text-decoration: none;
368
+ color: inherit;
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
296
375
  }
297
376
  }
298
377
  }
@@ -325,7 +404,11 @@ watch(
325
404
  .overflow-navigation-wrapper
326
405
  .overflow-navigation-item.is-hovered
327
406
  .overflow-navigation-details
328
- .expanding-panel-summary {
407
+ .expanding-panel-summary,
408
+ .overflow-navigation-wrapper
409
+ .overflow-navigation-item.is-hovered
410
+ .overflow-navigation-details
411
+ .expanding-panel-classic-summary {
329
412
  anchor-name: --overflow-nav-indicator;
330
413
  }
331
414
 
@@ -340,7 +423,11 @@ watch(
340
423
  .overflow-navigation-wrapper:not(:has(.overflow-navigation-item.is-hovered))
341
424
  .overflow-navigation-item.is-active
342
425
  .overflow-navigation-details
343
- .expanding-panel-summary {
426
+ .expanding-panel-summary,
427
+ .overflow-navigation-wrapper:not(:has(.overflow-navigation-item.is-hovered))
428
+ .overflow-navigation-item.is-active
429
+ .overflow-navigation-details
430
+ .expanding-panel-classic-summary {
344
431
  anchor-name: --overflow-nav-indicator;
345
432
  }
346
433
 
@@ -100,7 +100,7 @@
100
100
  />
101
101
  </summary>
102
102
  <div class="overflow-details-nav" role="menu">
103
- <NavigationItems :main-navigation-state="mainNavigationState" />
103
+ <NavigationItems :main-navigation-state="mainNavigationState" :panel-variant="panelVariant" />
104
104
  </div>
105
105
  </details>
106
106
  <slot v-if="slots.secondaryNavigation" name="secondaryNavigation"></slot>
@@ -125,6 +125,7 @@ interface Props {
125
125
  collapseAtMainNavIntersection?: boolean;
126
126
  styleClassPassthrough?: string | string[];
127
127
  allowExpandOnGesture?: boolean;
128
+ panelVariant?: "modern" | "classic";
128
129
  }
129
130
 
130
131
  const props = withDefaults(defineProps<Props>(), {
@@ -138,6 +139,9 @@ const props = withDefaults(defineProps<Props>(), {
138
139
  collapseAtMainNavIntersection: false,
139
140
  styleClassPassthrough: () => [],
140
141
  allowExpandOnGesture: true,
142
+ // Forwarded to NavigationItems' overflow submenu panels — see its own default for why
143
+ // "classic" is the safe default (CLAUDE.md pitfall #19).
144
+ panelVariant: "classic",
141
145
  });
142
146
 
143
147
  const collapseNavigationBelowWidth = computed(
@@ -138,10 +138,10 @@ describe("NavigationItems", () => {
138
138
  const wrapper = await mountSuspended(NavigationItems, {
139
139
  props: { mainNavigationState },
140
140
  });
141
- const details = wrapper.find(".expanding-panel-details");
141
+ const details = wrapper.find(".expanding-panel-classic-details");
142
142
  expect(details.attributes("open")).toBeUndefined();
143
143
 
144
- await wrapper.find(".expanding-panel-summary").trigger("click");
144
+ await wrapper.find(".expanding-panel-classic-summary").trigger("click");
145
145
  await nextTick();
146
146
  expect(details.attributes("open")).toBe("");
147
147
  });
@@ -6,24 +6,24 @@ exports[`NavigationItems > renders correct HTML structure with default props 1`]
6
6
  <li class="overflow-navigation-item is-active" style="--_main-navigation-item-width: 0px;" data-group-key="firstNav" data-local-index="0" role="none"><a href="/" class="overflow-navigation-link" role="menuitem"><span class="overflow-navigation-text">Home</span></a></li>
7
7
  <li class="overflow-navigation-item visible" style="--_main-navigation-item-width: 0px;" data-group-key="firstNav" data-local-index="1" role="none"><a href="/about" class="overflow-navigation-link" role="menuitem"><span class="overflow-navigation-text">About</span></a></li>
8
8
  <li class="overflow-navigation-item visible" style="--_main-navigation-item-width: 0px;" data-group-key="firstNav" data-local-index="2" role="none">
9
- <div class="expanding-panel overflow-navigation-details">
10
- <details class="expanding-panel-details" name="overflow-navigation-group">
11
- <summary id="id-overflow-navigation-group-trigger" class="expanding-panel-summary" aria-controls="id-overflow-navigation-group-content" aria-expanded="false">
9
+ <div class="expanding-panel-classic overflow-navigation-details">
10
+ <details class="expanding-panel-classic-details" name="overflow-navigation-group">
11
+ <summary id="id-overflow-navigation-group-trigger" class="expanding-panel-classic-summary" aria-controls="id-overflow-navigation-group-content" aria-expanded="false">
12
12
  <div class="label-wrapper"><span class="overflow-navigation-text" aria-label="UI Components submenu" role="menuitem" aria-haspopup="true">UI Components</span></div>
13
13
  <div class="icon-wrapper"><span class="iconify i-mdi:chevron-down icon" aria-hidden="true"></span></div>
14
14
  </summary>
15
- <div id="id-overflow-navigation-group-content" class="expanding-panel-content" aria-labelledby="id-overflow-navigation-group-trigger" role="region">
16
- <div class="inner">
17
- <div class="overflow-navigation-sub-nav-inner">
18
- <ul class="overflow-navigation-sub-nav-list">
19
- <li class="overflow-navigation-sub-nav-item"><a href="/forms/examples/buttons" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Buttons</span></a></li>
20
- <li class="overflow-navigation-sub-nav-item"><a href="/ui/tabs" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Tabs</span></a></li>
21
- </ul>
22
- <div aria-hidden="true" class="overflow-sub-nav-indicator-hovered"></div>
23
- </div>
15
+ </details>
16
+ <div id="id-overflow-navigation-group-content" class="expanding-panel-classic-content" aria-labelledby="id-overflow-navigation-group-trigger" role="region">
17
+ <div class="inner">
18
+ <div class="overflow-navigation-sub-nav-inner">
19
+ <ul class="overflow-navigation-sub-nav-list">
20
+ <li class="overflow-navigation-sub-nav-item"><a href="/forms/examples/buttons" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Buttons</span></a></li>
21
+ <li class="overflow-navigation-sub-nav-item"><a href="/ui/tabs" class="overflow-navigation-sub-nav-link" role="menuitem"><span class="overflow-navigation-sub-nav-text">Tabs</span></a></li>
22
+ </ul>
23
+ <div aria-hidden="true" class="overflow-sub-nav-indicator-hovered"></div>
24
24
  </div>
25
25
  </div>
26
- </details>
26
+ </div>
27
27
  </div>
28
28
  </li>
29
29
  </ul>