@sofidevo/astro-dynamic-header 3.0.1 → 4.0.0
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/README.md +543 -337
- package/package.json +20 -16
- package/src/ChevronIcon.astro +33 -0
- package/src/HamburgerButton.astro +100 -92
- package/src/Header.astro +105 -156
- package/src/MobileNav.astro +133 -129
- package/src/MoonIcon.astro +114 -0
- package/src/NavMenu.astro +175 -281
- package/src/SunIcon.astro +42 -0
- package/src/index.ts +15 -8
- package/src/scripts/hamburger.ts +55 -0
- package/src/scripts/nav-menu.ts +119 -0
package/src/NavMenu.astro
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { MenuItem, SecondaryMenuItem, TertiaryMenuItem } from "./index.js";
|
|
3
|
+
import ChevronIcon from "./ChevronIcon.astro";
|
|
3
4
|
|
|
4
5
|
export interface Props {
|
|
5
|
-
/**
|
|
6
|
-
* Header type for styling.
|
|
7
|
-
* @example "floating"
|
|
8
|
-
*/
|
|
9
6
|
type?: "floating" | "fullscreen";
|
|
10
|
-
/** Array of menu items to display. */
|
|
11
7
|
menuItems?: MenuItem[];
|
|
12
|
-
/**
|
|
13
|
-
* Whether to show the home link in the menu.
|
|
14
|
-
* @default true
|
|
15
|
-
*/
|
|
16
8
|
showHomeLink?: boolean;
|
|
17
|
-
/**
|
|
18
|
-
* Text for the home link.
|
|
19
|
-
* @default "Home"
|
|
20
|
-
*/
|
|
21
9
|
homeText?: string;
|
|
22
|
-
|
|
10
|
+
homeUrl?: string;
|
|
23
11
|
header__menu__class?: string;
|
|
24
|
-
/** Extra CSS class(es) for each top-level `<li>` item. */
|
|
25
12
|
header__item__class?: string;
|
|
26
|
-
/** Extra CSS class(es) for each top-level menu `<a>` link. */
|
|
27
13
|
menu__link__class?: string;
|
|
28
14
|
}
|
|
29
15
|
|
|
@@ -31,6 +17,7 @@ const {
|
|
|
31
17
|
menuItems = [],
|
|
32
18
|
showHomeLink = true,
|
|
33
19
|
homeText = "Home",
|
|
20
|
+
homeUrl = "/",
|
|
34
21
|
header__menu__class,
|
|
35
22
|
header__item__class,
|
|
36
23
|
menu__link__class,
|
|
@@ -40,115 +27,19 @@ const pagePathname = Astro.url.pathname;
|
|
|
40
27
|
---
|
|
41
28
|
|
|
42
29
|
<script>
|
|
43
|
-
|
|
44
|
-
const subsubmenus = document.querySelectorAll(
|
|
45
|
-
".subsubmenu",
|
|
46
|
-
) as NodeListOf<HTMLElement>;
|
|
47
|
-
|
|
48
|
-
subsubmenus.forEach((subsubmenu) => {
|
|
49
|
-
const rect = subsubmenu.getBoundingClientRect();
|
|
50
|
-
const viewportWidth = window.innerWidth;
|
|
51
|
-
|
|
52
|
-
if (rect.right > viewportWidth) {
|
|
53
|
-
subsubmenu.style.right = "100%";
|
|
54
|
-
subsubmenu.style.left = "0";
|
|
55
|
-
} else {
|
|
56
|
-
subsubmenu.style.left = "100%";
|
|
57
|
-
subsubmenu.style.right = "0";
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
30
|
+
import { initNavMenu } from "./scripts/nav-menu.js";
|
|
61
31
|
|
|
62
|
-
|
|
63
|
-
const parentItems = document.querySelectorAll(".submenu__item--secondary");
|
|
64
|
-
|
|
65
|
-
parentItems.forEach((parentItem) => {
|
|
66
|
-
const subsubmenu = parentItem.querySelector(".subsubmenu");
|
|
67
|
-
|
|
68
|
-
if (subsubmenu) {
|
|
69
|
-
const adjustPosition = () => {
|
|
70
|
-
const viewportWidth = window.innerWidth;
|
|
71
|
-
|
|
72
|
-
const originalDisplay = (subsubmenu as HTMLElement).style.display;
|
|
73
|
-
const originalVisibility = (subsubmenu as HTMLElement).style
|
|
74
|
-
.visibility;
|
|
75
|
-
|
|
76
|
-
(subsubmenu as HTMLElement).style.display = "block";
|
|
77
|
-
(subsubmenu as HTMLElement).style.visibility = "hidden";
|
|
78
|
-
(subsubmenu as HTMLElement).style.left = "100%";
|
|
79
|
-
(subsubmenu as HTMLElement).style.transform = "auto";
|
|
80
|
-
|
|
81
|
-
const rect = subsubmenu.getBoundingClientRect();
|
|
82
|
-
|
|
83
|
-
if (rect.right > viewportWidth) {
|
|
84
|
-
(subsubmenu as HTMLElement).style.left = "-100%";
|
|
85
|
-
(subsubmenu as HTMLElement).style.right = "auto";
|
|
86
|
-
const leftRect = subsubmenu.getBoundingClientRect();
|
|
87
|
-
|
|
88
|
-
if (leftRect.left < 0) {
|
|
89
|
-
(subsubmenu as HTMLElement).style.left = "100%";
|
|
90
|
-
const overflow = rect.right - viewportWidth + 30;
|
|
91
|
-
(subsubmenu as HTMLElement).style.transform =
|
|
92
|
-
`translateX(-${overflow}px)`;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
(subsubmenu as HTMLElement).style.display = originalDisplay;
|
|
96
|
-
(subsubmenu as HTMLElement).style.visibility = originalVisibility;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const showSubsubmenu = () => {
|
|
100
|
-
adjustPosition();
|
|
101
|
-
|
|
102
|
-
(subsubmenu as HTMLElement).style.opacity = "1";
|
|
103
|
-
(subsubmenu as HTMLElement).style.pointerEvents = "all";
|
|
104
|
-
|
|
105
|
-
const isPositionedLeft =
|
|
106
|
-
(subsubmenu as HTMLElement).style.left === "-100%";
|
|
107
|
-
const transformDirection = isPositionedLeft ? "-32px" : "32px";
|
|
108
|
-
|
|
109
|
-
const currentTransform = (subsubmenu as HTMLElement).style.transform;
|
|
110
|
-
if (currentTransform && currentTransform.includes("translateX")) {
|
|
111
|
-
const existingTranslate = currentTransform.match(
|
|
112
|
-
/translateX\(([^)]+)\)/,
|
|
113
|
-
);
|
|
114
|
-
if (existingTranslate) {
|
|
115
|
-
const existingValue = existingTranslate[1];
|
|
116
|
-
(subsubmenu as HTMLElement).style.transform =
|
|
117
|
-
`translateX(calc(${existingValue} + ${transformDirection}))`;
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
(subsubmenu as HTMLElement).style.transform =
|
|
121
|
-
`${currentTransform} translateX(${transformDirection})`.trim();
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const hideSubsubmenu = () => {
|
|
126
|
-
(subsubmenu as HTMLElement).style.opacity = "";
|
|
127
|
-
(subsubmenu as HTMLElement).style.pointerEvents = "";
|
|
128
|
-
(subsubmenu as HTMLElement).style.transform = "";
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
parentItem.addEventListener("mouseenter", showSubsubmenu);
|
|
132
|
-
parentItem.addEventListener("mouseleave", hideSubsubmenu);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
window.addEventListener("resize", adjustSubsubmenuPositionOnResize);
|
|
138
|
-
document.addEventListener("DOMContentLoaded", () => {
|
|
139
|
-
adjustSubsubmenuPositionOnResize();
|
|
140
|
-
handleSubsubmenuInteraction();
|
|
141
|
-
});
|
|
32
|
+
initNavMenu();
|
|
142
33
|
</script>
|
|
143
34
|
|
|
144
35
|
<nav class:list={["header__menu", header__menu__class]} id="header-menu">
|
|
145
36
|
<ul class="menu">
|
|
146
37
|
{
|
|
147
|
-
showHomeLink && pagePathname !==
|
|
38
|
+
showHomeLink && pagePathname !== homeUrl && (
|
|
148
39
|
<li class:list={["header__item", header__item__class]}>
|
|
149
40
|
<a
|
|
150
41
|
class:list={["menu__link", menu__link__class]}
|
|
151
|
-
href=
|
|
42
|
+
href={homeUrl}
|
|
152
43
|
data-astro-prefetch="hover"
|
|
153
44
|
>
|
|
154
45
|
{homeText}
|
|
@@ -162,12 +53,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
162
53
|
<a class:list={["menu__link", menu__link__class]} href={item.link}>
|
|
163
54
|
{item.text}
|
|
164
55
|
{item.submenu && (
|
|
165
|
-
<
|
|
166
|
-
class="iconify-arrow"
|
|
167
|
-
icon="codicon:triangle-down"
|
|
168
|
-
width="15"
|
|
169
|
-
height="15"
|
|
170
|
-
/>
|
|
56
|
+
<ChevronIcon class="arrow-down" width={15} height={15} />
|
|
171
57
|
)}
|
|
172
58
|
</a>
|
|
173
59
|
{item.submenu && (
|
|
@@ -177,11 +63,10 @@ const pagePathname = Astro.url.pathname;
|
|
|
177
63
|
<a href={sub.link}>
|
|
178
64
|
{sub.text}
|
|
179
65
|
{sub.submenu && sub.submenu.length > 0 && (
|
|
180
|
-
<
|
|
181
|
-
class="
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
height="15"
|
|
66
|
+
<ChevronIcon
|
|
67
|
+
class="ChevronIcon-arrow"
|
|
68
|
+
width={15}
|
|
69
|
+
height={15}
|
|
185
70
|
/>
|
|
186
71
|
)}
|
|
187
72
|
</a>
|
|
@@ -204,186 +89,195 @@ const pagePathname = Astro.url.pathname;
|
|
|
204
89
|
</ul>
|
|
205
90
|
</nav>
|
|
206
91
|
|
|
207
|
-
<style
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
92
|
+
<style>
|
|
93
|
+
/*
|
|
94
|
+
* base (resets) < components (ours) < utilities (consumer classNames).
|
|
95
|
+
* Order statement keeps it deterministic regardless of bundling order.
|
|
96
|
+
*/
|
|
97
|
+
@layer theme, base, components, utilities;
|
|
98
|
+
|
|
99
|
+
@layer components {
|
|
100
|
+
.header__menu {
|
|
101
|
+
@media (width < 768px) {
|
|
102
|
+
display: none;
|
|
103
|
+
}
|
|
211
104
|
}
|
|
212
|
-
}
|
|
213
105
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
106
|
+
.menu {
|
|
107
|
+
list-style: none;
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
flex-wrap: wrap;
|
|
219
111
|
|
|
220
|
-
|
|
221
|
-
|
|
112
|
+
@media (width < 768px) {
|
|
113
|
+
flex-direction: column;
|
|
114
|
+
}
|
|
222
115
|
}
|
|
223
|
-
}
|
|
224
116
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
@media screen and (min-width: 768px) {
|
|
240
|
-
.menu__link::after {
|
|
241
|
-
position: absolute;
|
|
242
|
-
top: 33px;
|
|
243
|
-
left: 0;
|
|
244
|
-
content: "";
|
|
245
|
-
width: 100%;
|
|
246
|
-
transform: scale(0);
|
|
247
|
-
transform-origin: 1;
|
|
248
|
-
height: 2px;
|
|
249
|
-
transition: transform 0.3s ease;
|
|
250
|
-
background: var(--accent-color, #00ffff);
|
|
117
|
+
.menu__link {
|
|
118
|
+
text-decoration: none;
|
|
119
|
+
padding: 0.5em;
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 0.8em;
|
|
123
|
+
color: color-mix(
|
|
124
|
+
in srgb,
|
|
125
|
+
var(--accent-color, #00ffff) 30%,
|
|
126
|
+
var(--text-color, #ffffff) 70%
|
|
127
|
+
);
|
|
128
|
+
position: relative;
|
|
251
129
|
}
|
|
252
130
|
|
|
253
|
-
|
|
254
|
-
|
|
131
|
+
@media screen and (min-width: 768px) {
|
|
132
|
+
.menu__link::after {
|
|
133
|
+
position: absolute;
|
|
134
|
+
top: 33px;
|
|
135
|
+
left: 0;
|
|
136
|
+
content: "";
|
|
137
|
+
width: 100%;
|
|
138
|
+
transform: scale(0);
|
|
139
|
+
transform-origin: 1;
|
|
140
|
+
height: 2px;
|
|
141
|
+
transition: transform 0.3s ease;
|
|
142
|
+
background: var(--accent-color, #00ffff);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.menu__link:hover::after {
|
|
146
|
+
transform: scale(1);
|
|
147
|
+
}
|
|
255
148
|
}
|
|
256
|
-
}
|
|
257
149
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
150
|
+
.ChevronIcon-arrow {
|
|
151
|
+
transition: transform 0.3s linear;
|
|
152
|
+
}
|
|
261
153
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
154
|
+
.menu__link:hover > .ChevronIcon-arrow {
|
|
155
|
+
transform: rotate(180deg);
|
|
156
|
+
}
|
|
265
157
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
158
|
+
.submenu,
|
|
159
|
+
.subsubmenu {
|
|
160
|
+
color: var(--text-color, #ffffff);
|
|
161
|
+
background-color: var(--bg-color-opaque, #171717);
|
|
162
|
+
border: 0.5px solid
|
|
163
|
+
color-mix(in srgb, var(--accent-color, #3e1c71) 30%, transparent);
|
|
164
|
+
position: absolute;
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
gap: 0.4em;
|
|
167
|
+
padding: 1em;
|
|
168
|
+
border-radius: 10px;
|
|
169
|
+
transition: transform 0.3s ease-in-out;
|
|
170
|
+
pointer-events: none;
|
|
171
|
+
opacity: 0;
|
|
172
|
+
text-wrap: nowrap;
|
|
173
|
+
}
|
|
282
174
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
175
|
+
.menu__item {
|
|
176
|
+
padding: 6px;
|
|
177
|
+
position: relative;
|
|
178
|
+
list-style: none;
|
|
179
|
+
}
|
|
288
180
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
181
|
+
.submenu a,
|
|
182
|
+
.subsubmenu a {
|
|
183
|
+
display: flex;
|
|
184
|
+
justify-content: space-between;
|
|
185
|
+
align-items: center;
|
|
186
|
+
width: 100%;
|
|
187
|
+
height: 64px;
|
|
188
|
+
border-radius: 6px;
|
|
189
|
+
color: var(--text-color, #fff);
|
|
190
|
+
}
|
|
299
191
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
192
|
+
.submenu__item--secondary,
|
|
193
|
+
.submenu__item--tertiary {
|
|
194
|
+
position: relative;
|
|
195
|
+
}
|
|
304
196
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
197
|
+
.submenu__item--secondary a,
|
|
198
|
+
.submenu__item--tertiary a {
|
|
199
|
+
padding: 26px;
|
|
200
|
+
}
|
|
309
201
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
202
|
+
.submenu__item--secondary a:hover,
|
|
203
|
+
.submenu__item--tertiary a:hover {
|
|
204
|
+
background: color-mix(
|
|
205
|
+
in srgb,
|
|
206
|
+
var(--accent-color, #00ffff) 15%,
|
|
207
|
+
transparent
|
|
208
|
+
);
|
|
209
|
+
color: var(--text-color, white);
|
|
210
|
+
}
|
|
319
211
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
212
|
+
.submenu {
|
|
213
|
+
padding: 0.9em;
|
|
214
|
+
right: calc(50% - 80px);
|
|
215
|
+
}
|
|
324
216
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
217
|
+
.submenu::after {
|
|
218
|
+
content: "";
|
|
219
|
+
position: absolute;
|
|
220
|
+
top: -1.1em;
|
|
221
|
+
width: 100%;
|
|
222
|
+
z-index: -3;
|
|
223
|
+
pointer-events: none;
|
|
224
|
+
height: 7em;
|
|
225
|
+
right: 0;
|
|
226
|
+
}
|
|
334
227
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
228
|
+
.subsubmenu {
|
|
229
|
+
top: 0;
|
|
230
|
+
left: auto;
|
|
231
|
+
right: auto;
|
|
232
|
+
min-width: 193px;
|
|
233
|
+
}
|
|
341
234
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
235
|
+
.subsubmenu::before {
|
|
236
|
+
content: "";
|
|
237
|
+
position: absolute;
|
|
238
|
+
top: -4em;
|
|
239
|
+
z-index: -1;
|
|
240
|
+
pointer-events: none;
|
|
241
|
+
height: 10em;
|
|
242
|
+
width: 20em;
|
|
243
|
+
height: 14em;
|
|
244
|
+
}
|
|
352
245
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
246
|
+
.subsubmenu::after {
|
|
247
|
+
content: "";
|
|
248
|
+
position: absolute;
|
|
249
|
+
top: -14px;
|
|
250
|
+
right: 3em;
|
|
251
|
+
width: 100%;
|
|
252
|
+
z-index: -3;
|
|
253
|
+
height: 21em;
|
|
254
|
+
}
|
|
362
255
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
256
|
+
.submenu li {
|
|
257
|
+
width: 100%;
|
|
258
|
+
}
|
|
366
259
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
260
|
+
.menu__item:hover > .submenu {
|
|
261
|
+
opacity: 1;
|
|
262
|
+
pointer-events: all;
|
|
263
|
+
transform: translateY(1.4em);
|
|
264
|
+
}
|
|
372
265
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
266
|
+
.menu__item:hover > .submenu::after {
|
|
267
|
+
pointer-events: all;
|
|
268
|
+
}
|
|
376
269
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
270
|
+
.submenu li:hover > .subsubmenu {
|
|
271
|
+
opacity: 1;
|
|
272
|
+
pointer-events: all;
|
|
273
|
+
}
|
|
381
274
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
275
|
+
.submenu li:hover > .subsubmenu::before {
|
|
276
|
+
pointer-events: all;
|
|
277
|
+
}
|
|
385
278
|
|
|
386
|
-
|
|
387
|
-
|
|
279
|
+
.submenu li:hover > .subsubmenu::after {
|
|
280
|
+
pointer-events: all;
|
|
281
|
+
}
|
|
388
282
|
}
|
|
389
283
|
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
const props = Astro.props;
|
|
3
|
+
|
|
4
|
+
const viewBox = "0 0 24 24";
|
|
5
|
+
const content = `<g class="gutruu"><path fill-opacity="0" class="xkoryc"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.4s" values="34;0"/><animate fill="freeze" attributeName="fill-opacity" begin="0.5s" dur="0.4s" to="1"/></path><g class="ft5dvn"><path d="M12 21v1M21 12h1M12 3v-1M3 12h-1" opacity="0"><animateTransform attributeName="transform" dur="30s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/><set fill="freeze" attributeName="opacity" begin="1s" to="1"/><animate fill="freeze" attributeName="d" begin="1s" dur="0.2s" values="M12 19v1M19 12h1M12 5v-1M5 12h-1;M12 21v1M21 12h1M12 3v-1M3 12h-1"/></path><path d="M18.5 18.5l0.5 0.5M18.5 5.5l0.5 -0.5M5.5 5.5l-0.5 -0.5M5.5 18.5l-0.5 0.5" opacity="0"><animateTransform attributeName="transform" dur="30s" repeatCount="indefinite" type="rotate" values="0 12 12;360 12 12"/><set fill="freeze" attributeName="opacity" begin="1.2s" to="1"/><animate fill="freeze" attributeName="d" begin="1.2s" dur="0.2s" values="M17 17l0.5 0.5M17 7l0.5 -0.5M7 7l-0.5 -0.5M7 17l-0.5 0.5;M18.5 18.5l0.5 0.5M18.5 5.5l0.5 -0.5M5.5 5.5l-0.5 -0.5M5.5 18.5l-0.5 0.5"/></path></g></g>`;
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<svg
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="25px"
|
|
11
|
+
height="25px"
|
|
12
|
+
viewBox={viewBox}
|
|
13
|
+
{...props}
|
|
14
|
+
set:html={content}
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<style is:inline>
|
|
18
|
+
/*
|
|
19
|
+
* base (resets) < components (ours) < utilities (consumer classNames).
|
|
20
|
+
* Order statement keeps it deterministic regardless of bundling order.
|
|
21
|
+
*/
|
|
22
|
+
@layer theme, base, components, utilities;
|
|
23
|
+
|
|
24
|
+
@layer components {
|
|
25
|
+
.gutruu {
|
|
26
|
+
stroke: currentColor;
|
|
27
|
+
stroke-linecap: round;
|
|
28
|
+
stroke-linejoin: round;
|
|
29
|
+
stroke-width: var(--svg-stroke-width--2px, 2px);
|
|
30
|
+
}
|
|
31
|
+
.xkoryc {
|
|
32
|
+
fill: currentColor;
|
|
33
|
+
stroke-dasharray: 34;
|
|
34
|
+
d: path(
|
|
35
|
+
"M12 7c2.76 0 5 2.24 5 5c0 2.76 -2.24 5 -5 5c-2.76 0 -5 -2.24 -5 -5c0 -2.76 2.24 -5 5 -5Z"
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
.ft5dvn {
|
|
39
|
+
fill: none;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</style>
|
package/src/index.ts
CHANGED
|
@@ -129,8 +129,12 @@ export interface DualThemeConfig {
|
|
|
129
129
|
* Custom CSS class names for high-level layout & appearance customization.
|
|
130
130
|
*
|
|
131
131
|
* These target the structural wrapper elements of the Header. For fine-grained
|
|
132
|
-
* control over
|
|
133
|
-
*
|
|
132
|
+
* control over the nav items use the `xxx__class` props inside `navigation`,
|
|
133
|
+
* and style the logo directly on the markup you pass to the `logo` slot.
|
|
134
|
+
*
|
|
135
|
+
* The component styles live in `@layer components`, so any class passed here
|
|
136
|
+
* (Tailwind utilities included) wins over the built-in styles — no
|
|
137
|
+
* `!important` and no `twMerge()` required.
|
|
134
138
|
*
|
|
135
139
|
* @example
|
|
136
140
|
* ```astro
|
|
@@ -142,10 +146,6 @@ export interface HeaderClassNames {
|
|
|
142
146
|
container?: string;
|
|
143
147
|
/** Inner `<header>` element — best place for shadows, borders, transitions. */
|
|
144
148
|
header?: string;
|
|
145
|
-
/** Logo anchor `<a>` — add hover states or focus rings here. */
|
|
146
|
-
logo?: string;
|
|
147
|
-
/** Logo text `<span>` — override typography here. */
|
|
148
|
-
logoText?: string;
|
|
149
149
|
/** Desktop nav wrapper `<div>` — adjust spacing between logo and menu. */
|
|
150
150
|
nav?: string;
|
|
151
151
|
/** Mobile nav panel `<nav>` — add slide-in overrides or z-index tweaks. */
|
|
@@ -192,18 +192,25 @@ export interface HeaderProps {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
export interface NavMenuProps {
|
|
195
|
-
|
|
195
|
+
type?: "floating" | "fullscreen";
|
|
196
|
+
menuItems?: MenuItem[];
|
|
196
197
|
showHomeLink?: boolean;
|
|
197
198
|
homeText?: string;
|
|
199
|
+
header__menu__class?: string;
|
|
200
|
+
header__item__class?: string;
|
|
201
|
+
menu__link__class?: string;
|
|
198
202
|
}
|
|
199
203
|
|
|
200
204
|
export interface MobileNavProps {
|
|
201
|
-
|
|
205
|
+
type?: "floating" | "fullscreen";
|
|
206
|
+
menuItems?: MenuItem[];
|
|
202
207
|
showHomeLink?: boolean;
|
|
203
208
|
homeText?: string;
|
|
209
|
+
mobileNav__class?: string;
|
|
204
210
|
accentColor?: string;
|
|
205
211
|
}
|
|
206
212
|
|
|
207
213
|
export interface HamburgerButtonProps {
|
|
208
214
|
color?: string;
|
|
209
215
|
}
|
|
216
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behaviour for the hamburger button + mobile panel.
|
|
3
|
+
* Extracted from `Header.astro` so it can be unit tested.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class HamburgerController {
|
|
7
|
+
private readonly hamburgerBtn: HTMLElement | null;
|
|
8
|
+
private readonly mobileMenu: HTMLElement | null;
|
|
9
|
+
private readonly links: NodeListOf<HTMLElement>;
|
|
10
|
+
|
|
11
|
+
constructor(doc: Document = document) {
|
|
12
|
+
this.hamburgerBtn = doc.getElementById("hamburger-btn");
|
|
13
|
+
this.mobileMenu = doc.getElementById("mobile-header-menu");
|
|
14
|
+
this.links = doc.querySelectorAll(".mobile-menu__link");
|
|
15
|
+
this.init();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private init(): void {
|
|
19
|
+
if (!this.hamburgerBtn || !this.mobileMenu) return;
|
|
20
|
+
|
|
21
|
+
this.hamburgerBtn.addEventListener("click", () => this.toggleMenu());
|
|
22
|
+
this.links.forEach((link) => {
|
|
23
|
+
link.addEventListener("click", () => this.closeMenu());
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
closeMenu(): void {
|
|
28
|
+
this.hamburgerBtn?.classList.remove("is-active");
|
|
29
|
+
this.mobileMenu?.classList.remove("is-active");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toggleMenu(): void {
|
|
33
|
+
this.hamburgerBtn?.classList.toggle("is-active");
|
|
34
|
+
this.mobileMenu?.classList.toggle("is-active");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Wires the hamburger as soon as the DOM is ready (module scripts may run
|
|
40
|
+
* before or after `DOMContentLoaded`, so both paths are handled).
|
|
41
|
+
*/
|
|
42
|
+
export function initHamburger(doc: Document = document): HamburgerController | null {
|
|
43
|
+
if (doc.readyState === "loading") {
|
|
44
|
+
let controller: HamburgerController | null = null;
|
|
45
|
+
doc.addEventListener(
|
|
46
|
+
"DOMContentLoaded",
|
|
47
|
+
() => {
|
|
48
|
+
controller = new HamburgerController(doc);
|
|
49
|
+
},
|
|
50
|
+
{ once: true },
|
|
51
|
+
);
|
|
52
|
+
return controller;
|
|
53
|
+
}
|
|
54
|
+
return new HamburgerController(doc);
|
|
55
|
+
}
|