@sofidevo/astro-dynamic-header 3.0.1 → 3.1.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 +159 -389
- package/package.json +7 -4
- package/src/ChevronIcon.astro +25 -0
- package/src/Header.astro +2 -0
- package/src/MobileNav.astro +12 -16
- package/src/MoonIcon.astro +106 -0
- package/src/NavMenu.astro +19 -17
- package/src/SunIcon.astro +34 -0
- package/src/index.ts +9 -2
package/README.md
CHANGED
|
@@ -1,505 +1,275 @@
|
|
|
1
1
|
# @sofidevo/astro-dynamic-header
|
|
2
2
|
|
|
3
|
-
A dynamic, responsive header component for Astro projects
|
|
4
|
-
|
|
5
|
-
> [!WARNING]
|
|
6
|
-
> **Breaking Changes — v2.2+**: The `logo` prop has been removed in favor of a much more flexible `logo` slot. The `color` prop has also been removed from the `HamburgerButton` component to ensure flawless dark mode synchronization. The `theme` property behavior was also refactored to prioritize native CSS variables, reducing DOM bloat and enabling purely CSS-based customization. See the [Changelog](#changelog) section below.
|
|
7
|
-
|
|
8
|
-
> [!WARNING]
|
|
9
|
-
> **Breaking Changes — v2.0+**: The `logo` and `navigation` props were restructured from strings/arrays to configuration objects. See the [Component Props](#component-props) and [Comprehensive Example](#comprehensive-example) to migrate.
|
|
10
|
-
|
|
11
|
-
> [!NOTE]
|
|
12
|
-
> **What's new — v2.1**: `CustomClassNames` has been renamed `HeaderClassNames` (the old name still works as an alias). New nested class props added to `LogoConfig` and `NavConfig`. New `mobileNav` slot in `HeaderClassNames`. `defaultThemes` is now exported for use outside the component. See the [Changelog](#changelog) section below.
|
|
3
|
+
A dynamic, responsive header component for Astro projects. Supports floating and fullscreen layouts, multi-level dropdown navigation, native CSS variable customization, dark mode, and TypeScript — all with zero external icon dependencies.
|
|
13
4
|
|
|
14
5
|
## Features
|
|
15
6
|
|
|
16
|
-
- **
|
|
17
|
-
- **Fully Responsive
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
7
|
+
- **Floating & Fullscreen layouts** — switch layouts with a single prop.
|
|
8
|
+
- **Fully Responsive** — mobile-first accordion dropdowns with optimized hit targets.
|
|
9
|
+
- **3-level Dropdowns** — top, secondary, and tertiary nested navigation items.
|
|
10
|
+
- **Dark Mode Ready** — auto-detects `.dark` on `<html>`, or forces a state with `preset`.
|
|
11
|
+
- **Inline SVG Icons** — no external CDNs, no extra network requests, no flash of missing icons.
|
|
12
|
+
- **Slot Support** — inject your custom logo and header actions directly into slots.
|
|
13
|
+
- **Pure CSS Customization** — customize background, blur, and colors using native CSS variables.
|
|
14
|
+
- **Full TypeScript** — all props and config interfaces are fully typed.
|
|
24
15
|
|
|
25
|
-
### Live
|
|
16
|
+
### Live Demo
|
|
26
17
|
|
|
27
18
|
[https://base-astro-psi.vercel.app/fullscreen-demo](https://base-astro-psi.vercel.app/fullscreen-demo)
|
|
28
19
|
|
|
20
|
+
---
|
|
21
|
+
|
|
29
22
|
## Installation
|
|
30
23
|
|
|
31
24
|
```bash
|
|
32
|
-
|
|
25
|
+
npm i @sofidevo/astro-dynamic-header
|
|
33
26
|
```
|
|
34
27
|
|
|
35
|
-
|
|
28
|
+
No external CDN scripts or stylesheet additions are required.
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```html
|
|
40
|
-
<script src="https://code.iconify.design/iconify-icon/3.0.0/iconify-icon.min.js"></script>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Add this to your main layout or in the `<head>` section of your Astro pages.
|
|
30
|
+
---
|
|
44
31
|
|
|
45
32
|
## Quick Start
|
|
46
33
|
|
|
47
|
-
### Basic Usage (Automatic Theme)
|
|
48
|
-
|
|
49
|
-
By default, the header uses `preset="auto"`, which automatically detects the theme based on a `.dark` class on your root element (`html` or `body`).
|
|
50
|
-
|
|
51
34
|
```astro
|
|
52
35
|
---
|
|
53
36
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
54
37
|
|
|
55
38
|
const menuItems = [
|
|
56
39
|
{ link: '/about', text: 'About' },
|
|
40
|
+
{ link: '/contact', text: 'Contact' },
|
|
57
41
|
];
|
|
58
42
|
---
|
|
59
43
|
|
|
60
|
-
|
|
61
|
-
<
|
|
44
|
+
<Header navigation={{ menuItems }}>
|
|
45
|
+
<a slot="logo" href="/">MyLogo</a>
|
|
46
|
+
</Header>
|
|
62
47
|
```
|
|
63
48
|
|
|
64
|
-
### Advanced Usage (Dual-Theme Customization)
|
|
65
|
-
|
|
66
|
-
You can provide custom colors for both light and dark modes simultaneously.
|
|
67
|
-
|
|
68
|
-
```astro
|
|
69
|
-
|
|
70
49
|
---
|
|
71
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
72
|
-
const navigation = {
|
|
73
|
-
menuItems: [
|
|
74
|
-
{ link: '/about', text: 'About' },
|
|
75
|
-
]
|
|
76
|
-
};
|
|
77
|
-
const theme = {
|
|
78
|
-
light: {
|
|
79
|
-
accentColor: "#3e1c71",
|
|
80
|
-
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
81
|
-
},
|
|
82
|
-
dark: {
|
|
83
|
-
accentColor: "#00ffff",
|
|
84
|
-
backgroundColor: "rgba(20, 20, 20, 0.9)"
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
<Header
|
|
90
|
-
preset="auto"
|
|
91
|
-
theme={theme}
|
|
92
|
-
navigation={navigation}
|
|
93
|
-
/>
|
|
94
|
-
```
|
|
95
50
|
|
|
96
51
|
## Component Props
|
|
97
52
|
|
|
98
|
-
### Header
|
|
53
|
+
### `<Header>`
|
|
99
54
|
|
|
100
|
-
| Prop | Type | Default | Description
|
|
101
|
-
| ------------ | ----------------------------- | ------------ |
|
|
102
|
-
| `headerType` | `"floating" \| "fullscreen"` | `"floating"` |
|
|
103
|
-
| `preset` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme
|
|
104
|
-
| `navigation` | `NavConfig` | `{}` |
|
|
105
|
-
| `theme` | `DualThemeConfig` | `{}` |
|
|
106
|
-
| `classNames` | `HeaderClassNames` | `{}` |
|
|
55
|
+
| Prop | Type | Default | Description |
|
|
56
|
+
| ------------ | ----------------------------- | ------------ | ------------------------------------------------- |
|
|
57
|
+
| `headerType` | `"floating" \| "fullscreen"` | `"floating"` | Layout style |
|
|
58
|
+
| `preset` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme mode. `"auto"` follows `.dark` on `<html>`. |
|
|
59
|
+
| `navigation` | `NavConfig` | `{}` | Menu items, home link, and custom CSS classes |
|
|
60
|
+
| `theme` | `DualThemeConfig` | `{}` | Optional theme overrides (prefer CSS variables) |
|
|
61
|
+
| `classNames` | `HeaderClassNames` | `{}` | Inject CSS classes into structural elements |
|
|
107
62
|
|
|
108
|
-
|
|
63
|
+
---
|
|
109
64
|
|
|
110
|
-
|
|
65
|
+
## Configuration Objects
|
|
111
66
|
|
|
112
|
-
|
|
113
|
-
| ------- | ------------- | ---------------------------- |
|
|
114
|
-
| `light` | `ThemeConfig` | Styles applied in light mode |
|
|
115
|
-
| `dark` | `ThemeConfig` | Styles applied in dark mode |
|
|
67
|
+
### `NavConfig`
|
|
116
68
|
|
|
117
|
-
|
|
69
|
+
| Property | Type | Default | Description |
|
|
70
|
+
| --------------------- | ------------ | ------- | ------------------------------------------------------- |
|
|
71
|
+
| `menuItems` | `MenuItem[]` | `[]` | Top-level navigation items |
|
|
72
|
+
| `homeUrl` | `string` | `"/"` | URL for the home link |
|
|
73
|
+
| `header__menu__class` | `string` | — | Extra CSS class(es) for the desktop `<nav>` element |
|
|
74
|
+
| `header__item__class` | `string` | — | Extra CSS class(es) for each top-level `<li>` menu item |
|
|
75
|
+
| `menu__link__class` | `string` | — | Extra CSS class(es) for each top-level `<a>` link |
|
|
118
76
|
|
|
119
|
-
|
|
120
|
-
| ----------------------- | -------- | ---------------- |
|
|
121
|
-
| `backgroundColor` | `string` | _Preset default_ |
|
|
122
|
-
| `backgroundColorOpaque` | `string` | _Preset default_ |
|
|
123
|
-
| `backdropBlur` | `string` | `"blur(20px)"` |
|
|
124
|
-
| `zIndex` | `number` | `10` |
|
|
125
|
-
| `textColor` | `string` | _Preset default_ |
|
|
126
|
-
| `accentColor` | `string` | _Preset default_ |
|
|
77
|
+
### `MenuItem`
|
|
127
78
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
79
|
+
| Property | Type | Required | Description |
|
|
80
|
+
| --------- | --------------------- | -------- | --------------------------------- |
|
|
81
|
+
| `link` | `string` | ✅ | URL the item points to |
|
|
82
|
+
| `text` | `string` | ✅ | Display label |
|
|
83
|
+
| `submenu` | `SecondaryMenuItem[]` | — | Optional nested items (2nd level) |
|
|
132
84
|
|
|
133
|
-
|
|
134
|
-
<Header theme={{
|
|
135
|
-
light: {
|
|
136
|
-
backgroundColor: "rgba(255, 255, 255, 0.7)", // Transparent header body
|
|
137
|
-
backgroundColorOpaque: "#ffffff", // Solid submenus
|
|
138
|
-
}
|
|
139
|
-
}} />
|
|
140
|
-
```
|
|
85
|
+
### `SecondaryMenuItem`
|
|
141
86
|
|
|
142
|
-
|
|
87
|
+
| Property | Type | Required | Description |
|
|
88
|
+
| --------- | -------------------- | -------- | --------------------------------- |
|
|
89
|
+
| `link` | `string` | ✅ | URL the item points to |
|
|
90
|
+
| `text` | `string` | ✅ | Display label |
|
|
91
|
+
| `submenu` | `TertiaryMenuItem[]` | — | Optional nested items (3rd level) |
|
|
143
92
|
|
|
144
|
-
|
|
145
|
-
> `CustomClassNames` was renamed to `HeaderClassNames` in v2.1. The old name still works as a type alias — no migration required.
|
|
93
|
+
### `TertiaryMenuItem`
|
|
146
94
|
|
|
147
|
-
|
|
95
|
+
| Property | Type | Required | Description |
|
|
96
|
+
| -------- | -------- | -------- | ---------------------- |
|
|
97
|
+
| `link` | `string` | ✅ | URL the item points to |
|
|
98
|
+
| `text` | `string` | ✅ | Display label |
|
|
148
99
|
|
|
149
|
-
|
|
150
|
-
| ----------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
151
|
-
| `container` | Outer `div` wrapping the header | **Positioning & Layout**: `top-0`, `z-50`, `fixed`, adjusting `max-width` and `mx-auto` logic. |
|
|
152
|
-
| `header` | Inner `<header>` element | **Appearance**: Shadows (`shadow-md`), borders (`border-b`), or custom transition durations. |
|
|
153
|
-
| `logo` | `<a>` tag surrounding the logo | **Interactions**: Hover states, custom focus rings, or adjusting the flex alignment of the logo group. |
|
|
154
|
-
| `logoText` | `<span>` tag containing the logo text | **Typography**: Font weights, text shadows, or specific tracking/leading classes. |
|
|
155
|
-
| `nav` | `<div>` wrapping the desktop navigation items | **Desktop Layout**: Spacing between the logo and the menu, or responsive visibility classes (`hidden md:flex`). |
|
|
156
|
-
| `mobileNav` | Root `<nav>` of the mobile slide-in panel | **Mobile Panel**: Extra backdrop blur, custom z-index, slide-in overrides. |
|
|
100
|
+
---
|
|
157
101
|
|
|
158
|
-
|
|
102
|
+
## Custom Class Names
|
|
159
103
|
|
|
160
|
-
|
|
104
|
+
The `classNames` prop lets you inject CSS classes into specific structural elements.
|
|
161
105
|
|
|
162
|
-
|
|
163
|
-
<Header
|
|
164
|
-
classNames={{
|
|
165
|
-
header: "shadow-xl border-b border-black/5 dark:border-white/10 transition-all duration-500",
|
|
166
|
-
container: "top-4 px-6",
|
|
167
|
-
mobileNav: "backdrop-blur-md",
|
|
168
|
-
}}
|
|
169
|
-
/>
|
|
170
|
-
```
|
|
106
|
+
### `HeaderClassNames`
|
|
171
107
|
|
|
172
|
-
|
|
108
|
+
| Property | Target element | Common use cases |
|
|
109
|
+
| ----------- | -------------------------------------- | ------------------------------------------------- |
|
|
110
|
+
| `container` | Outer `<div>` wrapping the header | Positioning, padding, `z-index` |
|
|
111
|
+
| `header` | Inner `<header>` element | Shadows, borders, transitions |
|
|
112
|
+
| `logo` | `<a>` tag surrounding the logo slot | Hover effects, focus rings |
|
|
113
|
+
| `logoText` | `<span>` containing the logo text | Font weight, tracking, text transforms |
|
|
114
|
+
| `nav` | `<div>` wrapping the desktop nav items | Spacing, alignment, responsive visibility |
|
|
115
|
+
| `mobileNav` | Root `<nav>` of the mobile panel | Backdrop blur, custom z-index, slide-in overrides |
|
|
173
116
|
|
|
174
117
|
```astro
|
|
118
|
+
<!-- Tailwind example -->
|
|
175
119
|
<Header
|
|
176
120
|
classNames={{
|
|
177
|
-
|
|
178
|
-
|
|
121
|
+
header: "shadow-xl border-b border-black/5 dark:border-white/10",
|
|
122
|
+
container: "top-4 px-6",
|
|
123
|
+
mobileNav: "backdrop-blur-md",
|
|
179
124
|
}}
|
|
180
125
|
/>
|
|
181
126
|
```
|
|
182
127
|
|
|
183
|
-
|
|
184
|
-
> Since these classes are injected using Astro's `class:list`, you can also pass objects or arrays if you need conditional logic for your custom classes.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
#### NavConfig
|
|
189
|
-
|
|
190
|
-
| Property | Type | Description |
|
|
191
|
-
| --------------------- | ------------ | ------------------------------------------------------- |
|
|
192
|
-
| `homeUrl` | `string` | URL for the home link (defaults to `/`) |
|
|
193
|
-
| `menuItems` | `MenuItem[]` | Array of navigation menu items |
|
|
194
|
-
| `header__menu__class` | `string` | Extra CSS class(es) for the desktop `<nav>` element |
|
|
195
|
-
| `header__item__class` | `string` | Extra CSS class(es) for each top-level `<li>` menu item |
|
|
196
|
-
| `menu__link__class` | `string` | Extra CSS class(es) for each top-level `<a>` link |
|
|
197
|
-
|
|
198
|
-
#### MenuItem
|
|
199
|
-
|
|
200
|
-
| Property | Type | Description |
|
|
201
|
-
| --------- | --------------------- | ----------------------------------- |
|
|
202
|
-
| `link` | `string` | URL the menu item points to |
|
|
203
|
-
| `text` | `string` | Label text for the menu item |
|
|
204
|
-
| `submenu` | `SecondaryMenuItem[]` | Optional array of nested menu items |
|
|
205
|
-
|
|
206
|
-
## Slots Support
|
|
207
|
-
|
|
208
|
-
The Header component provides a flexible slot system that allows you to add additional content:
|
|
209
|
-
|
|
210
|
-
### Available Slots
|
|
128
|
+
---
|
|
211
129
|
|
|
212
|
-
|
|
213
|
-
| --------- | --------------------- | --------------------- | ---------------------------------------- |
|
|
214
|
-
| `logo` | Header Desktop/Mobile | Always | Add your custom logo HTML/Components |
|
|
215
|
-
| `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.) |
|
|
130
|
+
## Slots
|
|
216
131
|
|
|
217
|
-
|
|
132
|
+
| Slot name | Visible on | Description |
|
|
133
|
+
| --------- | ---------------- | ---------------------------------------------------------- |
|
|
134
|
+
| `logo` | Header | Render your logo exactly as you need (native HTML/widgets) |
|
|
135
|
+
| `actions` | Desktop + mobile | Add buttons, links, or utility widgets |
|
|
218
136
|
|
|
219
137
|
```astro
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
};
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
<Header navigation={navigation}>
|
|
138
|
+
<Header navigation={{ menuItems }}>
|
|
139
|
+
<a slot="logo" href="/" class="logo-link">
|
|
140
|
+
<img src="/logo.svg" alt="Branding" width="40" />
|
|
141
|
+
<span>MyBrand</span>
|
|
142
|
+
</a>
|
|
229
143
|
<div slot="actions">
|
|
230
|
-
<
|
|
144
|
+
<a href="/login" class="btn">Log in</a>
|
|
231
145
|
</div>
|
|
232
146
|
</Header>
|
|
233
147
|
```
|
|
234
148
|
|
|
235
|
-
## Comprehensive Example
|
|
236
|
-
|
|
237
|
-
Below is a complete implementation example showcasing custom logo configuration, navigation with a home URL, and theme overrides.
|
|
238
|
-
|
|
239
|
-
```astro
|
|
240
|
-
---
|
|
241
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
242
|
-
|
|
243
|
-
const menuItems = [
|
|
244
|
-
{
|
|
245
|
-
link: "#",
|
|
246
|
-
text: "Services",
|
|
247
|
-
submenu: [
|
|
248
|
-
{ link: "/design", text: "Design" },
|
|
249
|
-
{ link: "/consulting", text: "Consulting" },
|
|
250
|
-
{
|
|
251
|
-
link: "#",
|
|
252
|
-
text: "Web Development",
|
|
253
|
-
submenu: [
|
|
254
|
-
{ link: "/web/frontend", text: "Frontend" },
|
|
255
|
-
{ link: "/web/backend", text: "Backend" },
|
|
256
|
-
{ link: "/web/fullstack", text: "Full Stack" },
|
|
257
|
-
],
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
},
|
|
261
|
-
{ link: "/about", text: "About" },
|
|
262
|
-
{ link: "/contact", text: "Contact" },
|
|
263
|
-
];
|
|
264
|
-
|
|
265
|
-
];
|
|
266
149
|
---
|
|
267
150
|
|
|
268
|
-
|
|
269
|
-
:root {
|
|
270
|
-
/* Pure CSS Theming Configuration */
|
|
271
|
-
--l-accent: #ff0000;
|
|
272
|
-
--l-bg: rgba(255, 255, 255, 0.8);
|
|
273
|
-
|
|
274
|
-
--d-accent: #00ffff;
|
|
275
|
-
--d-bg: rgba(20, 20, 20, 0.9);
|
|
276
|
-
}
|
|
277
|
-
</style>
|
|
151
|
+
## Customization & Theme Config
|
|
278
152
|
|
|
279
|
-
|
|
280
|
-
headerType="floating"
|
|
281
|
-
preset="dark"
|
|
282
|
-
navigation={{
|
|
283
|
-
homeUrl: "/",
|
|
284
|
-
menuItems: menuItems,
|
|
285
|
-
header__menu__class: "flex gap-6",
|
|
286
|
-
menu__link__class: "font-medium",
|
|
287
|
-
}}
|
|
288
|
-
classNames={{
|
|
289
|
-
header: "shadow-xl",
|
|
290
|
-
mobileNav: "backdrop-blur-md",
|
|
291
|
-
}}
|
|
292
|
-
>
|
|
293
|
-
<a slot="logo" href="/" style="display: flex; align-items: center; gap: 10px; color: inherit; text-decoration: none;">
|
|
294
|
-
<img src="https://sofidev.blog/img/branding/logo.webp" alt="My Site Logo" width="44" />
|
|
295
|
-
<span style="font-weight: bold; font-size: 1.2rem;">SofiDev</span>
|
|
296
|
-
</a>
|
|
297
|
-
<button slot="actions">Login</button>
|
|
298
|
-
</Header>
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
## Header Types
|
|
153
|
+
You can fully customize the color scheme using **CSS Custom Properties** (recommended) or the `theme` prop.
|
|
302
154
|
|
|
303
|
-
###
|
|
155
|
+
### Option 1: Native CSS Variables (Recommended)
|
|
304
156
|
|
|
305
|
-
|
|
306
|
-
- Rounded corners
|
|
307
|
-
- Padding around container
|
|
308
|
-
- Perfect for modern, card-like designs
|
|
157
|
+
Set CSS variables globally in your `:root` style block:
|
|
309
158
|
|
|
310
|
-
|
|
159
|
+
```css
|
|
160
|
+
:root {
|
|
161
|
+
/* Light theme overrides */
|
|
162
|
+
--l-accent: #7c3aed;
|
|
163
|
+
--l-bg: rgba(255, 255, 255, 0.85);
|
|
164
|
+
--l-bg-opaque: #ffffff;
|
|
165
|
+
--l-text: #1a1a1a;
|
|
166
|
+
--l-blur: blur(20px);
|
|
167
|
+
|
|
168
|
+
/* Dark theme overrides */
|
|
169
|
+
--d-accent: #a78bfa;
|
|
170
|
+
--d-bg: rgba(10, 10, 10, 0.85);
|
|
171
|
+
--d-bg-opaque: #0a0a0a;
|
|
172
|
+
--d-text: #f5f5f5;
|
|
173
|
+
--d-blur: blur(20px);
|
|
174
|
+
|
|
175
|
+
/* Hamburger lines override */
|
|
176
|
+
--color-hamburger-lines: #7c3aed;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
311
179
|
|
|
312
|
-
|
|
313
|
-
- No border radius
|
|
314
|
-
- Edge-to-edge design
|
|
315
|
-
- Ideal for traditional website layouts
|
|
180
|
+
### Option 2: JS Theme Prop
|
|
316
181
|
|
|
317
|
-
|
|
182
|
+
```astro
|
|
183
|
+
---
|
|
184
|
+
import { defaultThemes } from '@sofidevo/astro-dynamic-header';
|
|
318
185
|
|
|
319
|
-
|
|
186
|
+
const theme = {
|
|
187
|
+
light: {
|
|
188
|
+
...defaultThemes.light,
|
|
189
|
+
accentColor: "#7c3aed",
|
|
190
|
+
backgroundColor: "rgba(255, 255, 255, 0.85)",
|
|
191
|
+
},
|
|
192
|
+
dark: {
|
|
193
|
+
...defaultThemes.dark,
|
|
194
|
+
accentColor: "#a78bfa",
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
---
|
|
320
198
|
|
|
321
|
-
|
|
322
|
-
:root {
|
|
323
|
-
--light-spot-color: #00ffff;
|
|
324
|
-
--color-tertiary: #ffffff;
|
|
325
|
-
--color-hamburger-lines: #ffffff;
|
|
326
|
-
}
|
|
199
|
+
<Header theme={theme} />
|
|
327
200
|
```
|
|
328
201
|
|
|
329
|
-
|
|
202
|
+
> [!IMPORTANT]
|
|
203
|
+
> When using transparent backgrounds, always supply a solid fallback in `backgroundColorOpaque`. Submenus and mobile panels utilize this solid color to prevent visual glitches with nested blur effects.
|
|
330
204
|
|
|
331
|
-
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## TypeScript
|
|
332
208
|
|
|
333
209
|
```astro
|
|
334
210
|
---
|
|
335
211
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
336
|
-
import { defaultThemes } from '@sofidevo/astro-dynamic-header';
|
|
337
212
|
import type {
|
|
213
|
+
MenuItem,
|
|
338
214
|
NavConfig,
|
|
339
|
-
DualThemeConfig,
|
|
340
215
|
HeaderClassNames,
|
|
341
|
-
|
|
342
|
-
SecondaryMenuItem
|
|
216
|
+
HeaderProps,
|
|
343
217
|
} from '@sofidevo/astro-dynamic-header';
|
|
344
218
|
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
link: '/products',
|
|
349
|
-
text: 'Products',
|
|
350
|
-
submenu: [
|
|
351
|
-
{ link: '/software', text: 'Software' },
|
|
352
|
-
{ link: '/hardware', text: 'Hardware' }
|
|
353
|
-
]
|
|
354
|
-
}
|
|
355
|
-
],
|
|
356
|
-
header__menu__class: "flex gap-6",
|
|
357
|
-
};
|
|
219
|
+
const menuItems: MenuItem[] = [
|
|
220
|
+
{ link: '/about', text: 'About' }
|
|
221
|
+
];
|
|
358
222
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
dark: { ...defaultThemes.dark, accentColor: "#00ffff", backgroundColor: "rgba(10, 10, 10, 0.9)" },
|
|
223
|
+
const navigation: NavConfig = {
|
|
224
|
+
menuItems,
|
|
225
|
+
homeUrl: "/",
|
|
226
|
+
header__menu__class: "flex gap-4"
|
|
364
227
|
};
|
|
365
228
|
|
|
366
229
|
const classNames: HeaderClassNames = {
|
|
367
|
-
header: "shadow-
|
|
368
|
-
mobileNav: "backdrop-blur-md",
|
|
230
|
+
header: "shadow-lg"
|
|
369
231
|
};
|
|
370
232
|
---
|
|
371
233
|
|
|
372
234
|
<Header
|
|
373
235
|
navigation={navigation}
|
|
374
236
|
classNames={classNames}
|
|
375
|
-
preset="auto"
|
|
376
237
|
/>
|
|
377
238
|
```
|
|
378
239
|
|
|
379
|
-
### Available Types
|
|
380
|
-
|
|
381
|
-
| Type | Description |
|
|
382
|
-
| ------------------- | ----------------------------------------------- |
|
|
383
|
-
| `MenuItem` | Top-level menu item with optional properties |
|
|
384
|
-
| `SecondaryMenuItem` | Second-level menu item |
|
|
385
|
-
| `TertiaryMenuItem` | Third-level menu item |
|
|
386
|
-
| `NavConfig` | Navigation config (items + nested class props) |
|
|
387
|
-
| `ThemeConfig` | Individual theme settings (colors, blur, etc.) |
|
|
388
|
-
| `DualThemeConfig` | Combined settings for light and dark modes |
|
|
389
|
-
| `HeaderClassNames` | Class overrides for structural wrapper elements |
|
|
390
|
-
| `CustomClassNames` | **Deprecated alias** for `HeaderClassNames` |
|
|
391
|
-
| `HeaderProps` | Main props for the Header component |
|
|
392
|
-
|
|
393
|
-
## Browser Support
|
|
394
|
-
|
|
395
|
-
- All modern browsers (Chrome, Firefox, Safari, Edge)
|
|
396
|
-
- Mobile responsive design with optimized touch targets
|
|
397
|
-
- Supports CSS `backdrop-filter` for glassmorphism
|
|
398
|
-
- Automatic theme switching based on OS or site preference via `.dark` class
|
|
399
|
-
|
|
400
|
-
## Troubleshooting
|
|
401
|
-
|
|
402
|
-
### Import Issues
|
|
403
|
-
|
|
404
|
-
If you encounter import errors, try these solutions:
|
|
405
|
-
|
|
406
|
-
1. **Use direct subpath import:**
|
|
407
|
-
|
|
408
|
-
```astro
|
|
409
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
2. **Check relative imports in TS:**
|
|
413
|
-
In some environments (like `node16`), you might need to use the `.js` extension even for TypeScript files when importing from the package internals, though the main entry point handles this for you.
|
|
414
|
-
|
|
415
|
-
3. **Verify TypeScript configuration:**
|
|
416
|
-
|
|
417
|
-
```json
|
|
418
|
-
// tsconfig.json
|
|
419
|
-
{
|
|
420
|
-
"compilerOptions": {
|
|
421
|
-
"moduleResolution": "bundler",
|
|
422
|
-
"allowImportingTsExtensions": true
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Compatibility
|
|
428
|
-
|
|
429
|
-
- Astro 4.x and 5.x
|
|
430
|
-
- SSG Projects (Static Site Generation)
|
|
431
|
-
- SSR Projects (Server-Side Rendering)
|
|
432
|
-
- Hybrid Projects (output: 'hybrid')
|
|
433
|
-
|
|
434
|
-
## Live Examples
|
|
435
|
-
|
|
436
|
-
Visit our demo website to see the component in action with interactive examples and complete documentation.
|
|
437
|
-
|
|
438
|
-
## License
|
|
439
|
-
|
|
440
|
-
MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
441
|
-
|
|
442
|
-
## Support
|
|
443
|
-
|
|
444
|
-
If you find this package helpful, please consider giving it a star on GitHub!
|
|
445
|
-
|
|
446
240
|
---
|
|
447
241
|
|
|
448
|
-
##
|
|
449
|
-
|
|
450
|
-
### v2.2 — Performance & DX Optimization
|
|
451
|
-
|
|
452
|
-
#### Breaking changes
|
|
453
|
-
|
|
454
|
-
- **Removed `logo` Object Configuration prop**: The `logo` property and `LogoConfig` interface have been removed. You should now use `<slot name="logo" />` to render your logo exactly as you need, passing native HTML or Astro components.
|
|
455
|
-
- **Removed `color` prop from `HamburgerButton`**: The `HamburgerButton` now inherits `--text-color` natively via CSS, which fixes a bug where the button would not properly update its color when switching to dark mode. Any direct uses of `<HamburgerButton color="..." />` will fail to compile in TypeScript and should be updated to rely on global CSS variables or context inheritance.
|
|
456
|
-
|
|
457
|
-
#### New features and enhancements
|
|
242
|
+
## Troubleshooting & FAQ
|
|
458
243
|
|
|
459
|
-
|
|
460
|
-
- **Dead Code Elimination**: Cleaned up repetitive prop destructuring inside `MobileNav.astro`.
|
|
244
|
+
### Import issues
|
|
461
245
|
|
|
462
|
-
|
|
246
|
+
Import using the direct subpath:
|
|
463
247
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
New `mobileNav` slot targets the mobile slide-in `<nav>` panel.
|
|
248
|
+
```astro
|
|
249
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
250
|
+
```
|
|
468
251
|
|
|
469
|
-
|
|
470
|
-
| Prop | Targets |
|
|
471
|
-
|------|---------|
|
|
472
|
-
| `logo__container__class` | Logo `<a>` wrapper |
|
|
473
|
-
| `logo__text__class` | Logo text `<span>` |
|
|
252
|
+
Other components are exported similarly:
|
|
474
253
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
| `menu__link__class` | Each top-level `<a>` |
|
|
254
|
+
```astro
|
|
255
|
+
import NavMenu from '@sofidevo/astro-dynamic-header/NavMenu';
|
|
256
|
+
import MobileNav from '@sofidevo/astro-dynamic-header/MobileNav';
|
|
257
|
+
import ChevronIcon from '@sofidevo/astro-dynamic-header/ChevronIcon';
|
|
258
|
+
```
|
|
481
259
|
|
|
482
|
-
|
|
483
|
-
```ts
|
|
484
|
-
import { defaultThemes } from "@sofidevo/astro-dynamic-header";
|
|
485
|
-
const theme = { dark: { ...defaultThemes.dark, accentColor: "#f43f5e" } };
|
|
486
|
-
```
|
|
260
|
+
### Acordion icons not showing up
|
|
487
261
|
|
|
488
|
-
|
|
262
|
+
Icons are rendered as inline SVG components. If you are upgrading from `v1.x` or `v2.0` and have the old ChevronIcon CDN `<script>` tag in your layout `<head>`, you can safely remove it.
|
|
489
263
|
|
|
490
|
-
|
|
491
|
-
- `HamburgerButton` color is now explicitly wired from the resolved theme (no visual change).
|
|
264
|
+
---
|
|
492
265
|
|
|
493
|
-
|
|
266
|
+
## Compatibility
|
|
494
267
|
|
|
495
|
-
-
|
|
268
|
+
- **Astro 7.x**
|
|
269
|
+
- SSG, SSR, and Hybrid project outputs.
|
|
496
270
|
|
|
497
|
-
|
|
271
|
+
---
|
|
498
272
|
|
|
499
|
-
|
|
500
|
-
> Breaking change: `logo` changed from `string` to `LogoConfig`, `navigation` changed from `MenuItem[]` to `NavConfig`.
|
|
273
|
+
## License
|
|
501
274
|
|
|
502
|
-
|
|
503
|
-
- `navigation` prop restructured to `NavConfig` object (`homeUrl`, `menuItems`).
|
|
504
|
-
- `classNames` prop introduced for CSS class injection (`CustomClassNames`).
|
|
505
|
-
- Dual-theme support via `DualThemeConfig` (`light` + `dark`).
|
|
275
|
+
MIT License.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofidevo/astro-dynamic-header",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "A dynamic Astro header component that switches between floating and fullscreen styles",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
"./NavMenu": "./src/NavMenu.astro",
|
|
15
15
|
"./MobileNav": "./src/MobileNav.astro",
|
|
16
16
|
"./HamburgerButton": "./src/HamburgerButton.astro",
|
|
17
|
+
"./ChevronIcon": "./src/ChevronIcon.astro",
|
|
18
|
+
"./defaults": "./src/defaults.ts",
|
|
17
19
|
"./Header.astro": "./src/Header.astro",
|
|
18
20
|
"./NavMenu.astro": "./src/NavMenu.astro",
|
|
19
21
|
"./MobileNav.astro": "./src/MobileNav.astro",
|
|
20
|
-
"./HamburgerButton.astro": "./src/HamburgerButton.astro"
|
|
22
|
+
"./HamburgerButton.astro": "./src/HamburgerButton.astro",
|
|
23
|
+
"./ChevronIcon.astro": "./src/ChevronIcon.astro"
|
|
21
24
|
},
|
|
22
25
|
"files": [
|
|
23
26
|
"src/*.ts",
|
|
@@ -50,13 +53,13 @@
|
|
|
50
53
|
"author": "SofiDev contact@sofidev.top",
|
|
51
54
|
"license": "MIT",
|
|
52
55
|
"peerDependencies": {
|
|
53
|
-
"astro": "^
|
|
56
|
+
"astro": "^7.0.0"
|
|
54
57
|
},
|
|
55
58
|
"devDependencies": {
|
|
56
59
|
"@astrojs/check": "^0.9.9",
|
|
57
60
|
"@types/jsdom": "^21.1.7",
|
|
58
61
|
"@vitest/coverage-v8": "^3.2.4",
|
|
59
|
-
"astro": "^
|
|
62
|
+
"astro": "^7.0.4",
|
|
60
63
|
"jsdom": "^26.1.0",
|
|
61
64
|
"typescript": "^5.0.0",
|
|
62
65
|
"vitest": "^3.2.4"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { class: className, ...rest } = Astro.props;
|
|
3
|
+
|
|
4
|
+
const viewBox = "0 0 24 24";
|
|
5
|
+
const content = `<path class="hlhawq"/>`;
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<svg
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="1em"
|
|
11
|
+
height="1em"
|
|
12
|
+
class={className}
|
|
13
|
+
viewBox={viewBox}
|
|
14
|
+
{...rest}
|
|
15
|
+
set:html={content}
|
|
16
|
+
/>
|
|
17
|
+
|
|
18
|
+
<style is:inline>
|
|
19
|
+
.hlhawq {
|
|
20
|
+
fill: currentColor;
|
|
21
|
+
d: path(
|
|
22
|
+
"M11.646 15.146L5.854 9.354a.5.5 0 0 1 .353-.854h11.586a.5.5 0 0 1 .353.854l-5.793 5.792a.5.5 0 0 1-.707 0"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
</style>
|
package/src/Header.astro
CHANGED
|
@@ -155,6 +155,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
155
155
|
<div class:list={["nav-menu-wrapper", classNames.nav]}>
|
|
156
156
|
<NavMenu
|
|
157
157
|
menuItems={menuItems}
|
|
158
|
+
homeUrl={homeUrl}
|
|
158
159
|
header__menu__class={header__menu__class}
|
|
159
160
|
header__item__class={header__item__class}
|
|
160
161
|
menu__link__class={menu__link__class}
|
|
@@ -173,6 +174,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
173
174
|
|
|
174
175
|
<MobileNav
|
|
175
176
|
menuItems={menuItems}
|
|
177
|
+
homeUrl={homeUrl}
|
|
176
178
|
type={headerType}
|
|
177
179
|
mobileNav__class={classNames.mobileNav}
|
|
178
180
|
>
|
package/src/MobileNav.astro
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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: "floating" or "fullscreen" */
|
|
@@ -10,6 +11,8 @@ export interface Props {
|
|
|
10
11
|
showHomeLink?: boolean;
|
|
11
12
|
/** Text for the home link (defaults to "Home") */
|
|
12
13
|
homeText?: string;
|
|
14
|
+
/** The destination URL for the home link. @default "/" */
|
|
15
|
+
homeUrl?: string;
|
|
13
16
|
/** Extra CSS class(es) for the root mobile `<nav>` panel. */
|
|
14
17
|
mobileNav__class?: string;
|
|
15
18
|
}
|
|
@@ -19,7 +22,8 @@ const {
|
|
|
19
22
|
mobileNav__class,
|
|
20
23
|
menuItems = [],
|
|
21
24
|
showHomeLink = true,
|
|
22
|
-
homeText = "Home"
|
|
25
|
+
homeText = "Home",
|
|
26
|
+
homeUrl = "/",
|
|
23
27
|
} = Astro.props;
|
|
24
28
|
|
|
25
29
|
const pagePathname = Astro.url.pathname;
|
|
@@ -34,11 +38,11 @@ const pagePathname = Astro.url.pathname;
|
|
|
34
38
|
>
|
|
35
39
|
<ol class="mobile-menu">
|
|
36
40
|
{
|
|
37
|
-
showHomeLink && pagePathname !==
|
|
41
|
+
showHomeLink && pagePathname !== homeUrl && (
|
|
38
42
|
<li class="header__item mobile-header__item">
|
|
39
43
|
<a
|
|
40
44
|
class="menu__link mobile-menu__link"
|
|
41
|
-
href=
|
|
45
|
+
href={homeUrl}
|
|
42
46
|
data-astro-prefetch="hover"
|
|
43
47
|
>
|
|
44
48
|
{homeText}
|
|
@@ -58,13 +62,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
58
62
|
>
|
|
59
63
|
<summary class:list={["menu__summary", { active: isActive }]}>
|
|
60
64
|
{item.text}
|
|
61
|
-
<iconify-
|
|
62
|
-
class="iconify-arrow"
|
|
63
|
-
icon="codicon:triangle-down"
|
|
64
|
-
width="15"
|
|
65
|
-
height="15"
|
|
66
|
-
style="color: var(--accent-color);"
|
|
67
|
-
/>
|
|
65
|
+
<ChevronIcon class="iconify-arrow" width={15} height={15} />
|
|
68
66
|
</summary>
|
|
69
67
|
<ol class="mobile-submenu">
|
|
70
68
|
{item?.submenu?.map((sub: SecondaryMenuItem) => {
|
|
@@ -85,12 +83,10 @@ const pagePathname = Astro.url.pathname;
|
|
|
85
83
|
]}
|
|
86
84
|
>
|
|
87
85
|
{sub.text}
|
|
88
|
-
<
|
|
86
|
+
<ChevronIcon
|
|
89
87
|
class="iconify-arrow"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
height="15"
|
|
93
|
-
style="color: var(--accent-color);"
|
|
88
|
+
width={15}
|
|
89
|
+
height={15}
|
|
94
90
|
/>
|
|
95
91
|
</summary>
|
|
96
92
|
<ol class="mobile-subsubmenu">
|
|
@@ -240,7 +236,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
240
236
|
overflow: clip;
|
|
241
237
|
}
|
|
242
238
|
|
|
243
|
-
.mobile-details:open iconify-
|
|
239
|
+
.mobile-details:open svg.iconify-arrow {
|
|
244
240
|
transform: rotate(180deg);
|
|
245
241
|
}
|
|
246
242
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
const props = Astro.props;
|
|
3
|
+
|
|
4
|
+
const regex = /\sid="(\S+)"/g;
|
|
5
|
+
const counters = new Map<string, number>();
|
|
6
|
+
|
|
7
|
+
function nextID(id: string) {
|
|
8
|
+
id = id.replace(/[0-9]+$/, "") || "a";
|
|
9
|
+
const count = counters.get(id) || 0;
|
|
10
|
+
counters.set(id, count + 1);
|
|
11
|
+
return count ? `${id}${count}` : id;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function replaceIDs(body: string) {
|
|
15
|
+
const ids: string[] = [];
|
|
16
|
+
let match;
|
|
17
|
+
while ((match = regex.exec(body))) ids.push(match[1]);
|
|
18
|
+
if (!ids.length) return body;
|
|
19
|
+
const suffix =
|
|
20
|
+
"suffix" + ((Math.random() * 16777216) | Date.now()).toString(16);
|
|
21
|
+
ids.forEach((id) => {
|
|
22
|
+
const newID = nextID(id);
|
|
23
|
+
const escapedID = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24
|
+
body = body.replace(
|
|
25
|
+
new RegExp('([#;"])(' + escapedID + ')([")]|\\.[a-z])', "g"),
|
|
26
|
+
"$1" + newID + suffix + "$3",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
body = body.replace(new RegExp(suffix, "g"), "");
|
|
30
|
+
return body;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const viewBox = "0 0 24 24";
|
|
34
|
+
const content = replaceIDs(
|
|
35
|
+
`<defs><mask id="SVGnw0mAdjE"><path d="M12 2c5.52 0 10 4.48 10 10c0 5.52 -4.48 10 -10 10c-5.52 0 -10 -4.48 -10 -10c0 -5.52 4.48 -10 10 -10Z" class="n1mjuz"><animate fill="freeze" attributeName="d" dur="0.4s" values="M12 6c3.31 0 6 2.69 6 6c0 3.31 -2.69 6 -6 6c-3.31 0 -6 -2.69 -6 -6c0 -3.31 2.69 -6 6 -6Z;M12 2c5.52 0 10 4.48 10 10c0 5.52 -4.48 10 -10 10c-5.52 0 -10 -4.48 -10 -10c0 -5.52 4.48 -10 10 -10Z"/><set fill="freeze" attributeName="opacity" begin="0.4s" to="0"/></path><path d="M18 -4c5.52 0 10 4.48 10 10c0 5.52 -4.48 10 -10 10c-5.52 0 -10 -4.48 -10 -10c0 -5.52 4.48 -10 10 -10Z"><animate fill="freeze" attributeName="d" dur="0.4s" values="M22 -4c3.31 0 6 2.69 6 6c0 3.31 -2.69 6 -6 6c-3.31 0 -6 -2.69 -6 -6c0 -3.31 2.69 -6 6 -6Z;M18 -4c5.52 0 10 4.48 10 10c0 5.52 -4.48 10 -10 10c-5.52 0 -10 -4.48 -10 -10c0 -5.52 4.48 -10 10 -10Z"/><set fill="freeze" attributeName="opacity" begin="0.4s" to="0"/></path></mask></defs><path class="smogax"><animate fill="freeze" attributeName="opacity" dur="0.4s" values="1;0"/></path><g class="cuyn6t"><path mask="url(#SVGnw0mAdjE)" class="t_ev7s"/><path opacity="0" class="izfmla"><set fill="freeze" attributeName="opacity" begin="0.4s" to="1"/></path></g><g stroke-dashoffset="4" class="abf34n"><path class="k7_l3l"><animate attributeName="stroke-dashoffset" begin="0.4s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="hhuniw"><animate attributeName="stroke-dashoffset" begin="1.07s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="z8cgtd"><animate attributeName="stroke-dashoffset" begin="1.74s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="drcfta"><animate attributeName="stroke-dashoffset" begin="2.41s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="yij7vx"><animate attributeName="stroke-dashoffset" begin="3.08s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="emoajy"><animate attributeName="stroke-dashoffset" begin="3.75s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="f1to6s"><animate attributeName="stroke-dashoffset" begin="4.42s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="niixso"><animate attributeName="stroke-dashoffset" begin="5.09s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path><path class="z8cgtd"><animate attributeName="stroke-dashoffset" begin="5.76s" dur="6s" keyTimes="0;0.08;0.22;0.3;1" repeatCount="indefinite" values="4;0;0;4;4"/></path></g>`,
|
|
36
|
+
);
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
<svg
|
|
40
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
41
|
+
width="25px"
|
|
42
|
+
height="25px"
|
|
43
|
+
viewBox={viewBox}
|
|
44
|
+
{...props}
|
|
45
|
+
set:html={content}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
<style is:inline>
|
|
49
|
+
.n1mjuz {
|
|
50
|
+
fill: var(--svg-color--fff, #fff);
|
|
51
|
+
}
|
|
52
|
+
.smogax {
|
|
53
|
+
fill: none;
|
|
54
|
+
stroke: white;
|
|
55
|
+
stroke-linecap: round;
|
|
56
|
+
stroke-linejoin: round;
|
|
57
|
+
stroke-width: var(--svg-stroke-width--2px, 2px);
|
|
58
|
+
d: path(
|
|
59
|
+
"M12 21v1M21 12h1M12 3v-1M3 12h-1M18.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"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
.t_ev7s {
|
|
63
|
+
d: path("M0 0h24v24H0z");
|
|
64
|
+
}
|
|
65
|
+
.izfmla {
|
|
66
|
+
stroke: white;
|
|
67
|
+
stroke-linecap: round;
|
|
68
|
+
stroke-linejoin: round;
|
|
69
|
+
stroke-width: var(--svg-stroke-width--2px, 2px);
|
|
70
|
+
d: path(
|
|
71
|
+
"M7 6c0 6.08 4.92 11 11 11c0.53 0 1.05 -0.04 1.56 -0.11c-1.61 2.47 -4.39 4.11 -7.56 4.11c-4.97 0 -9 -4.03 -9 -9c0 -3.17 1.64 -5.95 4.11 -7.56c-0.07 0.51 -0.11 1.03 -0.11 1.56Z"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
.abf34n {
|
|
75
|
+
fill: none;
|
|
76
|
+
stroke: white;
|
|
77
|
+
stroke-dasharray: 4;
|
|
78
|
+
stroke-linecap: round;
|
|
79
|
+
stroke-linejoin: round;
|
|
80
|
+
}
|
|
81
|
+
.k7_l3l {
|
|
82
|
+
d: path("M12 5h1.5M12 5h-1.5M12 5v1.5M12 5v-1.5");
|
|
83
|
+
}
|
|
84
|
+
.hhuniw {
|
|
85
|
+
d: path("M17 11h1.5M17 11h-1.5M17 11v1.5M17 11v-1.5");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.z8cgtd {
|
|
89
|
+
d: path("M20 5h1.5M20 5h-1.5M20 5v1.5M20 5v-1.5");
|
|
90
|
+
}
|
|
91
|
+
.drcfta {
|
|
92
|
+
d: path("M12 4h1.5M12 4h-1.5M12 4v1.5M12 4v-1.5");
|
|
93
|
+
}
|
|
94
|
+
.yij7vx {
|
|
95
|
+
d: path("M18 12h1.5M18 12h-1.5M18 12v1.5M18 12v-1.5");
|
|
96
|
+
}
|
|
97
|
+
.emoajy {
|
|
98
|
+
d: path("M19 4h1.5M19 4h-1.5M19 4v1.5M19 4v-1.5");
|
|
99
|
+
}
|
|
100
|
+
.f1to6s {
|
|
101
|
+
d: path("M13 4h1.5M13 4h-1.5M13 4v1.5M13 4v-1.5");
|
|
102
|
+
}
|
|
103
|
+
.niixso {
|
|
104
|
+
d: path("M19 11h1.5M19 11h-1.5M19 11v1.5M19 11v-1.5");
|
|
105
|
+
}
|
|
106
|
+
</style>
|
package/src/NavMenu.astro
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
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
|
/**
|
|
@@ -19,6 +20,11 @@ export interface Props {
|
|
|
19
20
|
* @default "Home"
|
|
20
21
|
*/
|
|
21
22
|
homeText?: string;
|
|
23
|
+
/**
|
|
24
|
+
* The destination URL for the home link.
|
|
25
|
+
* @default "/"
|
|
26
|
+
*/
|
|
27
|
+
homeUrl?: string;
|
|
22
28
|
/** Extra CSS class(es) for the `<nav>` element. */
|
|
23
29
|
header__menu__class?: string;
|
|
24
30
|
/** Extra CSS class(es) for each top-level `<li>` item. */
|
|
@@ -31,6 +37,7 @@ const {
|
|
|
31
37
|
menuItems = [],
|
|
32
38
|
showHomeLink = true,
|
|
33
39
|
homeText = "Home",
|
|
40
|
+
homeUrl = "/",
|
|
34
41
|
header__menu__class,
|
|
35
42
|
header__item__class,
|
|
36
43
|
menu__link__class,
|
|
@@ -144,11 +151,11 @@ const pagePathname = Astro.url.pathname;
|
|
|
144
151
|
<nav class:list={["header__menu", header__menu__class]} id="header-menu">
|
|
145
152
|
<ul class="menu">
|
|
146
153
|
{
|
|
147
|
-
showHomeLink && pagePathname !==
|
|
154
|
+
showHomeLink && pagePathname !== homeUrl && (
|
|
148
155
|
<li class:list={["header__item", header__item__class]}>
|
|
149
156
|
<a
|
|
150
157
|
class:list={["menu__link", menu__link__class]}
|
|
151
|
-
href=
|
|
158
|
+
href={homeUrl}
|
|
152
159
|
data-astro-prefetch="hover"
|
|
153
160
|
>
|
|
154
161
|
{homeText}
|
|
@@ -162,12 +169,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
162
169
|
<a class:list={["menu__link", menu__link__class]} href={item.link}>
|
|
163
170
|
{item.text}
|
|
164
171
|
{item.submenu && (
|
|
165
|
-
<
|
|
166
|
-
class="iconify-arrow"
|
|
167
|
-
icon="codicon:triangle-down"
|
|
168
|
-
width="15"
|
|
169
|
-
height="15"
|
|
170
|
-
/>
|
|
172
|
+
<ChevronIcon class="arrow-down" width={15} height={15} />
|
|
171
173
|
)}
|
|
172
174
|
</a>
|
|
173
175
|
{item.submenu && (
|
|
@@ -177,11 +179,10 @@ const pagePathname = Astro.url.pathname;
|
|
|
177
179
|
<a href={sub.link}>
|
|
178
180
|
{sub.text}
|
|
179
181
|
{sub.submenu && sub.submenu.length > 0 && (
|
|
180
|
-
<
|
|
181
|
-
class="
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
height="15"
|
|
182
|
+
<ChevronIcon
|
|
183
|
+
class="ChevronIcon-arrow"
|
|
184
|
+
width={15}
|
|
185
|
+
height={15}
|
|
185
186
|
/>
|
|
186
187
|
)}
|
|
187
188
|
</a>
|
|
@@ -255,11 +256,11 @@ const pagePathname = Astro.url.pathname;
|
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
|
|
258
|
-
.
|
|
259
|
+
.ChevronIcon-arrow {
|
|
259
260
|
transition: transform 0.3s linear;
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
.menu__link:hover > .
|
|
263
|
+
.menu__link:hover > .ChevronIcon-arrow {
|
|
263
264
|
transform: rotate(180deg);
|
|
264
265
|
}
|
|
265
266
|
|
|
@@ -325,11 +326,12 @@ const pagePathname = Astro.url.pathname;
|
|
|
325
326
|
.submenu::after {
|
|
326
327
|
content: "";
|
|
327
328
|
position: absolute;
|
|
328
|
-
top: -
|
|
329
|
+
top: -1.1em;
|
|
329
330
|
width: 100%;
|
|
330
331
|
z-index: -3;
|
|
331
332
|
pointer-events: none;
|
|
332
|
-
height:
|
|
333
|
+
height: 7em;
|
|
334
|
+
right: 0;
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
.subsubmenu {
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
.gutruu {
|
|
19
|
+
stroke: currentColor;
|
|
20
|
+
stroke-linecap: round;
|
|
21
|
+
stroke-linejoin: round;
|
|
22
|
+
stroke-width: var(--svg-stroke-width--2px, 2px);
|
|
23
|
+
}
|
|
24
|
+
.xkoryc {
|
|
25
|
+
fill: currentColor;
|
|
26
|
+
stroke-dasharray: 34;
|
|
27
|
+
d: path(
|
|
28
|
+
"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"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
.ft5dvn {
|
|
32
|
+
fill: none;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
package/src/index.ts
CHANGED
|
@@ -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
|
+
|