@sofidevo/astro-dynamic-header 1.1.34 → 2.0.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.
- package/README.md +232 -231
- package/package.json +4 -4
- package/src/Header.astro +197 -82
- package/src/MobileNav.astro +16 -28
- package/src/NavMenu.astro +27 -28
- package/src/index.ts +56 -6
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A dynamic, responsive header component for Astro projects that can switch between floating and fullscreen styles with multi-level dropdown navigation support.
|
|
4
4
|
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> **Breaking Changes**: Version 2.0+ introduces a restructured configuration object. If you are upgrading from an older version, please review the [Component Props](#component-props) and the [Comprehensive Example](#comprehensive-example) to migrate your configuration.
|
|
7
|
+
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
10
|
- **Dynamic Styles**: Switch between floating and fullscreen header layouts
|
|
@@ -12,9 +15,9 @@ A dynamic, responsive header component for Astro projects that can switch betwee
|
|
|
12
15
|
- **Customizable**: Extensive customization options for colors, sizes, and behavior
|
|
13
16
|
- **Astro Optimized**: Built specifically for Astro framework
|
|
14
17
|
|
|
15
|
-
|
|
16
18
|
### Live demo
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
[https://base-astro-psi.vercel.app/fullscreen-demo](https://base-astro-psi.vercel.app/fullscreen-demo)
|
|
18
21
|
|
|
19
22
|
## Installation
|
|
20
23
|
|
|
@@ -34,86 +37,54 @@ Add this to your main layout or in the `<head>` section of your Astro pages.
|
|
|
34
37
|
|
|
35
38
|
## Quick Start
|
|
36
39
|
|
|
37
|
-
### Basic Usage
|
|
40
|
+
### Basic Usage (Automatic Theme)
|
|
41
|
+
|
|
42
|
+
By default, the header uses `preset="auto"`, which automatically detects the theme based on a `.dark` class on your root element (`html` or `body`).
|
|
38
43
|
|
|
39
44
|
```astro
|
|
40
45
|
---
|
|
41
|
-
// Option 1: Import from direct subpath (recommended)
|
|
42
46
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
43
47
|
|
|
44
|
-
// Option 2: Import from main entry point with types
|
|
45
|
-
import { HeaderProps, type MenuItemType } from '@sofidevo/astro-dynamic-header';
|
|
46
48
|
|
|
47
|
-
const menuItems
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
const = menuItems: [
|
|
50
|
+
{ link: '/about', text: 'About' },
|
|
51
|
+
]
|
|
52
|
+
|
|
51
53
|
---
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
logoSrc="/logo.png"
|
|
56
|
-
menuItems={menuItems}
|
|
57
|
-
/>
|
|
55
|
+
<!-- Detects .dark class on root automatically -->
|
|
56
|
+
<Header navigation={{ menuItems }} />
|
|
58
57
|
```
|
|
59
58
|
|
|
60
|
-
###
|
|
61
|
-
|
|
62
|
-
To ensure imports work correctly in your Astro project, make sure your `tsconfig.json` has the appropriate configuration:
|
|
59
|
+
### Advanced Usage (Dual-Theme Customization)
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
{
|
|
66
|
-
"compilerOptions": {
|
|
67
|
-
"moduleResolution": "bundler",
|
|
68
|
-
"allowImportingTsExtensions": true,
|
|
69
|
-
"strict": true,
|
|
70
|
-
"noEmit": true,
|
|
71
|
-
"jsx": "preserve"
|
|
72
|
-
},
|
|
73
|
-
"extends": "astro/tsconfigs/strict"
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Advanced Usage
|
|
61
|
+
You can provide custom colors for both light and dark modes simultaneously.
|
|
78
62
|
|
|
79
63
|
```astro
|
|
64
|
+
|
|
80
65
|
---
|
|
81
66
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
text: 'Web Development',
|
|
92
|
-
submenu: [
|
|
93
|
-
{ link: '/web/frontend', text: 'Frontend' },
|
|
94
|
-
{ link: '/web/backend', text: 'Backend' },
|
|
95
|
-
{ link: '/web/fullstack', text: 'Full Stack' },
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
{ link: '/design', text: 'Design' },
|
|
99
|
-
{ link: '/consulting', text: 'Consulting' },
|
|
100
|
-
],
|
|
67
|
+
const navigation = {
|
|
68
|
+
menuItems: [
|
|
69
|
+
{ link: '/about', text: 'About' },
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
const theme = {
|
|
73
|
+
light: {
|
|
74
|
+
accentColor: "#3e1c71",
|
|
75
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
101
76
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
77
|
+
dark: {
|
|
78
|
+
accentColor: "#00ffff",
|
|
79
|
+
backgroundColor: "rgba(20, 20, 20, 0.9)"
|
|
80
|
+
}
|
|
81
|
+
};
|
|
105
82
|
---
|
|
106
83
|
|
|
107
84
|
<Header
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
logoWidth="150px"
|
|
112
|
-
homeUrl="/"
|
|
113
|
-
menuItems={menuItems}
|
|
114
|
-
backgroundColor="#000000dd"
|
|
115
|
-
backdropBlur="blur(15px)"
|
|
116
|
-
zIndex={100}
|
|
85
|
+
preset="auto"
|
|
86
|
+
theme={theme}
|
|
87
|
+
navigation={navigation}
|
|
117
88
|
/>
|
|
118
89
|
```
|
|
119
90
|
|
|
@@ -121,165 +92,189 @@ const menuItems = [
|
|
|
121
92
|
|
|
122
93
|
### Header Component
|
|
123
94
|
|
|
124
|
-
| Prop
|
|
125
|
-
|
|
126
|
-
| `headerType` | `"floating" \| "fullscreen"`
|
|
127
|
-
| `
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
95
|
+
| Prop | Type | Default | Description |
|
|
96
|
+
| ------------ | ----------------------------- | ------------ | ------------------------------------------ |
|
|
97
|
+
| `headerType` | `"floating" \| "fullscreen"` | `"floating"` | Header layout style |
|
|
98
|
+
| `preset` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme behavior. `auto` follows root class. |
|
|
99
|
+
| `logo` | `LogoConfig` | `{}` | Logo configuration object |
|
|
100
|
+
| `navigation` | `NavConfig` | `{}` | Navigation configuration object |
|
|
101
|
+
| `theme` | `DualThemeConfig` | `{}` | Custom theme overrides for light/dark |
|
|
102
|
+
| `classNames` | `CustomClassNames` | `{}` | Custom class names for CSS Modules |
|
|
103
|
+
|
|
104
|
+
### Config Objects
|
|
105
|
+
|
|
106
|
+
#### DualThemeConfig
|
|
107
|
+
|
|
108
|
+
| Propery | Type | Description |
|
|
109
|
+
| ------- | ------------- | ---------------------------- |
|
|
110
|
+
| `light` | `ThemeConfig` | Styles applied in light mode |
|
|
111
|
+
| `dark` | `ThemeConfig` | Styles applied in dark mode |
|
|
112
|
+
|
|
113
|
+
#### ThemeConfig
|
|
114
|
+
|
|
115
|
+
| Propery | Type | Default |
|
|
116
|
+
| ----------------------- | -------- | ---------------- |
|
|
117
|
+
| `backgroundColor` | `string` | _Preset default_ |
|
|
118
|
+
| `backgroundColorOpaque` | `string` | _Preset default_ |
|
|
119
|
+
| `backdropBlur` | `string` | `"blur(20px)"` |
|
|
120
|
+
| `zIndex` | `number` | `10` |
|
|
121
|
+
| `textColor` | `string` | _Preset default_ |
|
|
122
|
+
| `accentColor` | `string` | _Preset default_ |
|
|
123
|
+
|
|
124
|
+
> [!IMPORTANT]
|
|
125
|
+
> **Transparency vs Solid Submenus**: To ensure the best UI and avoid rendering bugs with `backdrop-filter` on nested elements, submenus and the mobile navigation panel are **solid/opaque**.
|
|
126
|
+
>
|
|
127
|
+
> If you use a transparent `backgroundColor` (e.g., `rgba`), remember to also provide its solid counterpart in `backgroundColorOpaque`.
|
|
128
|
+
|
|
129
|
+
```astro
|
|
130
|
+
<Header theme={{
|
|
131
|
+
light: {
|
|
132
|
+
backgroundColor: "rgba(255, 255, 255, 0.7)", // Transparent header body
|
|
133
|
+
backgroundColorOpaque: "#ffffff", // Solid submenus
|
|
134
|
+
}
|
|
135
|
+
}} />
|
|
147
136
|
```
|
|
148
137
|
|
|
149
|
-
|
|
138
|
+
#### CustomClassNames
|
|
150
139
|
|
|
151
|
-
|
|
140
|
+
| Propery | Type | Description |
|
|
141
|
+
| ----------- | -------- | -------------------------------- |
|
|
142
|
+
| `container` | `string` | Main container class |
|
|
143
|
+
| `header` | `string` | Header element class |
|
|
144
|
+
| `logo` | `string` | Logo link container class |
|
|
145
|
+
| `logoText` | `string` | Logo text class |
|
|
146
|
+
| `nav` | `string` | Desktop navigation wrapper class |
|
|
152
147
|
|
|
153
|
-
|
|
148
|
+
#### LogoConfig
|
|
149
|
+
|
|
150
|
+
| Property | Type | Description |
|
|
151
|
+
| ----------- | -------- | ------------------------------------------------ |
|
|
152
|
+
| `src` | `string` | URL of the logo image |
|
|
153
|
+
| `alt` | `string` | Alternative text for the logo image |
|
|
154
|
+
| `width` | `string` | Width of the logo (e.g., "50px", "5rem") |
|
|
155
|
+
| `text` | `string` | Text to display next to or instead of logo image |
|
|
156
|
+
| `textSize` | `string` | Font size for the logo text |
|
|
157
|
+
| `textColor` | `string` | Color for the logo text |
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
|-----------|----------|------------|-----------|
|
|
157
|
-
| `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.) that appear in both desktop and mobile |
|
|
159
|
+
#### NavConfig
|
|
158
160
|
|
|
159
|
-
|
|
161
|
+
| Property | Type | Description |
|
|
162
|
+
| ----------- | ------------ | --------------------------------------- |
|
|
163
|
+
| `homeUrl` | `string` | URL for the home link (defaults to `/`) |
|
|
164
|
+
| `menuItems` | `MenuItem[]` | Array of navigation menu items |
|
|
160
165
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
#### MenuItem
|
|
167
|
+
|
|
168
|
+
| Property | Type | Description |
|
|
169
|
+
| --------- | --------------------- | ----------------------------------- |
|
|
170
|
+
| `link` | `string` | URL the menu item points to |
|
|
171
|
+
| `text` | `string` | Label text for the menu item |
|
|
172
|
+
| `submenu` | `SecondaryMenuItem[]` | Optional array of nested menu items |
|
|
173
|
+
|
|
174
|
+
## Slots Support
|
|
175
|
+
|
|
176
|
+
The Header component provides a flexible slot system that allows you to add additional content:
|
|
177
|
+
|
|
178
|
+
### Available Slots
|
|
165
179
|
|
|
166
|
-
|
|
180
|
+
| Slot Name | Location | Visibility | Description |
|
|
181
|
+
| --------- | --------------------- | --------------------- | ---------------------------------------- |
|
|
182
|
+
| `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.) |
|
|
167
183
|
|
|
168
|
-
|
|
184
|
+
### Example with Slots
|
|
169
185
|
|
|
170
186
|
```astro
|
|
171
187
|
---
|
|
172
188
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
173
189
|
|
|
174
|
-
const
|
|
175
|
-
{ link: '/about', text: 'About' }
|
|
176
|
-
|
|
177
|
-
];
|
|
190
|
+
const navigation = {
|
|
191
|
+
menuItems: [{ link: '/about', text: 'About' }]
|
|
192
|
+
};
|
|
178
193
|
---
|
|
179
194
|
|
|
180
|
-
<Header
|
|
181
|
-
headerType="floating"
|
|
182
|
-
logoSrc="/logo.png"
|
|
183
|
-
logoText="My Company"
|
|
184
|
-
menuItems={menuItems}
|
|
185
|
-
>
|
|
195
|
+
<Header navigation={navigation}>
|
|
186
196
|
<div slot="actions">
|
|
187
197
|
<button class="login-btn">Login</button>
|
|
188
|
-
<button class="signup-btn">Sign Up</button>
|
|
189
198
|
</div>
|
|
190
199
|
</Header>
|
|
191
200
|
```
|
|
192
201
|
|
|
193
|
-
|
|
202
|
+
## Comprehensive Example
|
|
203
|
+
|
|
204
|
+
Below is a complete implementation example showcasing custom logo configuration, navigation with a home URL, and theme overrides.
|
|
194
205
|
|
|
195
206
|
```astro
|
|
196
207
|
---
|
|
197
208
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
198
209
|
|
|
199
210
|
const menuItems = [
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
|
|
211
|
+
{
|
|
212
|
+
link: "#",
|
|
213
|
+
text: "Services",
|
|
214
|
+
submenu: [
|
|
215
|
+
{ link: "/design", text: "Design" },
|
|
216
|
+
{ link: "/consulting", text: "Consulting" },
|
|
217
|
+
{
|
|
218
|
+
link: "#",
|
|
219
|
+
text: "Web Development",
|
|
220
|
+
submenu: [
|
|
221
|
+
{ link: "/web/frontend", text: "Frontend" },
|
|
222
|
+
{ link: "/web/backend", text: "Backend" },
|
|
223
|
+
{ link: "/web/fullstack", text: "Full Stack" },
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
{ link: "/about", text: "About" },
|
|
229
|
+
{ link: "/contact", text: "Contact" },
|
|
203
230
|
];
|
|
231
|
+
|
|
232
|
+
const theme = {
|
|
233
|
+
light: {
|
|
234
|
+
accentColor: "#ff0000",
|
|
235
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
|
236
|
+
},
|
|
237
|
+
dark: {
|
|
238
|
+
accentColor: "#00ffff",
|
|
239
|
+
backgroundColor: "rgba(20, 20, 20, 0.9)",
|
|
240
|
+
},
|
|
241
|
+
};
|
|
204
242
|
---
|
|
205
243
|
|
|
206
244
|
<Header
|
|
207
|
-
headerType="
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
245
|
+
headerType="floating"
|
|
246
|
+
preset="dark"
|
|
247
|
+
logo={{
|
|
248
|
+
src: "https://itssofi.dev/img/icons/sofi-icon.webp",
|
|
249
|
+
alt: "My Site Logo",
|
|
250
|
+
width: "44px",
|
|
251
|
+
}}
|
|
252
|
+
navigation={{
|
|
253
|
+
homeUrl: "/",
|
|
254
|
+
menuItems: menuItems,
|
|
255
|
+
}}
|
|
256
|
+
theme={theme}
|
|
219
257
|
>
|
|
220
|
-
<
|
|
221
|
-
<button class="btn btn-outline">Login</button>
|
|
222
|
-
<button class="btn btn-primary">Get Started</button>
|
|
223
|
-
</div>
|
|
258
|
+
<button slot="actions">Login</button>
|
|
224
259
|
</Header>
|
|
225
260
|
```
|
|
226
261
|
|
|
227
|
-
#### Styling Action Buttons
|
|
228
|
-
|
|
229
|
-
```css
|
|
230
|
-
.header-actions {
|
|
231
|
-
display: flex;
|
|
232
|
-
gap: 0.5em;
|
|
233
|
-
align-items: center;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.btn {
|
|
237
|
-
padding: 0.5em 1em;
|
|
238
|
-
border: none;
|
|
239
|
-
border-radius: 6px;
|
|
240
|
-
cursor: pointer;
|
|
241
|
-
font-weight: 500;
|
|
242
|
-
text-decoration: none;
|
|
243
|
-
display: inline-flex;
|
|
244
|
-
align-items: center;
|
|
245
|
-
transition: all 0.2s ease;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.btn-outline {
|
|
249
|
-
background: transparent;
|
|
250
|
-
color: #ffffff;
|
|
251
|
-
border: 1px solid #ffffff;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
.btn-outline:hover {
|
|
255
|
-
background: #ffffff;
|
|
256
|
-
color: #000000;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.btn-primary {
|
|
260
|
-
background: #00ffff;
|
|
261
|
-
color: #000000;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
.btn-primary:hover {
|
|
265
|
-
background: #00cccc;
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
262
|
## Header Types
|
|
269
263
|
|
|
270
264
|
### Floating Header
|
|
265
|
+
|
|
271
266
|
- Centered with max-width constraint
|
|
272
267
|
- Rounded corners
|
|
273
268
|
- Padding around container
|
|
274
269
|
- Perfect for modern, card-like designs
|
|
275
270
|
|
|
276
271
|
### Fullscreen Header
|
|
272
|
+
|
|
277
273
|
- Full viewport width
|
|
278
274
|
- No border radius
|
|
279
275
|
- Edge-to-edge design
|
|
280
276
|
- Ideal for traditional website layouts
|
|
281
277
|
|
|
282
|
-
|
|
283
278
|
## Styling and Customization
|
|
284
279
|
|
|
285
280
|
The component uses CSS custom properties that you can override:
|
|
@@ -294,24 +289,71 @@ The component uses CSS custom properties that you can override:
|
|
|
294
289
|
|
|
295
290
|
## TypeScript Support
|
|
296
291
|
|
|
297
|
-
|
|
292
|
+
The package provides full TypeScript support. You can import types to ensure your configuration is correct:
|
|
298
293
|
|
|
299
|
-
```
|
|
294
|
+
```astro
|
|
295
|
+
---
|
|
296
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
300
297
|
import type {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
HamburgerButtonProps
|
|
298
|
+
NavConfig,
|
|
299
|
+
DualThemeConfig,
|
|
300
|
+
MenuItem,
|
|
301
|
+
SecondaryMenuItem
|
|
306
302
|
} from '@sofidevo/astro-dynamic-header';
|
|
303
|
+
|
|
304
|
+
const navigation: NavConfig = {
|
|
305
|
+
menuItems: [
|
|
306
|
+
{
|
|
307
|
+
link: '/products',
|
|
308
|
+
text: 'Products',
|
|
309
|
+
submenu: [
|
|
310
|
+
{ link: '/software', text: 'Software' },
|
|
311
|
+
{ link: '/hardware', text: 'Hardware' }
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
]
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const theme: DualThemeConfig = {
|
|
318
|
+
light: {
|
|
319
|
+
accentColor: "#3e1c71",
|
|
320
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
|
321
|
+
backgroundColorOpaque: "#ffffff"
|
|
322
|
+
},
|
|
323
|
+
dark: {
|
|
324
|
+
accentColor: "#00ffff",
|
|
325
|
+
backgroundColor: "rgba(10, 10, 10, 0.9)",
|
|
326
|
+
backgroundColorOpaque: "#0a0a0a"
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
<Header
|
|
332
|
+
navigation={navigation}
|
|
333
|
+
theme={theme}
|
|
334
|
+
preset="auto"
|
|
335
|
+
/>
|
|
307
336
|
```
|
|
308
337
|
|
|
338
|
+
### Available Types
|
|
339
|
+
|
|
340
|
+
| Type | Description |
|
|
341
|
+
| ------------------- | ---------------------------------------------- |
|
|
342
|
+
| `MenuItem` | Top-level menu item with optional properties |
|
|
343
|
+
| `SecondaryMenuItem` | Second-level menu item |
|
|
344
|
+
| `TertiaryMenuItem` | Third-level menu item |
|
|
345
|
+
| `NavConfig` | Main navigation configuration object |
|
|
346
|
+
| `ThemeConfig` | Individual theme settings (colors, blur, etc.) |
|
|
347
|
+
| `DualThemeConfig` | Combined settings for light and dark modes |
|
|
348
|
+
| `LogoConfig` | Logo image and text configuration |
|
|
349
|
+
| `HeaderProps` | Main props for the Header component |
|
|
350
|
+
|
|
309
351
|
## Browser Support
|
|
310
352
|
|
|
311
|
-
- All modern browsers
|
|
312
|
-
- Mobile responsive design
|
|
313
|
-
- Supports CSS `backdrop-filter`
|
|
314
|
-
-
|
|
353
|
+
- All modern browsers (Chrome, Firefox, Safari, Edge)
|
|
354
|
+
- Mobile responsive design with optimized touch targets
|
|
355
|
+
- Supports CSS `backdrop-filter` for glassmorphism
|
|
356
|
+
- Automatic theme switching based on OS or site preference via `.dark` class
|
|
315
357
|
|
|
316
358
|
## Troubleshooting
|
|
317
359
|
|
|
@@ -320,29 +362,26 @@ import type {
|
|
|
320
362
|
If you encounter import errors, try these solutions:
|
|
321
363
|
|
|
322
364
|
1. **Use direct subpath import:**
|
|
365
|
+
|
|
323
366
|
```astro
|
|
324
367
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
325
368
|
```
|
|
326
369
|
|
|
327
|
-
2. **
|
|
370
|
+
2. **Check relative imports in TS:**
|
|
371
|
+
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.
|
|
372
|
+
|
|
373
|
+
3. **Verify TypeScript configuration:**
|
|
374
|
+
|
|
328
375
|
```json
|
|
329
376
|
// tsconfig.json
|
|
330
377
|
{
|
|
331
378
|
"compilerOptions": {
|
|
332
|
-
"moduleResolution": "bundler",
|
|
379
|
+
"moduleResolution": "bundler",
|
|
333
380
|
"allowImportingTsExtensions": true
|
|
334
381
|
}
|
|
335
382
|
}
|
|
336
383
|
```
|
|
337
384
|
|
|
338
|
-
3. **Import types separately:**
|
|
339
|
-
```astro
|
|
340
|
-
---
|
|
341
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
342
|
-
import type { MenuItemType } from '@sofidevo/astro-dynamic-header';
|
|
343
|
-
---
|
|
344
|
-
```
|
|
345
|
-
|
|
346
385
|
### Compatibility
|
|
347
386
|
|
|
348
387
|
- Astro 4.x and 5.x
|
|
@@ -354,44 +393,6 @@ If you encounter import errors, try these solutions:
|
|
|
354
393
|
|
|
355
394
|
Visit our demo website to see the component in action with interactive examples and complete documentation.
|
|
356
395
|
|
|
357
|
-
## Testing
|
|
358
|
-
|
|
359
|
-
This project includes a comprehensive test suite with 34 tests covering all critical functionality.
|
|
360
|
-
|
|
361
|
-
### Running Tests
|
|
362
|
-
|
|
363
|
-
```bash
|
|
364
|
-
# Run all tests
|
|
365
|
-
npm test
|
|
366
|
-
|
|
367
|
-
# Run tests in watch mode
|
|
368
|
-
npm run test:watch
|
|
369
|
-
|
|
370
|
-
# Run tests with coverage report
|
|
371
|
-
npm run test:coverage
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Test Coverage
|
|
375
|
-
|
|
376
|
-
The test suite covers:
|
|
377
|
-
|
|
378
|
-
#### Component Logic Tests
|
|
379
|
-
- **Header Component** (4 tests): Hamburger controller functionality, menu toggle behavior
|
|
380
|
-
- **HamburgerButton Component** (10 tests): Button states, responsive behavior, accessibility
|
|
381
|
-
- **MobileNav Component** (7 tests): Dropdown structure, nested submenus, conditional rendering
|
|
382
|
-
- **NavMenu Component** (6 tests): Dynamic positioning, submenu interactions, viewport adjustments
|
|
383
|
-
|
|
384
|
-
#### Integration Tests (7 tests)
|
|
385
|
-
- Component interaction flows
|
|
386
|
-
- Responsive behavior between mobile/desktop
|
|
387
|
-
- Keyboard navigation and accessibility
|
|
388
|
-
- Menu state management during navigation
|
|
389
|
-
|
|
390
|
-
### Test Technologies
|
|
391
|
-
- **Vitest**: Fast testing framework
|
|
392
|
-
- **jsdom**: DOM simulation for component testing
|
|
393
|
-
- **TypeScript**: Type-safe test writing
|
|
394
|
-
|
|
395
396
|
## License
|
|
396
397
|
|
|
397
398
|
MIT License - see the [LICENSE](./LICENSE) file for details.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofidevo/astro-dynamic-header",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
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",
|
|
@@ -50,20 +50,20 @@
|
|
|
50
50
|
"author": "SofiDev contact@sofidev.top",
|
|
51
51
|
"license": "MIT",
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"astro": "^6.0.
|
|
53
|
+
"astro": "^6.0.6"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@astrojs/check": "^0.9.8",
|
|
57
57
|
"@types/jsdom": "^21.1.7",
|
|
58
58
|
"@vitest/coverage-v8": "^3.2.4",
|
|
59
|
-
"astro": "^6.0.
|
|
59
|
+
"astro": "^6.0.6",
|
|
60
60
|
"jsdom": "^26.1.0",
|
|
61
61
|
"typescript": "^5.0.0",
|
|
62
62
|
"vitest": "^3.2.4"
|
|
63
63
|
},
|
|
64
64
|
"repository": {
|
|
65
65
|
"type": "git",
|
|
66
|
-
"url": "https://github.com/SofiDevO/astro-dynamic-header"
|
|
66
|
+
"url": "git+https://github.com/SofiDevO/astro-dynamic-header.git"
|
|
67
67
|
},
|
|
68
68
|
"bugs": {
|
|
69
69
|
"url": "https://github.com/SofiDevO/astro-dynamic-header/issues"
|
package/src/Header.astro
CHANGED
|
@@ -3,49 +3,83 @@ import HamburgerButton from "./HamburgerButton.astro";
|
|
|
3
3
|
import NavMenu from "./NavMenu.astro";
|
|
4
4
|
import MobileNav from "./MobileNav.astro";
|
|
5
5
|
|
|
6
|
+
import type {
|
|
7
|
+
LogoConfig,
|
|
8
|
+
NavConfig,
|
|
9
|
+
DualThemeConfig,
|
|
10
|
+
CustomClassNames,
|
|
11
|
+
} from "./index.js";
|
|
12
|
+
|
|
6
13
|
export interface Props {
|
|
7
14
|
headerType?: "floating" | "fullscreen";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
logoTextSize?: string;
|
|
14
|
-
logoTextColor?: string;
|
|
15
|
-
// properties for navigation
|
|
16
|
-
homeUrl?: string;
|
|
17
|
-
menuItems?: Array<{
|
|
18
|
-
link: string;
|
|
19
|
-
text: string;
|
|
20
|
-
submenu?: Array<{
|
|
21
|
-
link: string;
|
|
22
|
-
text: string;
|
|
23
|
-
submenu?: Array<{
|
|
24
|
-
link: string;
|
|
25
|
-
text: string;
|
|
26
|
-
}>;
|
|
27
|
-
}>;
|
|
28
|
-
}>;
|
|
29
|
-
// styles
|
|
30
|
-
backgroundColor?: string;
|
|
31
|
-
backdropBlur?: string;
|
|
32
|
-
zIndex?: number;
|
|
15
|
+
preset?: "light" | "dark" | "auto";
|
|
16
|
+
logo?: LogoConfig;
|
|
17
|
+
navigation?: NavConfig;
|
|
18
|
+
theme?: DualThemeConfig;
|
|
19
|
+
classNames?: CustomClassNames;
|
|
33
20
|
}
|
|
34
21
|
|
|
35
22
|
const {
|
|
36
23
|
headerType = "floating",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
logoTextColor = "#ffffff",
|
|
43
|
-
homeUrl = "/",
|
|
44
|
-
menuItems = [],
|
|
45
|
-
backgroundColor = "#0d0d0dcc",
|
|
46
|
-
backdropBlur = "blur(20px)",
|
|
47
|
-
zIndex = 10
|
|
24
|
+
preset = "auto",
|
|
25
|
+
logo = {},
|
|
26
|
+
navigation = {},
|
|
27
|
+
theme = {},
|
|
28
|
+
classNames = {},
|
|
48
29
|
} = Astro.props;
|
|
30
|
+
|
|
31
|
+
if (import.meta.env.DEV) {
|
|
32
|
+
// Check if logo is being passed as a string (legacy)
|
|
33
|
+
if (typeof logo === "string") {
|
|
34
|
+
console.warn(
|
|
35
|
+
"[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'logo' prop now expects an object. Please use logo={{ src: '...' }} instead.",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(navigation)) {
|
|
39
|
+
console.warn(
|
|
40
|
+
"[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'navigation' prop now expects an object with 'menuItems'. Please use navigation={{ menuItems: [...] }} instead.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Default theme configuration
|
|
46
|
+
const defaultThemes = {
|
|
47
|
+
light: {
|
|
48
|
+
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
49
|
+
backgroundColorOpaque: "rgb(255, 255, 255)",
|
|
50
|
+
backdropBlur: "blur(20px)",
|
|
51
|
+
zIndex: 10,
|
|
52
|
+
textColor: "#1a1a1a",
|
|
53
|
+
accentColor: "#3e1c71",
|
|
54
|
+
},
|
|
55
|
+
dark: {
|
|
56
|
+
backgroundColor: "#0d0d0dcc",
|
|
57
|
+
backgroundColorOpaque: "#0d0d0d",
|
|
58
|
+
backdropBlur: "blur(20px)",
|
|
59
|
+
zIndex: 10,
|
|
60
|
+
textColor: "#ffffff",
|
|
61
|
+
accentColor: "#00ffff",
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Merge with user overrides
|
|
66
|
+
const lightTheme = { ...defaultThemes.light, ...theme.light };
|
|
67
|
+
const darkTheme = { ...defaultThemes.dark, ...theme.dark };
|
|
68
|
+
|
|
69
|
+
// Navigation configuration
|
|
70
|
+
const { homeUrl = "/", menuItems = [] } = navigation;
|
|
71
|
+
|
|
72
|
+
// Logo configuration
|
|
73
|
+
const {
|
|
74
|
+
src: logoSrc = "/logo.png",
|
|
75
|
+
alt: logoAlt = "Logo",
|
|
76
|
+
width: logoWidth = "55px",
|
|
77
|
+
text: logoText = "",
|
|
78
|
+
textSize: logoTextSize = "1em",
|
|
79
|
+
textColor: logoTextColor = "inherit", // Will inherit from theme text-color
|
|
80
|
+
} = logo;
|
|
81
|
+
|
|
82
|
+
const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
49
83
|
---
|
|
50
84
|
|
|
51
85
|
<script>
|
|
@@ -64,7 +98,7 @@ const {
|
|
|
64
98
|
private init(): void {
|
|
65
99
|
if (this.hamburgerBtn && this.mobileMenu) {
|
|
66
100
|
this.hamburgerBtn.addEventListener("click", () => this.toggleMenu());
|
|
67
|
-
this.links.forEach(link => {
|
|
101
|
+
this.links.forEach((link) => {
|
|
68
102
|
link.addEventListener("click", () => this.closeMenu());
|
|
69
103
|
});
|
|
70
104
|
}
|
|
@@ -85,38 +119,138 @@ const {
|
|
|
85
119
|
}
|
|
86
120
|
}
|
|
87
121
|
|
|
88
|
-
|
|
89
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
122
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
90
123
|
new HamburgerController();
|
|
91
124
|
});
|
|
92
125
|
</script>
|
|
93
126
|
|
|
94
|
-
<div
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
127
|
+
<div
|
|
128
|
+
class:list={[
|
|
129
|
+
"header__container",
|
|
130
|
+
`header__container--${headerType}`,
|
|
131
|
+
classNames.container,
|
|
132
|
+
]}
|
|
133
|
+
style={{ zIndex: lightTheme.zIndex }}
|
|
134
|
+
>
|
|
135
|
+
<header
|
|
136
|
+
class:list={[
|
|
137
|
+
"header",
|
|
138
|
+
`header--${headerType}`,
|
|
139
|
+
forcedClass,
|
|
140
|
+
classNames.header,
|
|
141
|
+
]}
|
|
142
|
+
style={{
|
|
143
|
+
"--l-bg": lightTheme.backgroundColor,
|
|
144
|
+
"--l-bg-opaque": lightTheme.backgroundColorOpaque,
|
|
145
|
+
"--l-text": lightTheme.textColor,
|
|
146
|
+
"--l-accent": lightTheme.accentColor,
|
|
147
|
+
"--l-blur": lightTheme.backdropBlur,
|
|
148
|
+
"--d-bg": darkTheme.backgroundColor,
|
|
149
|
+
"--d-bg-opaque": darkTheme.backgroundColorOpaque,
|
|
150
|
+
"--d-text": darkTheme.textColor,
|
|
151
|
+
"--d-accent": darkTheme.accentColor,
|
|
152
|
+
"--d-blur": darkTheme.backdropBlur,
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<a class:list={["logo__container", classNames.logo]} href={homeUrl}>
|
|
156
|
+
<img
|
|
157
|
+
class="header__logo"
|
|
158
|
+
src={logoSrc}
|
|
159
|
+
alt={logoAlt}
|
|
160
|
+
style={{ width: logoWidth }}
|
|
161
|
+
/>
|
|
162
|
+
{
|
|
163
|
+
logoText && (
|
|
164
|
+
<span
|
|
165
|
+
class:list={["header__logo-text", classNames.logoText]}
|
|
166
|
+
style={{ color: logoTextColor, fontSize: logoTextSize }}
|
|
167
|
+
>
|
|
168
|
+
{logoText}
|
|
169
|
+
</span>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
99
172
|
</a>
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{Astro.slots.has('actions') && (
|
|
109
|
-
<div class="actions-mobile" slot="slot-panel">
|
|
173
|
+
|
|
174
|
+
<div class:list={["nav-menu-wrapper", classNames.nav]}>
|
|
175
|
+
<NavMenu menuItems={menuItems} />
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{
|
|
179
|
+
Astro.slots.has("actions") && (
|
|
180
|
+
<div class="actions-desktop">
|
|
110
181
|
<slot name="actions" />
|
|
111
182
|
</div>
|
|
112
|
-
)
|
|
113
|
-
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
<HamburgerButton />
|
|
114
187
|
|
|
188
|
+
<MobileNav menuItems={menuItems} type={headerType}>
|
|
189
|
+
{
|
|
190
|
+
Astro.slots.has("actions") && (
|
|
191
|
+
<div class="actions-mobile" slot="slot-panel">
|
|
192
|
+
<slot name="actions" />
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
</MobileNav>
|
|
115
197
|
</header>
|
|
116
198
|
</div>
|
|
117
199
|
|
|
118
|
-
<style is:inline
|
|
119
|
-
|
|
200
|
+
<style is:inline>
|
|
201
|
+
.header {
|
|
202
|
+
--bg-color: var(--l-bg);
|
|
203
|
+
--bg-color-opaque: var(--l-bg-opaque);
|
|
204
|
+
--text-color: var(--l-text);
|
|
205
|
+
--accent-color: var(--l-accent);
|
|
206
|
+
--backdrop-blur: var(--l-blur);
|
|
207
|
+
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
width: 100%;
|
|
211
|
+
justify-content: space-between;
|
|
212
|
+
padding: 1em 2em;
|
|
213
|
+
background-color: var(--bg-color);
|
|
214
|
+
backdrop-filter: var(--backdrop-blur);
|
|
215
|
+
-webkit-backdrop-filter: var(--backdrop-blur);
|
|
216
|
+
color: var(--text-color);
|
|
217
|
+
transition:
|
|
218
|
+
background-color 0.3s ease,
|
|
219
|
+
color 0.3s ease;
|
|
220
|
+
|
|
221
|
+
@media (width < 768px) {
|
|
222
|
+
align-self: flex-end;
|
|
223
|
+
padding: 0.4em;
|
|
224
|
+
margin: 0 auto;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Automatic dark mode detection via root class */
|
|
229
|
+
:root.dark .header:not(.header--force-light) {
|
|
230
|
+
--bg-color: var(--d-bg);
|
|
231
|
+
--bg-color-opaque: var(--d-bg-opaque);
|
|
232
|
+
--text-color: var(--d-text);
|
|
233
|
+
--accent-color: var(--d-accent);
|
|
234
|
+
--backdrop-blur: var(--d-blur);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* Forced themes */
|
|
238
|
+
.header--force-dark {
|
|
239
|
+
--bg-color: var(--d-bg) !important;
|
|
240
|
+
--bg-color-opaque: var(--d-bg-opaque) !important;
|
|
241
|
+
--text-color: var(--d-text) !important;
|
|
242
|
+
--accent-color: var(--d-accent) !important;
|
|
243
|
+
--backdrop-blur: var(--d-blur) !important;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.header--force-light {
|
|
247
|
+
--bg-color: var(--l-bg) !important;
|
|
248
|
+
--bg-color-opaque: var(--l-bg-opaque) !important;
|
|
249
|
+
--text-color: var(--l-text) !important;
|
|
250
|
+
--accent-color: var(--l-accent) !important;
|
|
251
|
+
--backdrop-blur: var(--l-blur) !important;
|
|
252
|
+
}
|
|
253
|
+
|
|
120
254
|
.header__container {
|
|
121
255
|
width: 100%;
|
|
122
256
|
display: flex;
|
|
@@ -131,43 +265,24 @@ const {
|
|
|
131
265
|
}
|
|
132
266
|
}
|
|
133
267
|
|
|
134
|
-
/* Floating header container styles */
|
|
135
268
|
.header__container--floating {
|
|
136
|
-
padding: .5em;
|
|
269
|
+
padding: 0.5em;
|
|
137
270
|
}
|
|
138
271
|
|
|
139
|
-
/* Fullscreen header container styles */
|
|
140
272
|
.header__container--fullscreen {
|
|
141
273
|
padding: 0;
|
|
142
274
|
}
|
|
143
275
|
|
|
144
|
-
/* Base styles for header */
|
|
145
|
-
.header {
|
|
146
|
-
display: flex;
|
|
147
|
-
align-items: center;
|
|
148
|
-
width: 100%;
|
|
149
|
-
justify-content: space-between;
|
|
150
|
-
padding: 1em 2em;
|
|
151
|
-
|
|
152
|
-
@media (width < 768px) {
|
|
153
|
-
align-self: flex-end;
|
|
154
|
-
padding: 0.4em;
|
|
155
|
-
margin: 0 auto;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/* Floating header styles */
|
|
160
276
|
.header--floating {
|
|
161
277
|
max-width: 1100px;
|
|
162
278
|
border-radius: 20px;
|
|
163
|
-
padding: .7em 1em;
|
|
279
|
+
padding: 0.7em 1em;
|
|
164
280
|
|
|
165
281
|
@media (width < 768px) {
|
|
166
282
|
max-width: 95%;
|
|
167
283
|
}
|
|
168
284
|
}
|
|
169
285
|
|
|
170
|
-
/* Fullscreen header styles */
|
|
171
286
|
.header--fullscreen {
|
|
172
287
|
border-radius: 0;
|
|
173
288
|
padding: 1em 2em;
|
|
@@ -182,16 +297,17 @@ const {
|
|
|
182
297
|
display: flex;
|
|
183
298
|
align-items: center;
|
|
184
299
|
text-decoration: none;
|
|
185
|
-
|
|
300
|
+
color: inherit;
|
|
301
|
+
& span {
|
|
186
302
|
font-weight: 600;
|
|
187
303
|
}
|
|
188
304
|
}
|
|
305
|
+
|
|
189
306
|
.header__logo {
|
|
190
307
|
margin-right: 1em;
|
|
191
308
|
object-fit: contain;
|
|
192
309
|
}
|
|
193
310
|
|
|
194
|
-
/* Actions visibility control */
|
|
195
311
|
.actions-desktop {
|
|
196
312
|
@media (width < 768px) {
|
|
197
313
|
display: none;
|
|
@@ -205,7 +321,6 @@ const {
|
|
|
205
321
|
}
|
|
206
322
|
}
|
|
207
323
|
|
|
208
|
-
/* Adjust transform for fullscreen header */
|
|
209
324
|
.header--fullscreen .mobile-header__menu.is-active {
|
|
210
325
|
transform: translate(0%);
|
|
211
326
|
}
|
package/src/MobileNav.astro
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
+
import type { MenuItem, SecondaryMenuItem, TertiaryMenuItem } from "./index.js";
|
|
3
|
+
|
|
2
4
|
export interface Props {
|
|
3
5
|
type?: "floating" | "fullscreen";
|
|
4
|
-
menuItems?:
|
|
5
|
-
link: string;
|
|
6
|
-
text: string;
|
|
7
|
-
submenu?: Array<{
|
|
8
|
-
link: string;
|
|
9
|
-
text: string;
|
|
10
|
-
submenu?: Array<{
|
|
11
|
-
link: string;
|
|
12
|
-
text: string;
|
|
13
|
-
}>;
|
|
14
|
-
}>;
|
|
15
|
-
}>;
|
|
6
|
+
menuItems?: MenuItem[];
|
|
16
7
|
showHomeLink?: boolean;
|
|
17
8
|
homeText?: string;
|
|
18
|
-
accentColor?: string;
|
|
19
9
|
}
|
|
20
10
|
|
|
21
11
|
const { type } = Astro.props;
|
|
@@ -24,7 +14,6 @@ const {
|
|
|
24
14
|
menuItems = [],
|
|
25
15
|
showHomeLink = true,
|
|
26
16
|
homeText = "Home",
|
|
27
|
-
accentColor = "var(--light-spot-color, #00ffff)",
|
|
28
17
|
} = Astro.props;
|
|
29
18
|
|
|
30
19
|
const pagePathname = Astro.url.pathname;
|
|
@@ -33,7 +22,6 @@ const pagePathname = Astro.url.pathname;
|
|
|
33
22
|
<nav
|
|
34
23
|
class={`mobile-header__menu mobile-header__menu--${type}`}
|
|
35
24
|
id="mobile-header-menu"
|
|
36
|
-
style={`--mobile-accent-color: ${accentColor};`}
|
|
37
25
|
>
|
|
38
26
|
<ol class="mobile-menu">
|
|
39
27
|
{
|
|
@@ -50,7 +38,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
50
38
|
)
|
|
51
39
|
}
|
|
52
40
|
{
|
|
53
|
-
menuItems?.map((item:
|
|
41
|
+
menuItems?.map((item: MenuItem) => (
|
|
54
42
|
<li>
|
|
55
43
|
{item.submenu ? (
|
|
56
44
|
<details class="mobile-details">
|
|
@@ -61,11 +49,11 @@ const pagePathname = Astro.url.pathname;
|
|
|
61
49
|
icon="codicon:triangle-down"
|
|
62
50
|
width="15"
|
|
63
51
|
height="15"
|
|
64
|
-
style=
|
|
52
|
+
style="color: var(--accent-color);"
|
|
65
53
|
/>
|
|
66
54
|
</summary>
|
|
67
55
|
<ol class="mobile-submenu">
|
|
68
|
-
{item?.submenu?.map((sub:
|
|
56
|
+
{item?.submenu?.map((sub: SecondaryMenuItem) => (
|
|
69
57
|
<li>
|
|
70
58
|
{sub.submenu && sub.submenu.length > 0 ? (
|
|
71
59
|
<details class="mobile-details">
|
|
@@ -76,11 +64,11 @@ const pagePathname = Astro.url.pathname;
|
|
|
76
64
|
icon="codicon:triangle-down"
|
|
77
65
|
width="15"
|
|
78
66
|
height="15"
|
|
79
|
-
style=
|
|
67
|
+
style="color: var(--accent-color);"
|
|
80
68
|
/>
|
|
81
69
|
</summary>
|
|
82
70
|
<ol class="mobile-subsubmenu">
|
|
83
|
-
{sub.submenu.map((subsub:
|
|
71
|
+
{sub.submenu.map((subsub: TertiaryMenuItem) => (
|
|
84
72
|
<li class="mobile-submenu__item secondary">
|
|
85
73
|
<a
|
|
86
74
|
class="menu__link mobile-menu__link"
|
|
@@ -125,16 +113,16 @@ const pagePathname = Astro.url.pathname;
|
|
|
125
113
|
position: fixed;
|
|
126
114
|
top: -11px;
|
|
127
115
|
left: 0;
|
|
128
|
-
background: #151515;
|
|
116
|
+
background: var(--bg-color-opaque, #151515);
|
|
129
117
|
z-index: 20;
|
|
130
|
-
transition: transform 0.3s ease-in-out;
|
|
118
|
+
transition: transform 0.3s ease-in-out, background-color 0.3s ease;
|
|
131
119
|
transform: translateX(100%);
|
|
132
120
|
padding: 10rem 2rem 2rem;
|
|
133
|
-
color: var(--
|
|
121
|
+
color: var(--text-color, #fff);
|
|
134
122
|
}
|
|
135
123
|
|
|
136
124
|
a {
|
|
137
|
-
color: var(--
|
|
125
|
+
color: var(--text-color, #fff);
|
|
138
126
|
}
|
|
139
127
|
}
|
|
140
128
|
|
|
@@ -151,7 +139,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
151
139
|
|
|
152
140
|
.mobile-menu li a {
|
|
153
141
|
border: solid 1px
|
|
154
|
-
color-mix(in srgb, var(--
|
|
142
|
+
color-mix(in srgb, var(--accent-color, #00ffff) 20%, transparent);
|
|
155
143
|
border-radius: 4px;
|
|
156
144
|
width: 100%;
|
|
157
145
|
height: 4rem;
|
|
@@ -171,7 +159,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
171
159
|
.mobile-details summary {
|
|
172
160
|
background: linear-gradient(
|
|
173
161
|
to right,
|
|
174
|
-
color-mix(in srgb, var(--
|
|
162
|
+
color-mix(in srgb, var(--accent-color, #00ffff) 20%, transparent),
|
|
175
163
|
transparent
|
|
176
164
|
);
|
|
177
165
|
border-radius: 4px;
|
|
@@ -207,7 +195,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
207
195
|
|
|
208
196
|
.mobile-submenu,
|
|
209
197
|
.mobile-subsubmenu {
|
|
210
|
-
border-left: dashed 1px var(--
|
|
198
|
+
border-left: dashed 1px var(--accent-color, #00ffff);
|
|
211
199
|
padding-left: 2rem;
|
|
212
200
|
margin-left: 1rem;
|
|
213
201
|
margin-top: 1rem;
|
|
@@ -230,7 +218,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
230
218
|
width: 100%;
|
|
231
219
|
font-size: 1.7rem;
|
|
232
220
|
height: 5rem;
|
|
233
|
-
color:
|
|
221
|
+
color: inherit;
|
|
234
222
|
display: flex;
|
|
235
223
|
align-items: center;
|
|
236
224
|
}
|
package/src/NavMenu.astro
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
+
import type { MenuItem, SecondaryMenuItem, TertiaryMenuItem } from "./index.js";
|
|
3
|
+
|
|
2
4
|
export interface Props {
|
|
3
|
-
menuItems?:
|
|
4
|
-
link: string;
|
|
5
|
-
text: string;
|
|
6
|
-
submenu?: Array<{
|
|
7
|
-
link: string;
|
|
8
|
-
text: string;
|
|
9
|
-
submenu?: Array<{
|
|
10
|
-
link: string;
|
|
11
|
-
text: string;
|
|
12
|
-
}>;
|
|
13
|
-
}>;
|
|
14
|
-
}>;
|
|
5
|
+
menuItems?: MenuItem[];
|
|
15
6
|
showHomeLink?: boolean;
|
|
16
7
|
homeText?: string;
|
|
17
8
|
}
|
|
@@ -24,7 +15,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
24
15
|
<script>
|
|
25
16
|
function adjustSubsubmenuPositionOnResize() {
|
|
26
17
|
const subsubmenus = document.querySelectorAll(
|
|
27
|
-
".subsubmenu"
|
|
18
|
+
".subsubmenu",
|
|
28
19
|
) as NodeListOf<HTMLElement>;
|
|
29
20
|
|
|
30
21
|
subsubmenus.forEach((subsubmenu) => {
|
|
@@ -91,7 +82,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
91
82
|
const currentTransform = (subsubmenu as HTMLElement).style.transform;
|
|
92
83
|
if (currentTransform && currentTransform.includes("translateX")) {
|
|
93
84
|
const existingTranslate = currentTransform.match(
|
|
94
|
-
/translateX\(([^)]+)\)
|
|
85
|
+
/translateX\(([^)]+)\)/,
|
|
95
86
|
);
|
|
96
87
|
if (existingTranslate) {
|
|
97
88
|
const existingValue = existingTranslate[1];
|
|
@@ -135,7 +126,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
135
126
|
)
|
|
136
127
|
}
|
|
137
128
|
{
|
|
138
|
-
menuItems.map((item) => (
|
|
129
|
+
menuItems.map((item: MenuItem) => (
|
|
139
130
|
<li class="menu__item">
|
|
140
131
|
<a class="menu__link" href={item.link}>
|
|
141
132
|
{item.text}
|
|
@@ -150,7 +141,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
150
141
|
</a>
|
|
151
142
|
{item.submenu && (
|
|
152
143
|
<ul class="submenu">
|
|
153
|
-
{item.submenu.map((sub) => (
|
|
144
|
+
{item.submenu.map((sub: SecondaryMenuItem) => (
|
|
154
145
|
<li class="submenu__item submenu__item--secondary">
|
|
155
146
|
<a href={sub.link}>
|
|
156
147
|
{sub.text}
|
|
@@ -165,7 +156,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
165
156
|
</a>
|
|
166
157
|
{sub.submenu && (
|
|
167
158
|
<ul class="subsubmenu">
|
|
168
|
-
{sub.submenu.map((subsub) => (
|
|
159
|
+
{sub.submenu.map((subsub: TertiaryMenuItem) => (
|
|
169
160
|
<li class="submenu__item--tertiary">
|
|
170
161
|
<a href={subsub.link}>{subsub.text}</a>
|
|
171
162
|
</li>
|
|
@@ -202,11 +193,15 @@ const pagePathname = Astro.url.pathname;
|
|
|
202
193
|
|
|
203
194
|
.menu__link {
|
|
204
195
|
text-decoration: none;
|
|
205
|
-
padding: 0.5em
|
|
196
|
+
padding: 0.5em;
|
|
206
197
|
display: flex;
|
|
207
198
|
align-items: center;
|
|
208
199
|
gap: 0.8em;
|
|
209
|
-
color: color-mix(
|
|
200
|
+
color: color-mix(
|
|
201
|
+
in srgb,
|
|
202
|
+
var(--accent-color, #00ffff) 30%,
|
|
203
|
+
var(--text-color, #ffffff) 70%
|
|
204
|
+
);
|
|
210
205
|
position: relative;
|
|
211
206
|
}
|
|
212
207
|
|
|
@@ -221,7 +216,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
221
216
|
transform-origin: 1;
|
|
222
217
|
height: 2px;
|
|
223
218
|
transition: transform 0.3s ease;
|
|
224
|
-
background: var(--
|
|
219
|
+
background: var(--accent-color, #00ffff);
|
|
225
220
|
}
|
|
226
221
|
|
|
227
222
|
.menu__link:hover::after {
|
|
@@ -239,13 +234,14 @@ const pagePathname = Astro.url.pathname;
|
|
|
239
234
|
|
|
240
235
|
.submenu,
|
|
241
236
|
.subsubmenu {
|
|
242
|
-
color:
|
|
243
|
-
background-color: #171717;
|
|
244
|
-
border: 0.5px solid
|
|
237
|
+
color: var(--text-color, #ffffff);
|
|
238
|
+
background-color: var(--bg-color-opaque, #171717);
|
|
239
|
+
border: 0.5px solid
|
|
240
|
+
color-mix(in srgb, var(--accent-color, #3e1c71) 30%, transparent);
|
|
245
241
|
position: absolute;
|
|
246
242
|
flex-direction: column;
|
|
247
243
|
gap: 0.4em;
|
|
248
|
-
padding:
|
|
244
|
+
padding: 1em;
|
|
249
245
|
border-radius: 10px;
|
|
250
246
|
transition: transform 0.3s ease-in-out;
|
|
251
247
|
pointer-events: none;
|
|
@@ -267,8 +263,7 @@ const pagePathname = Astro.url.pathname;
|
|
|
267
263
|
width: 100%;
|
|
268
264
|
height: 64px;
|
|
269
265
|
border-radius: 6px;
|
|
270
|
-
color: #fff;
|
|
271
|
-
color: var(--color-tertiary, #fff);
|
|
266
|
+
color: var(--text-color, #fff);
|
|
272
267
|
}
|
|
273
268
|
|
|
274
269
|
.submenu__item--secondary,
|
|
@@ -283,8 +278,12 @@ const pagePathname = Astro.url.pathname;
|
|
|
283
278
|
|
|
284
279
|
.submenu__item--secondary a:hover,
|
|
285
280
|
.submenu__item--tertiary a:hover {
|
|
286
|
-
background:
|
|
287
|
-
|
|
281
|
+
background: color-mix(
|
|
282
|
+
in srgb,
|
|
283
|
+
var(--accent-color, #00ffff) 15%,
|
|
284
|
+
transparent
|
|
285
|
+
);
|
|
286
|
+
color: var(--text-color, white);
|
|
288
287
|
}
|
|
289
288
|
|
|
290
289
|
.submenu {
|
package/src/index.ts
CHANGED
|
@@ -7,16 +7,66 @@ export interface MenuItemType {
|
|
|
7
7
|
submenu?: MenuItemType[];
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export interface
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export interface TertiaryMenuItem {
|
|
11
|
+
link: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SecondaryMenuItem {
|
|
16
|
+
link: string;
|
|
17
|
+
text: string;
|
|
18
|
+
submenu?: TertiaryMenuItem[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MenuItem {
|
|
22
|
+
link: string;
|
|
23
|
+
text: string;
|
|
24
|
+
submenu?: SecondaryMenuItem[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LogoConfig {
|
|
28
|
+
src?: string;
|
|
29
|
+
alt?: string;
|
|
30
|
+
width?: string;
|
|
31
|
+
text?: string;
|
|
32
|
+
textSize?: string;
|
|
33
|
+
textColor?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NavConfig {
|
|
15
37
|
homeUrl?: string;
|
|
16
|
-
menuItems?:
|
|
38
|
+
menuItems?: MenuItem[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ThemeConfig {
|
|
17
42
|
backgroundColor?: string;
|
|
43
|
+
backgroundColorOpaque?: string;
|
|
18
44
|
backdropBlur?: string;
|
|
19
45
|
zIndex?: number;
|
|
46
|
+
textColor?: string;
|
|
47
|
+
accentColor?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DualThemeConfig {
|
|
51
|
+
light?: ThemeConfig;
|
|
52
|
+
dark?: ThemeConfig;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface CustomClassNames {
|
|
56
|
+
container?: string;
|
|
57
|
+
header?: string;
|
|
58
|
+
logo?: string;
|
|
59
|
+
logoText?: string;
|
|
60
|
+
nav?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface HeaderProps {
|
|
64
|
+
headerType?: "floating" | "fullscreen";
|
|
65
|
+
preset?: "light" | "dark" | "auto";
|
|
66
|
+
logo?: LogoConfig;
|
|
67
|
+
navigation?: NavConfig;
|
|
68
|
+
theme?: DualThemeConfig;
|
|
69
|
+
classNames?: CustomClassNames;
|
|
20
70
|
}
|
|
21
71
|
|
|
22
72
|
export interface NavMenuProps {
|