srcdev-nuxt-components 9.6.0 → 9.6.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.
Files changed (107) hide show
  1. package/.claude/skills/component-compliance-checklist.md +8 -2
  2. package/.claude/skills/component-dynamic-slots.md +3 -3
  3. package/.claude/skills/component-export-types.md +10 -16
  4. package/.claude/skills/component-naming.md +1 -1
  5. package/.claude/skills/components/alert-content.md +9 -1
  6. package/.claude/skills/components/container-glow.md +21 -8
  7. package/.claude/skills/components/display-avatar.md +13 -7
  8. package/.claude/skills/components/display-chip.md +37 -24
  9. package/.claude/skills/components/display-pill.md +58 -10
  10. package/.claude/skills/components/display-prompt.md +8 -5
  11. package/.claude/skills/components/display-toast.md +21 -4
  12. package/.claude/skills/components/glowing-border.md +20 -2
  13. package/.claude/skills/components/responsive-header.md +11 -1
  14. package/.claude/skills/components/select-menu.md +54 -5
  15. package/.claude/skills/components/site-header.md +2 -2
  16. package/.claude/skills/components/tabbed-content.md +300 -0
  17. package/.claude/skills/composable-anchor-scroll.md +16 -15
  18. package/.claude/skills/index.md +4 -4
  19. package/.claude/skills/storybook-add-story.md +53 -0
  20. package/.claude/skills/theming-status-tokens.md +60 -0
  21. package/.vscode/srcdev-component-display-avatar.code-snippets +2 -1
  22. package/.vscode/srcdev-component-display-chip.code-snippets +3 -2
  23. package/.vscode/srcdev-component-display-pill.code-snippets +10 -0
  24. package/.vscode/srcdev-component-responsive-header.code-snippets +6 -0
  25. package/.vscode/srcdev-component-select-menu.code-snippets +28 -0
  26. package/.vscode/{srcdev-component-tabs-core.code-snippets → srcdev-component-tabbed-content.code-snippets} +35 -16
  27. package/app/assets/styles/setup/03.theming/_status.css +24 -0
  28. package/app/assets/styles/setup/03.theming/index.css +1 -0
  29. package/app/components/01.atoms/animations/container-glow/CONSUMER-STYLING.md +39 -35
  30. package/app/components/01.atoms/animations/container-glow/ContainerGlow.vue +25 -15
  31. package/app/components/01.atoms/animations/container-glow/stories/ContainerGlow.stories.ts +130 -24
  32. package/app/components/01.atoms/animations/container-glow/tests/ContainerGlow.spec.ts +45 -0
  33. package/app/components/01.atoms/animations/container-glow/tests/__snapshots__/ContainerGlow.spec.ts.snap +2 -2
  34. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +76 -19
  35. package/app/components/01.atoms/animations/glowing-border/GlowingBorder.vue +12 -10
  36. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +201 -42
  37. package/app/components/01.atoms/animations/glowing-border/tests/GlowingBorder.spec.ts +15 -4
  38. package/app/components/01.atoms/animations/glowing-border/tests/__snapshots__/GlowingBorder.spec.ts.snap +1 -1
  39. package/app/components/01.atoms/display-pill/CONSUMER-STYLING.md +75 -9
  40. package/app/components/01.atoms/display-pill/DisplayPill.vue +57 -24
  41. package/app/components/01.atoms/display-pill/stories/DisplayPill.stories.ts +240 -16
  42. package/app/components/01.atoms/display-pill/tests/DisplayPill.spec.ts +43 -14
  43. package/app/components/01.atoms/display-pill/tests/__snapshots__/DisplayPill.spec.ts.snap +3 -3
  44. package/app/components/01.atoms/tabbed-content/CONSUMER-STYLING.md +201 -0
  45. package/app/components/01.atoms/tabbed-content/TabbedContent.vue +750 -0
  46. package/app/components/01.atoms/tabbed-content/stories/TabbedContent.stories.ts +279 -0
  47. package/app/components/01.atoms/tabbed-content/tests/TabbedContent.spec.ts +816 -0
  48. package/app/components/02.molecules/alert-content/AlertContent.vue +11 -2
  49. package/app/components/02.molecules/alert-content/AlertContentInner.vue +19 -0
  50. package/app/components/02.molecules/alert-content/CONSUMER-STYLING.md +17 -4
  51. package/app/components/02.molecules/alert-content/stories/AlertContent.stories.ts +200 -17
  52. package/app/components/02.molecules/alert-content/tests/AlertContent.spec.ts +33 -0
  53. package/app/components/02.molecules/display-avatar/CONSUMER-STYLING.md +7 -5
  54. package/app/components/02.molecules/display-avatar/DisplayAvatar.vue +6 -2
  55. package/app/components/02.molecules/display-avatar/stories/DisplayAvatar.stories.ts +24 -3
  56. package/app/components/02.molecules/display-avatar/tests/DisplayAvatar.spec.ts +19 -0
  57. package/app/components/02.molecules/display-avatar/tests/__snapshots__/DisplayAvatar.spec.ts.snap +2 -1
  58. package/app/components/02.molecules/display-chip/CONSUMER-STYLING.md +52 -19
  59. package/app/components/02.molecules/display-chip/DisplayChip.vue +72 -54
  60. package/app/components/02.molecules/display-chip/stories/DisplayChip.stories.ts +103 -6
  61. package/app/components/02.molecules/display-chip/tests/DisplayChip.spec.ts +98 -29
  62. package/app/components/02.molecules/display-chip/tests/__snapshots__/DisplayChip.spec.ts.snap +8 -4
  63. package/app/components/02.molecules/prompt/CONSUMER-STYLING.md +9 -1
  64. package/app/components/02.molecules/prompt/DisplayPrompt.vue +2 -2
  65. package/app/components/02.molecules/prompt/stories/DisplayPrompt.stories.ts +208 -71
  66. package/app/components/02.molecules/prompt/tests/DisplayPrompt.spec.ts +19 -6
  67. package/app/components/02.molecules/select-menu/CONSUMER-STYLING.md +66 -8
  68. package/app/components/02.molecules/select-menu/SelectMenu.vue +141 -18
  69. package/app/components/02.molecules/select-menu/stories/SelectMenu.stories.ts +232 -2
  70. package/app/components/02.molecules/select-menu/tests/SelectMenu.spec.ts +151 -0
  71. package/app/components/02.molecules/select-menu/tests/__snapshots__/SelectMenu.spec.ts.snap +6 -2
  72. package/app/components/02.molecules/toast/CONSUMER-STYLING.md +91 -27
  73. package/app/components/02.molecules/toast/DisplayToast.vue +109 -50
  74. package/app/components/02.molecules/toast/DisplayToastProvider.vue +109 -45
  75. package/app/components/02.molecules/toast/stories/DisplayToast.stories.ts +23 -0
  76. package/app/components/02.molecules/toast/stories/DisplayToastProvider.stories.ts +73 -0
  77. package/app/components/02.molecules/toast/tests/DisplayToast.spec.ts +132 -18
  78. package/app/components/02.molecules/toast/tests/DisplayToastProvider.spec.ts +105 -39
  79. package/app/components/03.organisms/responsive-header/CONSUMER-STYLING.md +38 -11
  80. package/app/components/03.organisms/responsive-header/NavigationItems.vue +27 -12
  81. package/app/components/03.organisms/responsive-header/ResponsiveHeader.vue +35 -20
  82. package/app/components/03.organisms/responsive-header/stories/ResponsiveHeader.stories.ts +1 -1
  83. package/app/components/03.organisms/services/service-detail/tests/__snapshots__/ServiceDetail.spec.ts.snap +1 -1
  84. package/app/components/03.organisms/services/service-summary/tests/__snapshots__/ServiceSummary.spec.ts.snap +1 -1
  85. package/app/components/03.organisms/services/services-grids/tests/__snapshots__/ServiceSummaryGrid.spec.ts.snap +3 -3
  86. package/app/composables/useTabs.ts +148 -206
  87. package/app/types/components/display-chip.d.ts +8 -4
  88. package/app/types/components/display-toast.d.ts +2 -0
  89. package/app/types/components/index.ts +0 -1
  90. package/app/types/components/select-menu.d.ts +2 -0
  91. package/app/utils/pausableTimeout.ts +41 -0
  92. package/nuxt.config.ts +1 -1
  93. package/package.json +1 -1
  94. package/.claude/skills/components/tab-navigation.md +0 -288
  95. package/.claude/skills/components/tabs-core.md +0 -187
  96. package/.vscode/srcdev-component-tab-navigation.code-snippets +0 -30
  97. package/app/components/01.atoms/navigation/tabs/CONSUMER-STYLING.md +0 -120
  98. package/app/components/01.atoms/navigation/tabs/TabsCore.vue +0 -272
  99. package/app/components/01.atoms/navigation/tabs/stories/TabsCore.stories.ts +0 -199
  100. package/app/components/01.atoms/navigation/tabs/tests/TabsCore.spec.ts +0 -274
  101. package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +0 -191
  102. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +0 -550
  103. package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +0 -259
  104. package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +0 -379
  105. package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +0 -33
  106. package/app/composables/useNavCollapse.ts +0 -86
  107. package/app/types/components/nav-item.d.ts +0 -11
