srcdev-nuxt-components 9.1.8 → 9.1.10
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/settings.json +3 -1
- package/app/components/02.molecules/navigation/site-navigation/SiteNavigation.vue +47 -92
- package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +484 -0
- package/app/components/02.molecules/navigation/tab-navigation/stories/TabNavigation.stories.ts +340 -0
- package/app/components/02.molecules/navigation/tab-navigation/tests/TabNavigation.spec.ts +335 -0
- package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +27 -0
- package/app/composables/useNavCollapse.ts +90 -0
- package/app/stores/useNavigationStore.ts +113 -0
- package/package.json +1 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
ref="navRef"
|
|
4
|
+
class="tab-navigation"
|
|
5
|
+
:class="[
|
|
6
|
+
elementClasses,
|
|
7
|
+
`tab-navigation--${navAlign}`,
|
|
8
|
+
{ 'is-collapsed': isCollapsed, 'is-loaded': isLoaded, 'menu-open': isMenuOpen },
|
|
9
|
+
]"
|
|
10
|
+
aria-label="Site navigation"
|
|
11
|
+
>
|
|
12
|
+
<ul
|
|
13
|
+
v-if="!isCollapsed || !isLoaded"
|
|
14
|
+
ref="navListRef"
|
|
15
|
+
class="tab-nav-list"
|
|
16
|
+
@mousemove="handleNavMousemove"
|
|
17
|
+
@mouseleave="hoveredItemHref = null"
|
|
18
|
+
>
|
|
19
|
+
<li
|
|
20
|
+
v-for="item in navItemData.main"
|
|
21
|
+
:key="item.href"
|
|
22
|
+
:data-href="item.href"
|
|
23
|
+
:class="[item.cssName, { 'is-active': isActiveItem(item.href), 'is-hovered': hoveredItemHref === item.href }]"
|
|
24
|
+
>
|
|
25
|
+
<NuxtLink
|
|
26
|
+
:href="item.href"
|
|
27
|
+
:external="item.isExternal || undefined"
|
|
28
|
+
class="tab-nav-link"
|
|
29
|
+
data-nav-item
|
|
30
|
+
@click="handleNavLinkClick"
|
|
31
|
+
>
|
|
32
|
+
<Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
|
|
33
|
+
{{ item.text }}
|
|
34
|
+
</NuxtLink>
|
|
35
|
+
</li>
|
|
36
|
+
<li aria-hidden="true" role="none" class="nav-indicator-li"><div class="nav__hovered"></div></li>
|
|
37
|
+
<li aria-hidden="true" role="none" class="nav-indicator-li"><div class="nav__active-indicator"></div></li>
|
|
38
|
+
</ul>
|
|
39
|
+
|
|
40
|
+
<InputButtonCore
|
|
41
|
+
v-if="isCollapsed && isLoaded"
|
|
42
|
+
class="tab-nav-burger"
|
|
43
|
+
:class="{ 'is-open': isMenuOpen }"
|
|
44
|
+
variant="tertiary"
|
|
45
|
+
:button-text="isMenuOpen ? 'Close navigation menu' : 'Open navigation menu'"
|
|
46
|
+
:aria-expanded="String(isMenuOpen)"
|
|
47
|
+
aria-controls="tab-nav-panel"
|
|
48
|
+
@click="toggleMenu"
|
|
49
|
+
>
|
|
50
|
+
<template #iconOnly>
|
|
51
|
+
<span class="burger-bar" aria-hidden="true"></span>
|
|
52
|
+
<span class="burger-bar" aria-hidden="true"></span>
|
|
53
|
+
<span class="burger-bar" aria-hidden="true"></span>
|
|
54
|
+
</template>
|
|
55
|
+
</InputButtonCore>
|
|
56
|
+
|
|
57
|
+
<Teleport to="body">
|
|
58
|
+
<div
|
|
59
|
+
v-if="isCollapsed && isLoaded"
|
|
60
|
+
class="tab-nav-backdrop"
|
|
61
|
+
:class="{ 'is-open': isMenuOpen }"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
@click="closeMenu"
|
|
64
|
+
></div>
|
|
65
|
+
</Teleport>
|
|
66
|
+
|
|
67
|
+
<div
|
|
68
|
+
v-if="isCollapsed && isLoaded"
|
|
69
|
+
id="tab-nav-panel"
|
|
70
|
+
class="tab-nav-panel"
|
|
71
|
+
:class="{ 'is-open': isMenuOpen }"
|
|
72
|
+
:inert="!isMenuOpen ? true : undefined"
|
|
73
|
+
>
|
|
74
|
+
<div class="tab-nav-panel-inner">
|
|
75
|
+
<ul class="tab-nav-panel-list">
|
|
76
|
+
<li v-for="item in navItemData.main" :key="item.href" :class="item.cssName">
|
|
77
|
+
<NuxtLink
|
|
78
|
+
:href="item.href"
|
|
79
|
+
:external="item.isExternal || undefined"
|
|
80
|
+
class="tab-nav-panel-link"
|
|
81
|
+
@click="handlePanelLinkClick"
|
|
82
|
+
>
|
|
83
|
+
<Icon v-if="item.iconName" :name="item.iconName" aria-hidden="true" />
|
|
84
|
+
{{ item.text }}
|
|
85
|
+
</NuxtLink>
|
|
86
|
+
</li>
|
|
87
|
+
</ul>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</nav>
|
|
91
|
+
</template>
|
|
92
|
+
|
|
93
|
+
<script setup lang="ts">
|
|
94
|
+
import type { NavItemData } from "~/types/components";
|
|
95
|
+
|
|
96
|
+
interface Props {
|
|
97
|
+
navItemData: NavItemData;
|
|
98
|
+
navAlign?: "left" | "center" | "right";
|
|
99
|
+
styleClassPassthrough?: string | string[];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
103
|
+
navAlign: "left",
|
|
104
|
+
styleClassPassthrough: () => [],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const { navRef, navListRef, isCollapsed, isLoaded, isMenuOpen, isActiveItem, toggleMenu, closeMenu, navigationStore } =
|
|
108
|
+
useNavCollapse(props.navItemData, "tab-nav-loaded");
|
|
109
|
+
|
|
110
|
+
// Handle navigation link clicks to update store
|
|
111
|
+
const handleNavLinkClick = (event: MouseEvent) => {
|
|
112
|
+
const target = (event.target as HTMLElement).closest<HTMLElement>("[href]");
|
|
113
|
+
if (!target) return;
|
|
114
|
+
const href = target.getAttribute("href");
|
|
115
|
+
if (href) navigationStore.handleNavLinkClick(href);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Handle panel link clicks to update store and close menu
|
|
119
|
+
const handlePanelLinkClick = (event: MouseEvent) => {
|
|
120
|
+
const target = (event.target as HTMLElement).closest<HTMLElement>("[href]");
|
|
121
|
+
if (!target) return;
|
|
122
|
+
const href = target.getAttribute("href");
|
|
123
|
+
if (href) navigationStore.handleNavLinkClick(href);
|
|
124
|
+
closeMenu();
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const hoveredItemHref = ref<string | null>(null);
|
|
128
|
+
|
|
129
|
+
const handleNavMousemove = (event: MouseEvent) => {
|
|
130
|
+
const li = (event.target as HTMLElement).closest<HTMLElement>("li[data-href]");
|
|
131
|
+
if (!li) return;
|
|
132
|
+
hoveredItemHref.value = li.dataset.href ?? null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
|
|
136
|
+
|
|
137
|
+
watch(
|
|
138
|
+
() => props.styleClassPassthrough,
|
|
139
|
+
() => resetElementClasses(props.styleClassPassthrough)
|
|
140
|
+
);
|
|
141
|
+
</script>
|
|
142
|
+
|
|
143
|
+
<style lang="css">
|
|
144
|
+
@layer components {
|
|
145
|
+
.tab-nav-backdrop {
|
|
146
|
+
--_backdrop-bg: var(--tab-nav-backdrop-bg, oklch(0% 0 0 / 55%));
|
|
147
|
+
--_backdrop-blur: var(--tab-nav-backdrop-blur, 3px);
|
|
148
|
+
--_backdrop-duration: var(--tab-nav-backdrop-duration, 350ms);
|
|
149
|
+
|
|
150
|
+
position: fixed;
|
|
151
|
+
inset: 0;
|
|
152
|
+
z-index: 10;
|
|
153
|
+
background: var(--_backdrop-bg);
|
|
154
|
+
backdrop-filter: blur(var(--_backdrop-blur));
|
|
155
|
+
opacity: 0;
|
|
156
|
+
pointer-events: none;
|
|
157
|
+
transition: opacity var(--_backdrop-duration) ease;
|
|
158
|
+
|
|
159
|
+
&.is-open {
|
|
160
|
+
opacity: 1;
|
|
161
|
+
pointer-events: auto;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.tab-navigation {
|
|
166
|
+
/* ─── Public token API ────────────────────────────────────────────── */
|
|
167
|
+
|
|
168
|
+
/* Decorators — horizontal nav */
|
|
169
|
+
--_decorator-hovered-bg: var(--tab-nav-decorator-hovered-bg, transparent);
|
|
170
|
+
/* --_decorator-indicator-color: var(--tab-nav-decorator-indicator-color, var(--slate-01, currentColor)); */
|
|
171
|
+
--_decorator-indicator-color: red;
|
|
172
|
+
|
|
173
|
+
/* Horizontal nav */
|
|
174
|
+
--_link-color: var(--tab-nav-link-color, var(--slate-01, currentColor));
|
|
175
|
+
--_link-hover-color: var(--tab-nav-link-hover-color, var(--slate-04, var(--_link-color)));
|
|
176
|
+
--_link-active-color: var(--tab-nav-link-active-color, var(--slate-01, var(--_link-color)));
|
|
177
|
+
--_link-size: var(--tab-nav-link-size, 1.6rem);
|
|
178
|
+
--_link-tracking: var(--tab-nav-link-tracking, 0.06em);
|
|
179
|
+
--_link-weight: var(--tab-nav-link-weight, 400);
|
|
180
|
+
--_nav-gap: var(--tab-nav-gap, 2.2rem);
|
|
181
|
+
--_nav-transition: var(--tab-nav-transition, 250ms ease);
|
|
182
|
+
|
|
183
|
+
/* Panel */
|
|
184
|
+
--_panel-bg: var(--tab-nav-panel-bg, var(--page-bg, #1a1614));
|
|
185
|
+
--_panel-border-color: var(
|
|
186
|
+
--tab-nav-panel-border-color,
|
|
187
|
+
color-mix(in oklch, var(--slate-01, #c0847a) 35%, transparent)
|
|
188
|
+
);
|
|
189
|
+
--_panel-item-border: var(--tab-nav-panel-item-border, color-mix(in oklch, var(--slate-01, white) 8%, transparent));
|
|
190
|
+
--_panel-link-color: var(--tab-nav-panel-link-color, var(--slate-01, currentColor));
|
|
191
|
+
--_panel-link-hover-color: var(--tab-nav-panel-link-hover-color, var(--slate-04, var(--_panel-link-color)));
|
|
192
|
+
--_panel-link-active-color: var(--tab-nav-panel-link-active-color, var(--slate-01, var(--_panel-link-color)));
|
|
193
|
+
--_panel-padding-block: var(--tab-nav-panel-padding-block, 1.4rem);
|
|
194
|
+
--_panel-padding-inline: var(--tab-nav-panel-padding-inline, 1.5rem);
|
|
195
|
+
--_panel-slide-duration: var(--tab-nav-panel-slide-duration, 350ms);
|
|
196
|
+
--_panel-slide-easing: var(--tab-nav-panel-slide-easing, cubic-bezier(0.4, 0, 0.2, 1));
|
|
197
|
+
|
|
198
|
+
/* Burger */
|
|
199
|
+
--_burger-bar-width: var(--tab-nav-burger-width, 22px);
|
|
200
|
+
--_burger-bar-height: var(--tab-nav-burger-height, 1.5px);
|
|
201
|
+
--_burger-bar-gap: var(--tab-nav-burger-gap, 5px);
|
|
202
|
+
--_burger-color: var(--tab-nav-burger-color, var(--slate-01, currentColor));
|
|
203
|
+
--_burger-transition: var(--tab-nav-burger-transition, 300ms ease);
|
|
204
|
+
|
|
205
|
+
/* ─────────────────────────────────────────────────────────────────── */
|
|
206
|
+
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
min-width: 0;
|
|
210
|
+
|
|
211
|
+
/* Hide everything until first measurement to prevent wrong-state flash */
|
|
212
|
+
&:not(.is-loaded) {
|
|
213
|
+
opacity: 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* ─── Horizontal list ───────────────────────────────────────────── */
|
|
217
|
+
|
|
218
|
+
.tab-nav-list {
|
|
219
|
+
list-style: none;
|
|
220
|
+
margin: 0;
|
|
221
|
+
padding: 0;
|
|
222
|
+
display: flex;
|
|
223
|
+
gap: var(--_nav-gap);
|
|
224
|
+
align-items: center;
|
|
225
|
+
position: relative;
|
|
226
|
+
|
|
227
|
+
.nav-indicator-li {
|
|
228
|
+
/* display: contents removes the li's box entirely — children become
|
|
229
|
+
direct participants in the flex container, so position: absolute
|
|
230
|
+
on .nav__hovered / .nav__active-indicator resolves against
|
|
231
|
+
.tab-nav-list (same containing block as the anchor li elements).
|
|
232
|
+
Without this, .nav-indicator-li is the containing block, which is
|
|
233
|
+
a different scope and Chrome's anchor positioning rejects it. */
|
|
234
|
+
display: contents;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* Indicators hidden by default — shown only with anchor positioning support */
|
|
238
|
+
.nav__hovered,
|
|
239
|
+
.nav__active-indicator {
|
|
240
|
+
display: none;
|
|
241
|
+
pointer-events: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.tab-nav-link {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
gap: 0.4em;
|
|
248
|
+
color: var(--_link-color);
|
|
249
|
+
font-size: var(--_link-size);
|
|
250
|
+
font-weight: var(--_link-weight);
|
|
251
|
+
letter-spacing: var(--_link-tracking);
|
|
252
|
+
text-decoration: none;
|
|
253
|
+
text-wrap: nowrap;
|
|
254
|
+
padding-block: 0.8rem;
|
|
255
|
+
padding-inline: 0.4rem;
|
|
256
|
+
position: relative;
|
|
257
|
+
z-index: 4;
|
|
258
|
+
transition: color var(--_nav-transition);
|
|
259
|
+
|
|
260
|
+
&:hover,
|
|
261
|
+
&:focus-visible {
|
|
262
|
+
color: var(--_link-hover-color);
|
|
263
|
+
outline: none;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
&.router-link-exact-active {
|
|
267
|
+
color: var(--_link-active-color);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* ─── Alignment variants ────────────────────────────────────────── */
|
|
273
|
+
|
|
274
|
+
&.tab-navigation--center .tab-nav-list {
|
|
275
|
+
margin-inline: auto;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
&.tab-navigation--right .tab-nav-list {
|
|
279
|
+
margin-inline-start: auto;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
&.is-collapsed {
|
|
283
|
+
justify-content: end;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* ─── Burger button (InputButtonCore) ──────────────────────────── */
|
|
287
|
+
|
|
288
|
+
.tab-nav-burger.input-button-core.icon-only {
|
|
289
|
+
margin-inline-start: auto;
|
|
290
|
+
color: var(--_burger-color);
|
|
291
|
+
|
|
292
|
+
/* Strip all InputButtonCore visual styling */
|
|
293
|
+
background: none;
|
|
294
|
+
border: none;
|
|
295
|
+
outline: none;
|
|
296
|
+
text-decoration: none;
|
|
297
|
+
padding: 8px;
|
|
298
|
+
border-radius: 4px;
|
|
299
|
+
transition: outline-color var(--_nav-transition);
|
|
300
|
+
|
|
301
|
+
&.icon-only {
|
|
302
|
+
aspect-ratio: unset;
|
|
303
|
+
border-radius: 4px;
|
|
304
|
+
|
|
305
|
+
.btn-icon {
|
|
306
|
+
margin: 0;
|
|
307
|
+
display: flex;
|
|
308
|
+
flex-direction: column;
|
|
309
|
+
gap: var(--_burger-bar-gap);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
&:focus-visible {
|
|
314
|
+
outline: 2px solid var(--_burger-color);
|
|
315
|
+
outline-offset: 4px;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.burger-bar {
|
|
320
|
+
display: block;
|
|
321
|
+
width: var(--_burger-bar-width);
|
|
322
|
+
height: var(--_burger-bar-height);
|
|
323
|
+
background: currentColor;
|
|
324
|
+
border-radius: 1px;
|
|
325
|
+
transform-origin: center;
|
|
326
|
+
transition:
|
|
327
|
+
transform var(--_burger-transition),
|
|
328
|
+
opacity var(--_burger-transition);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.tab-nav-burger.is-open {
|
|
332
|
+
.burger-bar:nth-child(1) {
|
|
333
|
+
transform: translateY(calc(var(--_burger-bar-height) + var(--_burger-bar-gap))) rotate(45deg);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.burger-bar:nth-child(2) {
|
|
337
|
+
opacity: 0;
|
|
338
|
+
transform: scaleX(0);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.burger-bar:nth-child(3) {
|
|
342
|
+
transform: translateY(calc(-1 * (var(--_burger-bar-height) + var(--_burger-bar-gap)))) rotate(-45deg);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* ─── Mobile drop panel ─────────────────────────────────────────── */
|
|
347
|
+
|
|
348
|
+
.tab-nav-panel {
|
|
349
|
+
position: absolute;
|
|
350
|
+
left: 0;
|
|
351
|
+
right: 0;
|
|
352
|
+
top: 100%;
|
|
353
|
+
|
|
354
|
+
display: grid;
|
|
355
|
+
grid-template-rows: 0fr;
|
|
356
|
+
border-block-start: 1px solid transparent;
|
|
357
|
+
transition:
|
|
358
|
+
grid-template-rows var(--_panel-slide-duration) var(--_panel-slide-easing),
|
|
359
|
+
border-color var(--_panel-slide-duration) var(--_panel-slide-easing);
|
|
360
|
+
|
|
361
|
+
z-index: 1;
|
|
362
|
+
|
|
363
|
+
&.is-open {
|
|
364
|
+
grid-template-rows: 1fr;
|
|
365
|
+
border-block-start-color: var(--_panel-border-color);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.tab-nav-panel-inner {
|
|
369
|
+
overflow: hidden;
|
|
370
|
+
background-color: var(--_panel-bg);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.tab-nav-panel-list {
|
|
374
|
+
list-style: none;
|
|
375
|
+
margin: 0;
|
|
376
|
+
padding: 0;
|
|
377
|
+
|
|
378
|
+
li {
|
|
379
|
+
border-block-end: 1px solid var(--_panel-item-border);
|
|
380
|
+
|
|
381
|
+
&:last-child {
|
|
382
|
+
border-block-end: none;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
&:hover {
|
|
386
|
+
background-color: color-mix(in oklch, var(--slate-01, white) 5%, transparent);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.tab-nav-panel-link {
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: center;
|
|
393
|
+
gap: 0.5em;
|
|
394
|
+
color: var(--_panel-link-color);
|
|
395
|
+
font-size: var(--_link-size);
|
|
396
|
+
font-weight: var(--_link-weight);
|
|
397
|
+
letter-spacing: var(--_link-tracking);
|
|
398
|
+
text-decoration: none;
|
|
399
|
+
padding-block: var(--_panel-padding-block);
|
|
400
|
+
padding-inline: var(--_panel-padding-inline);
|
|
401
|
+
position: relative;
|
|
402
|
+
z-index: 1;
|
|
403
|
+
transition: color var(--_nav-transition);
|
|
404
|
+
|
|
405
|
+
&:hover,
|
|
406
|
+
&:focus-visible {
|
|
407
|
+
color: var(--_panel-link-hover-color);
|
|
408
|
+
outline: none;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
&.router-link-exact-active {
|
|
412
|
+
color: var(--_panel-link-active-color);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/* ─── Anchor positioning for nav indicators ──────────────────────────────
|
|
420
|
+
anchor-name is declared unconditionally so the @oddbird polyfill can
|
|
421
|
+
read it on browsers without native support (the polyfill checks for
|
|
422
|
+
anchor-name in raw CSS text, but skips rules inside a failing @supports).
|
|
423
|
+
The display guard stays inside @supports so indicators are hidden on
|
|
424
|
+
truly old browsers where neither native CSS nor the polyfill will work.
|
|
425
|
+
Requires Chrome 125+, Edge 125+, Firefox 131+, or the polyfill.
|
|
426
|
+
──────────────────────────────────────────────────────────────────────── */
|
|
427
|
+
|
|
428
|
+
/* Single anchor --tab-nav-indicator tracks the hovered item, or falls back
|
|
429
|
+
to the active item when nothing is hovered. No dual anchor-names needed. */
|
|
430
|
+
|
|
431
|
+
/* Nothing hovered: anchor sits on the active item */
|
|
432
|
+
.tab-navigation .tab-nav-list:not(:has(.is-hovered)) li.is-active {
|
|
433
|
+
anchor-name: --tab-nav-indicator;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* Something hovered: anchor sits on the hovered item */
|
|
437
|
+
.tab-navigation .tab-nav-list li.is-hovered {
|
|
438
|
+
anchor-name: --tab-nav-indicator;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* @supports (anchor-name: --x) { */
|
|
442
|
+
/* Hover highlight: background pill that follows the pointer */
|
|
443
|
+
.tab-navigation .tab-nav-list .nav__hovered {
|
|
444
|
+
display: block;
|
|
445
|
+
position: absolute;
|
|
446
|
+
position-anchor: --tab-nav-indicator;
|
|
447
|
+
left: anchor(left);
|
|
448
|
+
right: anchor(right);
|
|
449
|
+
top: 0;
|
|
450
|
+
bottom: 0;
|
|
451
|
+
opacity: 0;
|
|
452
|
+
pointer-events: none;
|
|
453
|
+
transition:
|
|
454
|
+
left 200ms ease,
|
|
455
|
+
right 200ms ease,
|
|
456
|
+
opacity 150ms ease;
|
|
457
|
+
background: var(--tab-nav-decorator-hovered-bg, transparent);
|
|
458
|
+
border-radius: 4px;
|
|
459
|
+
z-index: 1;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.tab-navigation .tab-nav-list:has(.is-hovered) .nav__hovered {
|
|
463
|
+
opacity: 1;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* Active indicator bar: always follows --tab-nav-indicator */
|
|
467
|
+
.tab-navigation .tab-nav-list .nav__active-indicator {
|
|
468
|
+
display: block;
|
|
469
|
+
position: absolute;
|
|
470
|
+
position-anchor: --tab-nav-indicator;
|
|
471
|
+
left: anchor(left);
|
|
472
|
+
right: anchor(right);
|
|
473
|
+
bottom: 0;
|
|
474
|
+
height: 2px;
|
|
475
|
+
pointer-events: none;
|
|
476
|
+
transition:
|
|
477
|
+
left 200ms ease,
|
|
478
|
+
right 200ms ease;
|
|
479
|
+
background-color: var(--tab-nav-decorator-indicator-color, var(--slate-01, currentColor));
|
|
480
|
+
z-index: 3;
|
|
481
|
+
}
|
|
482
|
+
/* } */
|
|
483
|
+
}
|
|
484
|
+
</style>
|