@sofidevo/astro-dynamic-header 1.1.34 → 2.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 +158 -168
- package/package.json +4 -4
- package/src/Header.astro +179 -82
- package/src/MobileNav.astro +16 -28
- package/src/NavMenu.astro +27 -28
- package/src/index.ts +56 -6
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ A dynamic, responsive header component for Astro projects that can switch betwee
|
|
|
12
12
|
- **Customizable**: Extensive customization options for colors, sizes, and behavior
|
|
13
13
|
- **Astro Optimized**: Built specifically for Astro framework
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
### Live demo
|
|
17
|
-
|
|
16
|
+
|
|
17
|
+
[https://base-astro-psi.vercel.app/fullscreen-demo](https://base-astro-psi.vercel.app/fullscreen-demo)
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -34,86 +34,49 @@ Add this to your main layout or in the `<head>` section of your Astro pages.
|
|
|
34
34
|
|
|
35
35
|
## Quick Start
|
|
36
36
|
|
|
37
|
-
### Basic Usage
|
|
37
|
+
### Basic Usage (Automatic Theme)
|
|
38
|
+
|
|
39
|
+
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
40
|
|
|
39
41
|
```astro
|
|
40
42
|
---
|
|
41
|
-
// Option 1: Import from direct subpath (recommended)
|
|
42
43
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{ link: '/contact', text: 'Contact' },
|
|
50
|
-
];
|
|
45
|
+
const navigation = {
|
|
46
|
+
menuItems: [
|
|
47
|
+
{ link: '/about', text: 'About' },
|
|
48
|
+
]
|
|
49
|
+
};
|
|
51
50
|
---
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
logoSrc="/logo.png"
|
|
56
|
-
menuItems={menuItems}
|
|
57
|
-
/>
|
|
52
|
+
<!-- Detects .dark class on root automatically -->
|
|
53
|
+
<Header navigation={navigation} />
|
|
58
54
|
```
|
|
59
55
|
|
|
60
|
-
###
|
|
56
|
+
### Advanced Usage (Dual-Theme Customization)
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```json
|
|
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
|
|
58
|
+
You can provide custom colors for both light and dark modes simultaneously.
|
|
78
59
|
|
|
79
60
|
```astro
|
|
80
61
|
---
|
|
81
62
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
text: 'Services',
|
|
88
|
-
submenu: [
|
|
89
|
-
{
|
|
90
|
-
link: '#',
|
|
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
|
-
],
|
|
63
|
+
|
|
64
|
+
const theme = {
|
|
65
|
+
light: {
|
|
66
|
+
accentColor: "#3e1c71",
|
|
67
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
101
68
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
69
|
+
dark: {
|
|
70
|
+
accentColor: "#00ffff",
|
|
71
|
+
backgroundColor: "rgba(20, 20, 20, 0.9)"
|
|
72
|
+
}
|
|
73
|
+
};
|
|
105
74
|
---
|
|
106
75
|
|
|
107
76
|
<Header
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
logoWidth="150px"
|
|
112
|
-
homeUrl="/"
|
|
113
|
-
menuItems={menuItems}
|
|
114
|
-
backgroundColor="#000000dd"
|
|
115
|
-
backdropBlur="blur(15px)"
|
|
116
|
-
zIndex={100}
|
|
77
|
+
preset="auto"
|
|
78
|
+
theme={theme}
|
|
79
|
+
navigation={navigation}
|
|
117
80
|
/>
|
|
118
81
|
```
|
|
119
82
|
|
|
@@ -121,105 +84,83 @@ const menuItems = [
|
|
|
121
84
|
|
|
122
85
|
### Header Component
|
|
123
86
|
|
|
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
|
-
|
|
87
|
+
| Prop | Type | Default | Description |
|
|
88
|
+
| ------------ | ----------------------------- | ------------ | ------------------------------------------ |
|
|
89
|
+
| `headerType` | `"floating" \| "fullscreen"` | `"floating"` | Header layout style |
|
|
90
|
+
| `preset` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme behavior. `auto` follows root class. |
|
|
91
|
+
| `logo` | `LogoConfig` | `{}` | Logo configuration object |
|
|
92
|
+
| `navigation` | `NavConfig` | `{}` | Navigation configuration object |
|
|
93
|
+
| `theme` | `DualThemeConfig` | `{}` | Custom theme overrides for light/dark |
|
|
94
|
+
| `classNames` | `CustomClassNames` | `{}` | Custom class names for CSS Modules |
|
|
95
|
+
|
|
96
|
+
### Config Objects
|
|
97
|
+
|
|
98
|
+
#### DualThemeConfig
|
|
99
|
+
|
|
100
|
+
| Propery | Type | Description |
|
|
101
|
+
| ------- | ------------- | ---------------------------- |
|
|
102
|
+
| `light` | `ThemeConfig` | Styles applied in light mode |
|
|
103
|
+
| `dark` | `ThemeConfig` | Styles applied in dark mode |
|
|
104
|
+
|
|
105
|
+
#### ThemeConfig
|
|
106
|
+
|
|
107
|
+
| Propery | Type | Default |
|
|
108
|
+
| ---------------------- | -------- | ---------------- |
|
|
109
|
+
| `backgroundColor` | `string` | _Preset default_ |
|
|
110
|
+
| `backgroundColorOpaque`| `string` | _Preset default_ |
|
|
111
|
+
| `backdropBlur` | `string` | `"blur(20px)"` |
|
|
112
|
+
| `zIndex` | `number` | `10` |
|
|
113
|
+
| `textColor` | `string` | _Preset default_ |
|
|
114
|
+
| `accentColor` | `string` | _Preset default_ |
|
|
115
|
+
|
|
116
|
+
> [!IMPORTANT]
|
|
117
|
+
> **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**.
|
|
118
|
+
>
|
|
119
|
+
> If you use a transparent `backgroundColor` (e.g., `rgba`), remember to also provide its solid counterpart in `backgroundColorOpaque`.
|
|
120
|
+
|
|
121
|
+
```astro
|
|
122
|
+
<Header theme={{
|
|
123
|
+
light: {
|
|
124
|
+
backgroundColor: "rgba(255, 255, 255, 0.7)", // Transparent header body
|
|
125
|
+
backgroundColorOpaque: "#ffffff", // Solid submenus
|
|
126
|
+
}
|
|
127
|
+
}} />
|
|
147
128
|
```
|
|
148
129
|
|
|
130
|
+
#### CustomClassNames
|
|
131
|
+
|
|
132
|
+
| Propery | Type | Description |
|
|
133
|
+
| ----------- | -------- | -------------------------------- |
|
|
134
|
+
| `container` | `string` | Main container class |
|
|
135
|
+
| `header` | `string` | Header element class |
|
|
136
|
+
| `logo` | `string` | Logo link container class |
|
|
137
|
+
| `logoText` | `string` | Logo text class |
|
|
138
|
+
| `nav` | `string` | Desktop navigation wrapper class |
|
|
139
|
+
|
|
149
140
|
## Slots Support
|
|
150
141
|
|
|
151
142
|
The Header component provides a flexible slot system that allows you to add additional content:
|
|
152
143
|
|
|
153
144
|
### Available Slots
|
|
154
145
|
|
|
155
|
-
| Slot Name | Location
|
|
156
|
-
|
|
157
|
-
| `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.)
|
|
158
|
-
|
|
159
|
-
### Actions Slot Behavior
|
|
146
|
+
| Slot Name | Location | Visibility | Description |
|
|
147
|
+
| --------- | --------------------- | --------------------- | ---------------------------------------- |
|
|
148
|
+
| `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.) |
|
|
160
149
|
|
|
161
|
-
|
|
162
|
-
- **Desktop (≥768px)**: Shows actions in the main header after navigation
|
|
163
|
-
- **Mobile (<768px)**: Shows same actions inside the mobile menu panel
|
|
164
|
-
- **Automatic hiding**: Desktop version is hidden on mobile, mobile version is hidden on desktop
|
|
165
|
-
|
|
166
|
-
### Slot Examples
|
|
167
|
-
|
|
168
|
-
#### Using the Actions Slot
|
|
150
|
+
### Example with Slots
|
|
169
151
|
|
|
170
152
|
```astro
|
|
171
153
|
---
|
|
172
154
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
173
155
|
|
|
174
|
-
const
|
|
175
|
-
{ link: '/about', text: 'About' }
|
|
176
|
-
|
|
177
|
-
];
|
|
156
|
+
const navigation = {
|
|
157
|
+
menuItems: [{ link: '/about', text: 'About' }]
|
|
158
|
+
};
|
|
178
159
|
---
|
|
179
160
|
|
|
180
|
-
<Header
|
|
181
|
-
headerType="floating"
|
|
182
|
-
logoSrc="/logo.png"
|
|
183
|
-
logoText="My Company"
|
|
184
|
-
menuItems={menuItems}
|
|
185
|
-
>
|
|
161
|
+
<Header navigation={navigation}>
|
|
186
162
|
<div slot="actions">
|
|
187
163
|
<button class="login-btn">Login</button>
|
|
188
|
-
<button class="signup-btn">Sign Up</button>
|
|
189
|
-
</div>
|
|
190
|
-
</Header>
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
#### Complete Example with All Props
|
|
194
|
-
|
|
195
|
-
```astro
|
|
196
|
-
---
|
|
197
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
198
|
-
|
|
199
|
-
const menuItems = [
|
|
200
|
-
{ link: '/about', text: 'About' },
|
|
201
|
-
{ link: '/services', text: 'Services' },
|
|
202
|
-
{ link: '/contact', text: 'Contact' },
|
|
203
|
-
];
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
<Header
|
|
207
|
-
headerType="fullscreen"
|
|
208
|
-
logoSrc="/logo.png"
|
|
209
|
-
logoAlt="My Company"
|
|
210
|
-
logoWidth="60px"
|
|
211
|
-
logoText="My Company"
|
|
212
|
-
logoTextSize="1.2em"
|
|
213
|
-
logoTextColor="#ffffff"
|
|
214
|
-
homeUrl="/"
|
|
215
|
-
menuItems={menuItems}
|
|
216
|
-
backgroundColor="#000000dd"
|
|
217
|
-
backdropBlur="blur(15px)"
|
|
218
|
-
zIndex={100}
|
|
219
|
-
>
|
|
220
|
-
<div slot="actions" class="header-actions">
|
|
221
|
-
<button class="btn btn-outline">Login</button>
|
|
222
|
-
<button class="btn btn-primary">Get Started</button>
|
|
223
164
|
</div>
|
|
224
165
|
</Header>
|
|
225
166
|
```
|
|
@@ -265,21 +206,23 @@ const menuItems = [
|
|
|
265
206
|
background: #00cccc;
|
|
266
207
|
}
|
|
267
208
|
```
|
|
209
|
+
|
|
268
210
|
## Header Types
|
|
269
211
|
|
|
270
212
|
### Floating Header
|
|
213
|
+
|
|
271
214
|
- Centered with max-width constraint
|
|
272
215
|
- Rounded corners
|
|
273
216
|
- Padding around container
|
|
274
217
|
- Perfect for modern, card-like designs
|
|
275
218
|
|
|
276
219
|
### Fullscreen Header
|
|
220
|
+
|
|
277
221
|
- Full viewport width
|
|
278
222
|
- No border radius
|
|
279
223
|
- Edge-to-edge design
|
|
280
224
|
- Ideal for traditional website layouts
|
|
281
225
|
|
|
282
|
-
|
|
283
226
|
## Styling and Customization
|
|
284
227
|
|
|
285
228
|
The component uses CSS custom properties that you can override:
|
|
@@ -294,24 +237,71 @@ The component uses CSS custom properties that you can override:
|
|
|
294
237
|
|
|
295
238
|
## TypeScript Support
|
|
296
239
|
|
|
297
|
-
|
|
240
|
+
The package provides full TypeScript support. You can import types to ensure your configuration is correct:
|
|
298
241
|
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
242
|
+
```astro
|
|
243
|
+
---
|
|
244
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
245
|
+
import type {
|
|
246
|
+
NavConfig,
|
|
247
|
+
DualThemeConfig,
|
|
248
|
+
MenuItem,
|
|
249
|
+
SecondaryMenuItem
|
|
306
250
|
} from '@sofidevo/astro-dynamic-header';
|
|
251
|
+
|
|
252
|
+
const navigation: NavConfig = {
|
|
253
|
+
menuItems: [
|
|
254
|
+
{
|
|
255
|
+
link: '/products',
|
|
256
|
+
text: 'Products',
|
|
257
|
+
submenu: [
|
|
258
|
+
{ link: '/software', text: 'Software' },
|
|
259
|
+
{ link: '/hardware', text: 'Hardware' }
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const theme: DualThemeConfig = {
|
|
266
|
+
light: {
|
|
267
|
+
accentColor: "#3e1c71",
|
|
268
|
+
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
|
269
|
+
backgroundColorOpaque: "#ffffff"
|
|
270
|
+
},
|
|
271
|
+
dark: {
|
|
272
|
+
accentColor: "#00ffff",
|
|
273
|
+
backgroundColor: "rgba(10, 10, 10, 0.9)",
|
|
274
|
+
backgroundColorOpaque: "#0a0a0a"
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
<Header
|
|
280
|
+
navigation={navigation}
|
|
281
|
+
theme={theme}
|
|
282
|
+
preset="auto"
|
|
283
|
+
/>
|
|
307
284
|
```
|
|
308
285
|
|
|
286
|
+
### Available Types
|
|
287
|
+
|
|
288
|
+
| Type | Description |
|
|
289
|
+
|------|-------------|
|
|
290
|
+
| `MenuItem` | Top-level menu item with optional properties |
|
|
291
|
+
| `SecondaryMenuItem` | Second-level menu item |
|
|
292
|
+
| `TertiaryMenuItem` | Third-level menu item |
|
|
293
|
+
| `NavConfig` | Main navigation configuration object |
|
|
294
|
+
| `ThemeConfig` | Individual theme settings (colors, blur, etc.) |
|
|
295
|
+
| `DualThemeConfig` | Combined settings for light and dark modes |
|
|
296
|
+
| `LogoConfig` | Logo image and text configuration |
|
|
297
|
+
| `HeaderProps` | Main props for the Header component |
|
|
298
|
+
|
|
309
299
|
## Browser Support
|
|
310
300
|
|
|
311
|
-
- All modern browsers
|
|
312
|
-
- Mobile responsive design
|
|
313
|
-
- Supports CSS `backdrop-filter`
|
|
314
|
-
-
|
|
301
|
+
- All modern browsers (Chrome, Firefox, Safari, Edge)
|
|
302
|
+
- Mobile responsive design with optimized touch targets
|
|
303
|
+
- Supports CSS `backdrop-filter` for glassmorphism
|
|
304
|
+
- Automatic theme switching based on OS or site preference via `.dark` class
|
|
315
305
|
|
|
316
306
|
## Troubleshooting
|
|
317
307
|
|
|
@@ -320,29 +310,26 @@ import type {
|
|
|
320
310
|
If you encounter import errors, try these solutions:
|
|
321
311
|
|
|
322
312
|
1. **Use direct subpath import:**
|
|
313
|
+
|
|
323
314
|
```astro
|
|
324
315
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
325
316
|
```
|
|
326
317
|
|
|
327
|
-
2. **
|
|
318
|
+
2. **Check relative imports in TS:**
|
|
319
|
+
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.
|
|
320
|
+
|
|
321
|
+
3. **Verify TypeScript configuration:**
|
|
322
|
+
|
|
328
323
|
```json
|
|
329
324
|
// tsconfig.json
|
|
330
325
|
{
|
|
331
326
|
"compilerOptions": {
|
|
332
|
-
"moduleResolution": "bundler",
|
|
327
|
+
"moduleResolution": "bundler",
|
|
333
328
|
"allowImportingTsExtensions": true
|
|
334
329
|
}
|
|
335
330
|
}
|
|
336
331
|
```
|
|
337
332
|
|
|
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
333
|
### Compatibility
|
|
347
334
|
|
|
348
335
|
- Astro 4.x and 5.x
|
|
@@ -376,18 +363,21 @@ npm run test:coverage
|
|
|
376
363
|
The test suite covers:
|
|
377
364
|
|
|
378
365
|
#### Component Logic Tests
|
|
366
|
+
|
|
379
367
|
- **Header Component** (4 tests): Hamburger controller functionality, menu toggle behavior
|
|
380
368
|
- **HamburgerButton Component** (10 tests): Button states, responsive behavior, accessibility
|
|
381
369
|
- **MobileNav Component** (7 tests): Dropdown structure, nested submenus, conditional rendering
|
|
382
370
|
- **NavMenu Component** (6 tests): Dynamic positioning, submenu interactions, viewport adjustments
|
|
383
371
|
|
|
384
372
|
#### Integration Tests (7 tests)
|
|
373
|
+
|
|
385
374
|
- Component interaction flows
|
|
386
375
|
- Responsive behavior between mobile/desktop
|
|
387
376
|
- Keyboard navigation and accessibility
|
|
388
377
|
- Menu state management during navigation
|
|
389
378
|
|
|
390
379
|
### Test Technologies
|
|
380
|
+
|
|
391
381
|
- **Vitest**: Fast testing framework
|
|
392
382
|
- **jsdom**: DOM simulation for component testing
|
|
393
383
|
- **TypeScript**: Type-safe test writing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofidevo/astro-dynamic-header",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -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,69 @@ 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
|
+
// Default theme configuration
|
|
32
|
+
const defaultThemes = {
|
|
33
|
+
light: {
|
|
34
|
+
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
35
|
+
backgroundColorOpaque: "rgb(255, 255, 255)",
|
|
36
|
+
backdropBlur: "blur(20px)",
|
|
37
|
+
zIndex: 10,
|
|
38
|
+
textColor: "#1a1a1a",
|
|
39
|
+
accentColor: "#3e1c71",
|
|
40
|
+
},
|
|
41
|
+
dark: {
|
|
42
|
+
backgroundColor: "#0d0d0dcc",
|
|
43
|
+
backgroundColorOpaque: "#0d0d0d",
|
|
44
|
+
backdropBlur: "blur(20px)",
|
|
45
|
+
zIndex: 10,
|
|
46
|
+
textColor: "#ffffff",
|
|
47
|
+
accentColor: "#00ffff",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Merge with user overrides
|
|
52
|
+
const lightTheme = { ...defaultThemes.light, ...theme.light };
|
|
53
|
+
const darkTheme = { ...defaultThemes.dark, ...theme.dark };
|
|
54
|
+
|
|
55
|
+
// Navigation configuration
|
|
56
|
+
const { homeUrl = "/", menuItems = [] } = navigation;
|
|
57
|
+
|
|
58
|
+
// Logo configuration
|
|
59
|
+
const {
|
|
60
|
+
src: logoSrc = "/logo.png",
|
|
61
|
+
alt: logoAlt = "Logo",
|
|
62
|
+
width: logoWidth = "55px",
|
|
63
|
+
text: logoText = "",
|
|
64
|
+
textSize: logoTextSize = "1em",
|
|
65
|
+
textColor: logoTextColor = "inherit", // Will inherit from theme text-color
|
|
66
|
+
} = logo;
|
|
67
|
+
|
|
68
|
+
const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
49
69
|
---
|
|
50
70
|
|
|
51
71
|
<script>
|
|
@@ -64,7 +84,7 @@ const {
|
|
|
64
84
|
private init(): void {
|
|
65
85
|
if (this.hamburgerBtn && this.mobileMenu) {
|
|
66
86
|
this.hamburgerBtn.addEventListener("click", () => this.toggleMenu());
|
|
67
|
-
this.links.forEach(link => {
|
|
87
|
+
this.links.forEach((link) => {
|
|
68
88
|
link.addEventListener("click", () => this.closeMenu());
|
|
69
89
|
});
|
|
70
90
|
}
|
|
@@ -85,38 +105,134 @@ const {
|
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
|
|
89
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
108
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
90
109
|
new HamburgerController();
|
|
91
110
|
});
|
|
92
111
|
</script>
|
|
93
112
|
|
|
94
|
-
<div
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
<div
|
|
114
|
+
class:list={[
|
|
115
|
+
"header__container",
|
|
116
|
+
`header__container--${headerType}`,
|
|
117
|
+
classNames.container,
|
|
118
|
+
]}
|
|
119
|
+
style={{ zIndex: lightTheme.zIndex }}
|
|
120
|
+
>
|
|
121
|
+
<header
|
|
122
|
+
class:list={["header", `header--${headerType}`, forcedClass, classNames.header]}
|
|
123
|
+
style={{
|
|
124
|
+
"--l-bg": lightTheme.backgroundColor,
|
|
125
|
+
"--l-bg-opaque": lightTheme.backgroundColorOpaque,
|
|
126
|
+
"--l-text": lightTheme.textColor,
|
|
127
|
+
"--l-accent": lightTheme.accentColor,
|
|
128
|
+
"--l-blur": lightTheme.backdropBlur,
|
|
129
|
+
"--d-bg": darkTheme.backgroundColor,
|
|
130
|
+
"--d-bg-opaque": darkTheme.backgroundColorOpaque,
|
|
131
|
+
"--d-text": darkTheme.textColor,
|
|
132
|
+
"--d-accent": darkTheme.accentColor,
|
|
133
|
+
"--d-blur": darkTheme.backdropBlur,
|
|
134
|
+
}}
|
|
135
|
+
>
|
|
136
|
+
<a class:list={["logo__container", classNames.logo]} href={homeUrl}>
|
|
137
|
+
<img
|
|
138
|
+
class="header__logo"
|
|
139
|
+
src={logoSrc}
|
|
140
|
+
alt={logoAlt}
|
|
141
|
+
style={{ width: logoWidth }}
|
|
142
|
+
/>
|
|
143
|
+
{
|
|
144
|
+
logoText && (
|
|
145
|
+
<span
|
|
146
|
+
class:list={["header__logo-text", classNames.logoText]}
|
|
147
|
+
style={{ color: logoTextColor, fontSize: logoTextSize }}
|
|
148
|
+
>
|
|
149
|
+
{logoText}
|
|
150
|
+
</span>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
99
153
|
</a>
|
|
100
|
-
|
|
101
|
-
{
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{Astro.slots.has('actions') && (
|
|
109
|
-
<div class="actions-mobile" slot="slot-panel">
|
|
154
|
+
|
|
155
|
+
<div class:list={["nav-menu-wrapper", classNames.nav]}>
|
|
156
|
+
<NavMenu menuItems={menuItems} />
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{
|
|
160
|
+
Astro.slots.has("actions") && (
|
|
161
|
+
<div class="actions-desktop">
|
|
110
162
|
<slot name="actions" />
|
|
111
163
|
</div>
|
|
112
|
-
)
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
<HamburgerButton />
|
|
168
|
+
|
|
169
|
+
<MobileNav
|
|
170
|
+
menuItems={menuItems}
|
|
171
|
+
type={headerType}
|
|
172
|
+
>
|
|
173
|
+
{
|
|
174
|
+
Astro.slots.has("actions") && (
|
|
175
|
+
<div class="actions-mobile" slot="slot-panel">
|
|
176
|
+
<slot name="actions" />
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
113
180
|
</MobileNav>
|
|
114
|
-
|
|
115
181
|
</header>
|
|
116
182
|
</div>
|
|
117
183
|
|
|
118
|
-
<style is:inline
|
|
119
|
-
|
|
184
|
+
<style is:inline>
|
|
185
|
+
.header {
|
|
186
|
+
--bg-color: var(--l-bg);
|
|
187
|
+
--bg-color-opaque: var(--l-bg-opaque);
|
|
188
|
+
--text-color: var(--l-text);
|
|
189
|
+
--accent-color: var(--l-accent);
|
|
190
|
+
--backdrop-blur: var(--l-blur);
|
|
191
|
+
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
width: 100%;
|
|
195
|
+
justify-content: space-between;
|
|
196
|
+
padding: 1em 2em;
|
|
197
|
+
background-color: var(--bg-color);
|
|
198
|
+
backdrop-filter: var(--backdrop-blur);
|
|
199
|
+
-webkit-backdrop-filter: var(--backdrop-blur);
|
|
200
|
+
color: var(--text-color);
|
|
201
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
202
|
+
|
|
203
|
+
@media (width < 768px) {
|
|
204
|
+
align-self: flex-end;
|
|
205
|
+
padding: 0.4em;
|
|
206
|
+
margin: 0 auto;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* Automatic dark mode detection via root class */
|
|
211
|
+
:root.dark .header:not(.header--force-light) {
|
|
212
|
+
--bg-color: var(--d-bg);
|
|
213
|
+
--bg-color-opaque: var(--d-bg-opaque);
|
|
214
|
+
--text-color: var(--d-text);
|
|
215
|
+
--accent-color: var(--d-accent);
|
|
216
|
+
--backdrop-blur: var(--d-blur);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* Forced themes */
|
|
220
|
+
.header--force-dark {
|
|
221
|
+
--bg-color: var(--d-bg) !important;
|
|
222
|
+
--bg-color-opaque: var(--d-bg-opaque) !important;
|
|
223
|
+
--text-color: var(--d-text) !important;
|
|
224
|
+
--accent-color: var(--d-accent) !important;
|
|
225
|
+
--backdrop-blur: var(--d-blur) !important;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.header--force-light {
|
|
229
|
+
--bg-color: var(--l-bg) !important;
|
|
230
|
+
--bg-color-opaque: var(--l-bg-opaque) !important;
|
|
231
|
+
--text-color: var(--l-text) !important;
|
|
232
|
+
--accent-color: var(--l-accent) !important;
|
|
233
|
+
--backdrop-blur: var(--l-blur) !important;
|
|
234
|
+
}
|
|
235
|
+
|
|
120
236
|
.header__container {
|
|
121
237
|
width: 100%;
|
|
122
238
|
display: flex;
|
|
@@ -131,43 +247,24 @@ const {
|
|
|
131
247
|
}
|
|
132
248
|
}
|
|
133
249
|
|
|
134
|
-
/* Floating header container styles */
|
|
135
250
|
.header__container--floating {
|
|
136
|
-
padding: .5em;
|
|
251
|
+
padding: 0.5em;
|
|
137
252
|
}
|
|
138
253
|
|
|
139
|
-
/* Fullscreen header container styles */
|
|
140
254
|
.header__container--fullscreen {
|
|
141
255
|
padding: 0;
|
|
142
256
|
}
|
|
143
257
|
|
|
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
258
|
.header--floating {
|
|
161
259
|
max-width: 1100px;
|
|
162
260
|
border-radius: 20px;
|
|
163
|
-
padding: .7em 1em;
|
|
261
|
+
padding: 0.7em 1em;
|
|
164
262
|
|
|
165
263
|
@media (width < 768px) {
|
|
166
264
|
max-width: 95%;
|
|
167
265
|
}
|
|
168
266
|
}
|
|
169
267
|
|
|
170
|
-
/* Fullscreen header styles */
|
|
171
268
|
.header--fullscreen {
|
|
172
269
|
border-radius: 0;
|
|
173
270
|
padding: 1em 2em;
|
|
@@ -182,16 +279,17 @@ const {
|
|
|
182
279
|
display: flex;
|
|
183
280
|
align-items: center;
|
|
184
281
|
text-decoration: none;
|
|
185
|
-
|
|
282
|
+
color: inherit;
|
|
283
|
+
& span {
|
|
186
284
|
font-weight: 600;
|
|
187
285
|
}
|
|
188
286
|
}
|
|
287
|
+
|
|
189
288
|
.header__logo {
|
|
190
289
|
margin-right: 1em;
|
|
191
290
|
object-fit: contain;
|
|
192
291
|
}
|
|
193
292
|
|
|
194
|
-
/* Actions visibility control */
|
|
195
293
|
.actions-desktop {
|
|
196
294
|
@media (width < 768px) {
|
|
197
295
|
display: none;
|
|
@@ -205,7 +303,6 @@ const {
|
|
|
205
303
|
}
|
|
206
304
|
}
|
|
207
305
|
|
|
208
|
-
/* Adjust transform for fullscreen header */
|
|
209
306
|
.header--fullscreen .mobile-header__menu.is-active {
|
|
210
307
|
transform: translate(0%);
|
|
211
308
|
}
|
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 {
|