@@ -1,550 +0,0 @@
1
- <template>
2
- <nav
3
- v-if="visibleItems.length"
4
- ref="navRef"
5
- class="tab-navigation"
6
- :class="[
7
- elementClasses,
8
- `tab-navigation--${navAlign}`,
9
- { 'is-collapsed': isCollapsed, 'is-loaded': isLoaded, 'menu-open': isMenuOpen, 'is-animated': isAnimated },
10
- ]"
11
- :aria-label="ariaLabel"
12
- >
13
- <ul v-if="!isCollapsed || !isLoaded" ref="navListRef" class="tab-nav-list" @mouseleave="hoveredItemHref = null">
14
- <li
15
- v-for="(item, index) in visibleItems"
16
- :key="`${index}-${item.href}`"
17
- :data-href="item.href"
18
- :class="[
19
- item.cssName,
20
- {
21
- 'is-active': item.href?.startsWith('#') ? item.href === activeHash : isActiveItem(item.href),
22
- 'is-hovered': hoveredItemHref === item.href,
23
- },
24
- ]"
25
- @mouseenter="hoveredItemHref = item.href ?? null"
26
- >
27
- <!-- Plain <a> for hash links — keeps Vue Router out of the smooth-scroll path -->
28
- <a
29
- v-if="item.href?.startsWith('#')"
30
- :href="item.href"
31
- class="tab-nav-link"
32
- data-nav-item
33
- @click="(e) => item.href && handleNavClick(e, item.href)"
34
- >
35
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
36
- {{ item.text }}
37
- </a>
38
- <NuxtLink
39
- v-else
40
- :href="item.href"
41
- :external="item.isExternal || undefined"
42
- class="tab-nav-link"
43
- data-nav-item
44
- @click="(e) => item.href && handleNavClick(e, item.href)"
45
- >
46
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
47
- {{ item.text }}
48
- </NuxtLink>
49
- </li>
50
- <li aria-hidden="true" role="none" class="nav-indicator-li"><div class="nav__hovered"></div></li>
51
- <li aria-hidden="true" role="none" class="nav-indicator-li"><div class="nav__active-indicator"></div></li>
52
- </ul>
53
-
54
- <InputButton
55
- v-if="showCollapsed"
56
- class="tab-nav-burger"
57
- :class="{ 'is-open': isMenuOpen }"
58
- variant="tertiary"
59
- :button-text="isMenuOpen ? closeMenuLabel : openMenuLabel"
60
- :aria-expanded="String(isMenuOpen)"
61
- :aria-controls="panelId"
62
- @click="toggleMenu"
63
- >
64
- <template #iconOnly>
65
- <span class="burger-bar" aria-hidden="true"></span>
66
- <span class="burger-bar" aria-hidden="true"></span>
67
- <span class="burger-bar" aria-hidden="true"></span>
68
- </template>
69
- </InputButton>
70
-
71
- <Teleport to="body">
72
- <div
73
- v-if="showCollapsed"
74
- class="tab-nav-backdrop"
75
- :class="{ 'is-open': isMenuOpen }"
76
- aria-hidden="true"
77
- @click="closeMenu"
78
- ></div>
79
- </Teleport>
80
-
81
- <div
82
- v-if="showCollapsed"
83
- :id="panelId"
84
- class="tab-nav-panel"
85
- :class="{ 'is-open': isMenuOpen }"
86
- :inert="!isMenuOpen ? true : undefined"
87
- >
88
- <div class="tab-nav-panel-inner">
89
- <ul class="tab-nav-panel-list">
90
- <li v-for="(item, index) in visibleItems" :key="`${index}-${item.href}`" :class="item.cssName">
91
- <a
92
- v-if="item.href?.startsWith('#')"
93
- :href="item.href"
94
- class="tab-nav-panel-link"
95
- @click="
96
- (e) => {
97
- item.href && handleNavClick(e, item.href);
98
- closeMenu();
99
- }
100
- "
101
- >
102
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
103
- {{ item.text }}
104
- </a>
105
- <NuxtLink
106
- v-else
107
- :href="item.href"
108
- :external="item.isExternal || undefined"
109
- class="tab-nav-panel-link"
110
- @click="
111
- (e) => {
112
- item.href && handleNavClick(e, item.href);
113
- closeMenu();
114
- }
115
- "
116
- >
117
- <Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
118
- {{ item.text }}
119
- </NuxtLink>
120
- </li>
121
- </ul>
122
- </div>
123
- </div>
124
- </nav>
125
- </template>
126
-
127
- <script setup lang="ts">
128
- import type { NavItemData } from "~/types/components";
129
-
130
- interface Props {
131
- navItemData: NavItemData;
132
- navAlign?: "left" | "center" | "right";
133
- styleClassPassthrough?: string | string[];
134
- anchorScrollOffset?: number | (() => number);
135
- /** aria-label on the nav landmark — override for localisation. */
136
- ariaLabel?: string;
137
- /** Burger button label while the menu is closed — override for localisation. */
138
- openMenuLabel?: string;
139
- /** Burger button label while the menu is open — override for localisation. */
140
- closeMenuLabel?: string;
141
- }
142
-
143
- const props = withDefaults(defineProps<Props>(), {
144
- navAlign: "left",
145
- styleClassPassthrough: () => [],
146
- anchorScrollOffset: undefined,
147
- ariaLabel: "Site navigation",
148
- openMenuLabel: "Open navigation menu",
149
- closeMenuLabel: "Close navigation menu",
150
- });
151
-
152
- if (import.meta.dev) {
153
- console.warn(
154
- "TabNavigation is deprecated: use ResponsiveHeader, which also handles #anchor links " +
155
- "(anchorScrollOffset) and collapses item by item. See \"Migrating from TabNavigation\" in " +
156
- ".claude/skills/components/responsive-header.md."
157
- );
158
- }
159
-
160
- // Items with no text would render links with no accessible name.
161
- const visibleItems = computed(() => (props.navItemData.main ?? []).filter((item) => item.text?.trim()));
162
- const panelId = useId();
163
-
164
- const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu } =
165
- useNavCollapse("tab-nav-loaded");
166
-
167
- const { handleNavClick, activeHash } = useAnchorScroll({ offset: props.anchorScrollOffset });
168
-
169
- onMounted(() => {
170
- if (!activeHash.value) {
171
- const firstHashItem = visibleItems.value.find((item) => item.href?.startsWith("#"));
172
- if (firstHashItem?.href) activeHash.value = firstHashItem.href;
173
- }
174
- });
175
-
176
- // ─── Animation gate — disables indicator transitions during route changes ────
177
- // Starts true: CSS anchor positioning resolves before first paint so there is
178
- // no previous position to animate from on initial render.
179
- // Uses flush:"pre" so isAnimated = false lands in the same DOM update as the
180
- // is-active class moving — the browser never sees the anchor shift with
181
- // transitions active.
182
- const isAnimated = ref(true);
183
- const route = useRoute();
184
-
185
- watch(
186
- () => route.path,
187
- () => {
188
- isAnimated.value = false;
189
- requestAnimationFrame(() => {
190
- requestAnimationFrame(() => {
191
- isAnimated.value = true;
192
- });
193
- });
194
- },
195
- { flush: "pre" }
196
- );
197
-
198
- const hoveredItemHref = ref<string | null>(null);
199
- const showCollapsed = computed(() => isCollapsed.value && isLoaded.value);
200
-
201
- const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
202
-
203
- watch(
204
- () => props.styleClassPassthrough,
205
- () => resetElementClasses(props.styleClassPassthrough)
206
- );
207
- </script>
208
-
209
- <style lang="css">
210
- @layer components {
211
- .tab-nav-backdrop {
212
- position: fixed;
213
- inset: 0;
214
- z-index: 10;
215
- background: var(--tab-nav-backdrop-bg, oklch(0% 0 0 / 55%));
216
- backdrop-filter: blur(var(--tab-nav-backdrop-blur, 3px));
217
- opacity: 0;
218
- pointer-events: none;
219
- transition: opacity var(--tab-nav-backdrop-duration, 350ms) ease;
220
-
221
- &.is-open {
222
- opacity: 1;
223
- pointer-events: auto;
224
- }
225
- }
226
-
227
- .tab-navigation {
228
- /* ─── Public token API ────────────────────────────────────────────── */
229
-
230
- /* Horizontal nav */
231
- --_link-color: var(--tab-nav-link-color, var(--theme-text));
232
- --_link-hover-color: var(--tab-nav-link-hover-color, var(--theme-accent));
233
- --_link-size: var(--tab-nav-link-size, 1.6rem);
234
- --_link-tracking: var(--tab-nav-link-tracking, 0.06em);
235
- --_link-weight: var(--tab-nav-link-weight, 400);
236
- --_nav-transition: var(--tab-nav-transition, 250ms ease);
237
-
238
- /* Panel */
239
- --_panel-link-color: var(--tab-nav-panel-link-color, var(--_link-color));
240
- --_panel-slide-duration: var(--tab-nav-panel-slide-duration, 350ms);
241
- --_panel-slide-easing: var(--tab-nav-panel-slide-easing, cubic-bezier(0.4, 0, 0.2, 1));
242
-
243
- /* Burger */
244
- --_burger-bar-height: var(--tab-nav-burger-height, 1.5px);
245
- --_burger-bar-gap: var(--tab-nav-burger-gap, 5px);
246
- --_burger-color: var(--tab-nav-burger-color, var(--_link-color));
247
- --_burger-transition: var(--tab-nav-burger-transition, 300ms ease);
248
-
249
- /* ─────────────────────────────────────────────────────────────────── */
250
-
251
- display: flex;
252
- align-items: center;
253
- min-width: 0;
254
-
255
- /* Hide everything until first measurement to prevent wrong-state flash */
256
- &:not(.is-loaded) {
257
- opacity: 0;
258
- }
259
-
260
- /* ─── Horizontal list ───────────────────────────────────────────── */
261
-
262
- .tab-nav-list {
263
- list-style: none;
264
- margin: 0;
265
- padding: 0;
266
- display: flex;
267
- gap: var(--tab-nav-gap, 2.2rem);
268
- align-items: center;
269
- position: relative;
270
-
271
- .nav-indicator-li {
272
- /* display: contents removes the li's box entirely — children become
273
- direct participants in the flex container, so position: absolute
274
- on .nav__hovered / .nav__active-indicator resolves against
275
- .tab-nav-list (same containing block as the anchor li elements).
276
- Without this, .nav-indicator-li is the containing block, which is
277
- a different scope and Chrome's anchor positioning rejects it. */
278
- display: contents;
279
- }
280
-
281
- /* Indicators hidden by default — shown only with anchor positioning support */
282
- .nav__hovered,
283
- .nav__active-indicator {
284
- display: none;
285
- pointer-events: none;
286
- }
287
-
288
- .tab-nav-link {
289
- display: flex;
290
- align-items: center;
291
- gap: 0.4em;
292
- color: var(--_link-color);
293
- font-size: var(--_link-size);
294
- font-weight: var(--_link-weight);
295
- letter-spacing: var(--_link-tracking);
296
- text-decoration: none;
297
- white-space: nowrap;
298
- padding-block: 0.8rem;
299
- padding-inline: 0.4rem;
300
- position: relative;
301
- z-index: 4;
302
- transition: color var(--_nav-transition);
303
-
304
- &:hover,
305
- &:focus-visible {
306
- color: var(--_link-hover-color);
307
- outline: none;
308
- }
309
-
310
- &:focus-visible {
311
- outline: var(--tab-nav-focus-ring-width, 2px) solid var(--tab-nav-focus-ring-colour, currentColor);
312
- outline-offset: var(--tab-nav-focus-ring-offset, 2px);
313
- }
314
-
315
- &.router-link-exact-active {
316
- color: var(--tab-nav-link-active-color, var(--_link-color));
317
- }
318
- }
319
- }
320
-
321
- /* ─── Alignment variants ────────────────────────────────────────── */
322
-
323
- &.tab-navigation--center .tab-nav-list {
324
- margin-inline: auto;
325
- }
326
-
327
- &.tab-navigation--right .tab-nav-list {
328
- margin-inline-start: auto;
329
- }
330
-
331
- &.is-collapsed {
332
- justify-content: end;
333
- }
334
-
335
- /* ─── Burger button (InputButton) ──────────────────────────── */
336
-
337
- .tab-nav-burger.input-button.icon-only {
338
- margin-inline-start: auto;
339
- color: var(--_burger-color);
340
-
341
- /* Strip all InputButton visual styling */
342
- background: none;
343
- border: none;
344
- outline: none;
345
- text-decoration: none;
346
- padding: 8px;
347
- border-radius: 4px;
348
- transition: outline-color var(--_nav-transition);
349
-
350
- &.icon-only {
351
- aspect-ratio: unset;
352
- border-radius: 4px;
353
-
354
- .btn-icon {
355
- margin: 0;
356
- display: flex;
357
- flex-direction: column;
358
- gap: var(--_burger-bar-gap);
359
- }
360
- }
361
-
362
- &:focus-visible {
363
- outline: 2px solid var(--_burger-color);
364
- outline-offset: 4px;
365
- }
366
- }
367
-
368
- .burger-bar {
369
- display: block;
370
- width: var(--tab-nav-burger-width, 22px);
371
- height: var(--_burger-bar-height);
372
- background: currentColor;
373
- border-radius: 1px;
374
- transform-origin: center;
375
- transition:
376
- transform var(--_burger-transition),
377
- opacity var(--_burger-transition);
378
- }
379
-
380
- .tab-nav-burger.is-open {
381
- .burger-bar:nth-child(1) {
382
- transform: translateY(calc(var(--_burger-bar-height) + var(--_burger-bar-gap))) rotate(45deg);
383
- }
384
-
385
- .burger-bar:nth-child(2) {
386
- opacity: 0;
387
- transform: scaleX(0);
388
- }
389
-
390
- .burger-bar:nth-child(3) {
391
- transform: translateY(calc(-1 * (var(--_burger-bar-height) + var(--_burger-bar-gap)))) rotate(-45deg);
392
- }
393
- }
394
-
395
- /* ─── Mobile drop panel ─────────────────────────────────────────── */
396
-
397
- .tab-nav-panel {
398
- position: absolute;
399
- left: 0;
400
- right: 0;
401
- top: 100%;
402
-
403
- display: grid;
404
- grid-template-rows: 0fr;
405
- border-block-start: 1px solid transparent;
406
- transition:
407
- grid-template-rows var(--_panel-slide-duration) var(--_panel-slide-easing),
408
- border-color var(--_panel-slide-duration) var(--_panel-slide-easing);
409
-
410
- z-index: 1;
411
-
412
- &.is-open {
413
- grid-template-rows: 1fr;
414
- border-block-start-color: var(--tab-nav-panel-border-color, color-mix(in oklch, var(--_link-color) 20%, transparent));
415
- }
416
-
417
- .tab-nav-panel-inner {
418
- overflow: hidden;
419
- background-color: var(--tab-nav-panel-bg, var(--page-bg, var(--theme-surface-subtle)));
420
- }
421
-
422
- .tab-nav-panel-list {
423
- list-style: none;
424
- margin: 0;
425
- padding: 0;
426
-
427
- li {
428
- border-block-end: 1px solid
429
- var(--tab-nav-panel-item-border, color-mix(in oklch, var(--_panel-link-color) 12%, transparent));
430
-
431
- &:last-child {
432
- border-block-end: none;
433
- }
434
-
435
- &:hover {
436
- background-color: var(
437
- --tab-nav-panel-item-hover-bg,
438
- color-mix(in oklch, var(--_panel-link-color) 5%, transparent)
439
- );
440
- }
441
- }
442
-
443
- .tab-nav-panel-link {
444
- display: flex;
445
- align-items: center;
446
- gap: 0.5em;
447
- color: var(--_panel-link-color);
448
- font-size: var(--_link-size);
449
- font-weight: var(--_link-weight);
450
- letter-spacing: var(--_link-tracking);
451
- text-decoration: none;
452
- padding-block: var(--tab-nav-panel-padding-block, 1.4rem);
453
- padding-inline: var(--tab-nav-panel-padding-inline, 1.5rem);
454
- overflow-wrap: anywhere;
455
- min-inline-size: 0;
456
- position: relative;
457
- z-index: 1;
458
- transition: color var(--_nav-transition);
459
-
460
- &:hover,
461
- &:focus-visible {
462
- color: var(--tab-nav-panel-link-hover-color, var(--_link-hover-color));
463
- outline: none;
464
- }
465
-
466
- &:focus-visible {
467
- outline: var(--tab-nav-focus-ring-width, 2px) solid var(--tab-nav-focus-ring-colour, currentColor);
468
- outline-offset: calc(-1 * var(--tab-nav-focus-ring-width, 2px));
469
- }
470
-
471
- &.router-link-exact-active {
472
- color: var(--tab-nav-panel-link-active-color, var(--_panel-link-color));
473
- }
474
- }
475
- }
476
- }
477
- }
478
-
479
- /* ─── Anchor positioning for nav indicators ──────────────────────────────
480
- anchor-name is declared unconditionally so the @oddbird polyfill can
481
- read it on browsers without native support (the polyfill checks for
482
- anchor-name in raw CSS text, but skips rules inside a failing @supports).
483
- The display guard stays inside @supports so indicators are hidden on
484
- truly old browsers where neither native CSS nor the polyfill will work.
485
- Requires Chrome 125+, Edge 125+, Firefox 131+, or the polyfill.
486
- ──────────────────────────────────────────────────────────────────────── */
487
-
488
- /* Single anchor --tab-nav-indicator tracks the hovered item, or falls back
489
- to the active item when nothing is hovered. No dual anchor-names needed. */
490
-
491
- /* Nothing hovered: anchor sits on the active item */
492
- .tab-navigation .tab-nav-list:not(:has(.is-hovered)) li.is-active {
493
- anchor-name: --tab-nav-indicator;
494
- }
495
-
496
- /* Something hovered: anchor sits on the hovered item */
497
- .tab-navigation .tab-nav-list li.is-hovered {
498
- anchor-name: --tab-nav-indicator;
499
- }
500
-
501
- /* @supports (anchor-name: --x) { */
502
- /* Hover highlight: background pill that follows the pointer */
503
- .tab-navigation .tab-nav-list .nav__hovered {
504
- display: block;
505
- position: absolute;
506
- position-anchor: --tab-nav-indicator;
507
- left: anchor(left);
508
- right: anchor(right);
509
- top: 0;
510
- bottom: 0;
511
- opacity: 0;
512
- pointer-events: none;
513
- background: var(--tab-nav-decorator-hovered-bg, transparent);
514
- border-radius: 4px;
515
- z-index: 1;
516
- }
517
-
518
- .tab-navigation.is-animated .tab-nav-list .nav__hovered {
519
- transition:
520
- left 200ms ease,
521
- right 200ms ease,
522
- opacity 150ms ease;
523
- }
524
-
525
- .tab-navigation .tab-nav-list:has(.is-hovered) .nav__hovered {
526
- opacity: 1;
527
- }
528
-
529
- /* Active indicator bar: always follows --tab-nav-indicator */
530
- .tab-navigation .tab-nav-list .nav__active-indicator {
531
- display: block;
532
- position: absolute;
533
- position-anchor: --tab-nav-indicator;
534
- left: anchor(left);
535
- right: anchor(right);
536
- bottom: 0;
537
- height: 2px;
538
- pointer-events: none;
539
- background-color: var(--tab-nav-decorator-indicator-color, var(--theme-accent));
540
- z-index: 3;
541
- }
542
-
543
- .tab-navigation.is-animated .tab-nav-list .nav__active-indicator {
544
- transition:
545
- left 200ms ease,
546
- right 200ms ease;
547
- }
548
- /* } */
549
- }
550
- </style>