@sofidevo/astro-dynamic-header 2.0.2 → 3.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 +160 -88
- package/package.json +3 -3
- package/src/HamburgerButton.astro +2 -12
- package/src/Header.astro +73 -116
- package/src/MobileNav.astro +47 -12
- package/src/NavMenu.astro +23 -7
- package/src/defaults.ts +36 -0
- package/src/index.ts +62 -61
package/README.md
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
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
5
|
> [!WARNING]
|
|
6
|
-
> **Breaking Changes
|
|
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.
|
|
7
13
|
|
|
8
14
|
## Features
|
|
9
15
|
|
|
@@ -12,7 +18,8 @@ A dynamic, responsive header component for Astro projects that can switch betwee
|
|
|
12
18
|
- **Multi-level Dropdowns**: Support for nested navigation menus
|
|
13
19
|
- **Slot Support**: Customizable slots for desktop header and mobile panel content
|
|
14
20
|
- **TypeScript Support**: Full type safety and IntelliSense
|
|
15
|
-
- **
|
|
21
|
+
- **Two-layer customization**: High-level `classNames` prop + fine-grained nested class props inside `navigation` and `logo`
|
|
22
|
+
- **Exportable defaults**: Import and extend `defaultThemes` from your own code
|
|
16
23
|
- **Astro Optimized**: Built specifically for Astro framework
|
|
17
24
|
|
|
18
25
|
### Live demo
|
|
@@ -45,11 +52,9 @@ By default, the header uses `preset="auto"`, which automatically detects the the
|
|
|
45
52
|
---
|
|
46
53
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
]
|
|
52
|
-
|
|
55
|
+
const menuItems = [
|
|
56
|
+
{ link: '/about', text: 'About' },
|
|
57
|
+
];
|
|
53
58
|
---
|
|
54
59
|
|
|
55
60
|
<!-- Detects .dark class on root automatically -->
|
|
@@ -92,14 +97,13 @@ const theme = {
|
|
|
92
97
|
|
|
93
98
|
### Header Component
|
|
94
99
|
|
|
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
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `classNames` | `CustomClassNames` | `{}` | Custom class names for CSS Modules |
|
|
100
|
+
| Prop | Type | Default | Description |
|
|
101
|
+
| ------------ | ----------------------------- | ------------ | ---------------------------------------------- |
|
|
102
|
+
| `headerType` | `"floating" \| "fullscreen"` | `"floating"` | Header layout style |
|
|
103
|
+
| `preset` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme behavior. `auto` follows root class. |
|
|
104
|
+
| `navigation` | `NavConfig` | `{}` | Navigation configuration object |
|
|
105
|
+
| `theme` | `DualThemeConfig` | `{}` | Custom theme overrides for light/dark |
|
|
106
|
+
| `classNames` | `HeaderClassNames` | `{}` | High-level class overrides for layout elements |
|
|
103
107
|
|
|
104
108
|
### Config Objects
|
|
105
109
|
|
|
@@ -135,64 +139,61 @@ const theme = {
|
|
|
135
139
|
}} />
|
|
136
140
|
```
|
|
137
141
|
|
|
138
|
-
#### CustomClassNames
|
|
142
|
+
#### CustomClassNames → HeaderClassNames
|
|
143
|
+
|
|
144
|
+
> [!NOTE]
|
|
145
|
+
> `CustomClassNames` was renamed to `HeaderClassNames` in v2.1. The old name still works as a type alias — no migration required.
|
|
139
146
|
|
|
140
|
-
The `classNames` prop
|
|
147
|
+
The `classNames` prop targets the **structural wrapper elements** of the Header. For fine-grained control of individual nav links or the logo internals, use the nested `xxx__class` props inside the `navigation` or `logo` config objects instead.
|
|
141
148
|
|
|
142
|
-
| Property | Target Element
|
|
143
|
-
| ----------- |
|
|
144
|
-
| `container` | Outer `div` wrapping the header
|
|
145
|
-
| `header` | Inner `<header>` element
|
|
146
|
-
| `logo` | `<a>` tag surrounding the logo
|
|
147
|
-
| `logoText` | `<span>` tag containing the logo text
|
|
148
|
-
| `nav` |
|
|
149
|
+
| Property | Target Element | Purpose & Common Use Cases |
|
|
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. |
|
|
149
157
|
|
|
150
|
-
#####
|
|
158
|
+
##### Usage Examples
|
|
151
159
|
|
|
152
|
-
**
|
|
153
|
-
Ideal for creating a modern "glass" effect with a subtle border and shadow that adapts to dark mode.
|
|
160
|
+
**Premium Shadow & Border:**
|
|
154
161
|
|
|
155
162
|
```astro
|
|
156
|
-
<Header
|
|
157
|
-
classNames={{
|
|
163
|
+
<Header
|
|
164
|
+
classNames={{
|
|
158
165
|
header: "shadow-xl border-b border-black/5 dark:border-white/10 transition-all duration-500",
|
|
159
|
-
container: "top-4 px-6"
|
|
160
|
-
|
|
166
|
+
container: "top-4 px-6",
|
|
167
|
+
mobileNav: "backdrop-blur-md",
|
|
168
|
+
}}
|
|
161
169
|
/>
|
|
162
170
|
```
|
|
163
171
|
|
|
164
|
-
**Custom Typography
|
|
165
|
-
Perfect for matching the header with your brand's specific typography and layout requirements.
|
|
172
|
+
**Custom Typography & Nav Spacing:**
|
|
166
173
|
|
|
167
174
|
```astro
|
|
168
|
-
<Header
|
|
169
|
-
classNames={{
|
|
175
|
+
<Header
|
|
176
|
+
classNames={{
|
|
170
177
|
logoText: "tracking-tighter font-black italic uppercase",
|
|
171
|
-
nav: "ml-auto gap-8"
|
|
172
|
-
}}
|
|
178
|
+
nav: "ml-auto gap-8",
|
|
179
|
+
}}
|
|
173
180
|
/>
|
|
174
181
|
```
|
|
175
182
|
|
|
176
183
|
> [!TIP]
|
|
177
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.
|
|
178
185
|
|
|
179
|
-
#### LogoConfig
|
|
180
186
|
|
|
181
|
-
| Property | Type | Description |
|
|
182
|
-
| ----------- | -------- | ------------------------------------------------ |
|
|
183
|
-
| `src` | `string` | URL of the logo image |
|
|
184
|
-
| `alt` | `string` | Alternative text for the logo image |
|
|
185
|
-
| `width` | `string` | Width of the logo (e.g., "50px", "5rem") |
|
|
186
|
-
| `text` | `string` | Text to display next to or instead of logo image |
|
|
187
|
-
| `textSize` | `string` | Font size for the logo text |
|
|
188
|
-
| `textColor` | `string` | Color for the logo text |
|
|
189
187
|
|
|
190
188
|
#### NavConfig
|
|
191
189
|
|
|
192
|
-
| Property
|
|
193
|
-
|
|
|
194
|
-
| `homeUrl`
|
|
195
|
-
| `menuItems`
|
|
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 |
|
|
196
197
|
|
|
197
198
|
#### MenuItem
|
|
198
199
|
|
|
@@ -210,6 +211,7 @@ The Header component provides a flexible slot system that allows you to add addi
|
|
|
210
211
|
|
|
211
212
|
| Slot Name | Location | Visibility | Description |
|
|
212
213
|
| --------- | --------------------- | --------------------- | ---------------------------------------- |
|
|
214
|
+
| `logo` | Header Desktop/Mobile | Always | Add your custom logo HTML/Components |
|
|
213
215
|
| `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.) |
|
|
214
216
|
|
|
215
217
|
### Example with Slots
|
|
@@ -260,32 +262,38 @@ const menuItems = [
|
|
|
260
262
|
{ link: "/contact", text: "Contact" },
|
|
261
263
|
];
|
|
262
264
|
|
|
263
|
-
|
|
264
|
-
light: {
|
|
265
|
-
accentColor: "#ff0000",
|
|
266
|
-
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
|
267
|
-
},
|
|
268
|
-
dark: {
|
|
269
|
-
accentColor: "#00ffff",
|
|
270
|
-
backgroundColor: "rgba(20, 20, 20, 0.9)",
|
|
271
|
-
},
|
|
272
|
-
};
|
|
265
|
+
];
|
|
273
266
|
---
|
|
274
267
|
|
|
268
|
+
<style is:inline>
|
|
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>
|
|
278
|
+
|
|
275
279
|
<Header
|
|
276
280
|
headerType="floating"
|
|
277
281
|
preset="dark"
|
|
278
|
-
logo={{
|
|
279
|
-
src: "https://itssofi.dev/img/icons/sofi-icon.webp",
|
|
280
|
-
alt: "My Site Logo",
|
|
281
|
-
width: "44px",
|
|
282
|
-
}}
|
|
283
282
|
navigation={{
|
|
284
283
|
homeUrl: "/",
|
|
285
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",
|
|
286
291
|
}}
|
|
287
|
-
theme={theme}
|
|
288
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>
|
|
289
297
|
<button slot="actions">Login</button>
|
|
290
298
|
</Header>
|
|
291
299
|
```
|
|
@@ -325,9 +333,11 @@ The package provides full TypeScript support. You can import types to ensure you
|
|
|
325
333
|
```astro
|
|
326
334
|
---
|
|
327
335
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
336
|
+
import { defaultThemes } from '@sofidevo/astro-dynamic-header';
|
|
328
337
|
import type {
|
|
329
338
|
NavConfig,
|
|
330
339
|
DualThemeConfig,
|
|
340
|
+
HeaderClassNames,
|
|
331
341
|
MenuItem,
|
|
332
342
|
SecondaryMenuItem
|
|
333
343
|
} from '@sofidevo/astro-dynamic-header';
|
|
@@ -342,42 +352,43 @@ const navigation: NavConfig = {
|
|
|
342
352
|
{ link: '/hardware', text: 'Hardware' }
|
|
343
353
|
]
|
|
344
354
|
}
|
|
345
|
-
]
|
|
355
|
+
],
|
|
356
|
+
header__menu__class: "flex gap-6",
|
|
346
357
|
};
|
|
347
358
|
|
|
359
|
+
// Prefer CSS custom properties globally rather than passing the `theme` object.
|
|
360
|
+
// But you can still use the theme prop if needed:
|
|
348
361
|
const theme: DualThemeConfig = {
|
|
349
|
-
light: {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
backgroundColor: "rgba(10, 10, 10, 0.9)",
|
|
357
|
-
backgroundColorOpaque: "#0a0a0a"
|
|
358
|
-
}
|
|
362
|
+
light: { ...defaultThemes.light, accentColor: "#3e1c71" },
|
|
363
|
+
dark: { ...defaultThemes.dark, accentColor: "#00ffff", backgroundColor: "rgba(10, 10, 10, 0.9)" },
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const classNames: HeaderClassNames = {
|
|
367
|
+
header: "shadow-xl",
|
|
368
|
+
mobileNav: "backdrop-blur-md",
|
|
359
369
|
};
|
|
360
370
|
---
|
|
361
371
|
|
|
362
372
|
<Header
|
|
363
373
|
navigation={navigation}
|
|
364
|
-
|
|
374
|
+
classNames={classNames}
|
|
365
375
|
preset="auto"
|
|
366
376
|
/>
|
|
367
377
|
```
|
|
368
378
|
|
|
369
379
|
### Available Types
|
|
370
380
|
|
|
371
|
-
| Type | Description
|
|
372
|
-
| ------------------- |
|
|
373
|
-
| `MenuItem` | Top-level menu item with optional properties
|
|
374
|
-
| `SecondaryMenuItem` | Second-level menu item
|
|
375
|
-
| `TertiaryMenuItem` | Third-level menu item
|
|
376
|
-
| `NavConfig` |
|
|
377
|
-
| `ThemeConfig` | Individual theme settings (colors, blur, etc.)
|
|
378
|
-
| `DualThemeConfig` | Combined settings for light and dark modes
|
|
379
|
-
| `
|
|
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 |
|
|
381
392
|
|
|
382
393
|
## Browser Support
|
|
383
394
|
|
|
@@ -431,3 +442,64 @@ MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
|
431
442
|
## Support
|
|
432
443
|
|
|
433
444
|
If you find this package helpful, please consider giving it a star on GitHub!
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## Changelog
|
|
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
|
|
458
|
+
|
|
459
|
+
- **Native CSS Variable Theming (Better DX)**: Redesigned how default styles are injected into the DOM. `Header.astro` no longer injects all default properties into `style={...}` attributes on load, solving the "DOM bloat" issue. You can now deeply customize the component purely by setting CSS variables like `--l-bg`, `--d-bg`, `--l-accent`, and `--d-accent` inside your `:root` style tag, removing the necessity to parse massive `theme` JS objects.
|
|
460
|
+
- **Dead Code Elimination**: Cleaned up repetitive prop destructuring inside `MobileNav.astro`.
|
|
461
|
+
|
|
462
|
+
### v2.1 — Style & Customization Refactor
|
|
463
|
+
|
|
464
|
+
#### New features
|
|
465
|
+
|
|
466
|
+
- **`HeaderClassNames`** replaces `CustomClassNames` (alias kept — no migration required).
|
|
467
|
+
New `mobileNav` slot targets the mobile slide-in `<nav>` panel.
|
|
468
|
+
|
|
469
|
+
- **Nested class props** on `LogoConfig`:
|
|
470
|
+
| Prop | Targets |
|
|
471
|
+
|------|---------|
|
|
472
|
+
| `logo__container__class` | Logo `<a>` wrapper |
|
|
473
|
+
| `logo__text__class` | Logo text `<span>` |
|
|
474
|
+
|
|
475
|
+
- **Nested class props** on `NavConfig`:
|
|
476
|
+
| Prop | Targets |
|
|
477
|
+
|------|---------|
|
|
478
|
+
| `header__menu__class` | Desktop `<nav>` element |
|
|
479
|
+
| `header__item__class` | Each top-level `<li>` |
|
|
480
|
+
| `menu__link__class` | Each top-level `<a>` |
|
|
481
|
+
|
|
482
|
+
- **`defaultThemes` exported** — import and spread the built-in tokens to extend them:
|
|
483
|
+
```ts
|
|
484
|
+
import { defaultThemes } from "@sofidevo/astro-dynamic-header";
|
|
485
|
+
const theme = { dark: { ...defaultThemes.dark, accentColor: "#f43f5e" } };
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Internal improvements
|
|
489
|
+
|
|
490
|
+
- Default theme tokens extracted to `src/defaults.ts` — easier to read and maintain.
|
|
491
|
+
- `HamburgerButton` color is now explicitly wired from the resolved theme (no visual change).
|
|
492
|
+
|
|
493
|
+
#### Deprecations
|
|
494
|
+
|
|
495
|
+
- `CustomClassNames` — use `HeaderClassNames` instead. The alias will remain until the next major version.
|
|
496
|
+
|
|
497
|
+
### v2.0 — Object-based Configuration API
|
|
498
|
+
|
|
499
|
+
> [!WARNING]
|
|
500
|
+
> Breaking change: `logo` changed from `string` to `LogoConfig`, `navigation` changed from `MenuItem[]` to `NavConfig`.
|
|
501
|
+
|
|
502
|
+
- `logo` prop restructured to `LogoConfig` object (`src`, `alt`, `width`, `text`, `textSize`, `textColor`).
|
|
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`).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofidevo/astro-dynamic-header",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.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",
|
|
@@ -53,10 +53,10 @@
|
|
|
53
53
|
"astro": "^6.0.6"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@astrojs/check": "^0.9.
|
|
56
|
+
"@astrojs/check": "^0.9.9",
|
|
57
57
|
"@types/jsdom": "^21.1.7",
|
|
58
58
|
"@vitest/coverage-v8": "^3.2.4",
|
|
59
|
-
"astro": "^6.
|
|
59
|
+
"astro": "^6.3.7",
|
|
60
60
|
"jsdom": "^26.1.0",
|
|
61
61
|
"typescript": "^5.0.0",
|
|
62
62
|
"vitest": "^3.2.4"
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
export interface Props {
|
|
3
|
-
/** Override the default color of the hamburger lines */
|
|
4
|
-
color?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const { color = "var(--text-color, var(--color-hamburger-lines, #fff))" } =
|
|
8
|
-
Astro.props;
|
|
2
|
+
export interface Props {}
|
|
9
3
|
---
|
|
10
4
|
|
|
11
5
|
<button
|
|
12
6
|
class="hamburger hamburger--collapse"
|
|
13
7
|
type="button"
|
|
14
8
|
id="hamburger-btn"
|
|
15
|
-
style={`--hamburger-color: ${color};`}
|
|
16
9
|
>
|
|
17
10
|
<span class="hamburger-box">
|
|
18
11
|
<span class="hamburger-inner"></span>
|
|
@@ -65,10 +58,7 @@ const { color = "var(--text-color, var(--color-hamburger-lines, #fff))" } =
|
|
|
65
58
|
.hamburger-inner,
|
|
66
59
|
.hamburger-inner::before,
|
|
67
60
|
.hamburger-inner::after {
|
|
68
|
-
background-color: var(
|
|
69
|
-
--hamburger-color,
|
|
70
|
-
var(--color-hamburger-lines, #fff)
|
|
71
|
-
);
|
|
61
|
+
background-color: var(--text-color, inherit);
|
|
72
62
|
position: absolute;
|
|
73
63
|
width: 40px;
|
|
74
64
|
height: 4px;
|
package/src/Header.astro
CHANGED
|
@@ -4,21 +4,20 @@ import NavMenu from "./NavMenu.astro";
|
|
|
4
4
|
import MobileNav from "./MobileNav.astro";
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
|
-
LogoConfig,
|
|
8
7
|
NavConfig,
|
|
9
8
|
DualThemeConfig,
|
|
10
|
-
|
|
9
|
+
HeaderClassNames,
|
|
11
10
|
} from "./index.js";
|
|
12
11
|
|
|
13
12
|
export interface Props {
|
|
14
|
-
/**
|
|
13
|
+
/**
|
|
15
14
|
* Layout style.
|
|
16
15
|
* - "floating": Centered with max-width and rounded corners.
|
|
17
16
|
* - "fullscreen": Full width with no border radius.
|
|
18
17
|
* @default "floating"
|
|
19
18
|
*/
|
|
20
19
|
headerType?: "floating" | "fullscreen";
|
|
21
|
-
/**
|
|
20
|
+
/**
|
|
22
21
|
* Theme behavior.
|
|
23
22
|
* - "light": Force light mode.
|
|
24
23
|
* - "dark": Force dark mode.
|
|
@@ -26,35 +25,31 @@ export interface Props {
|
|
|
26
25
|
* @default "auto"
|
|
27
26
|
*/
|
|
28
27
|
preset?: "light" | "dark" | "auto";
|
|
29
|
-
|
|
30
|
-
logo?: LogoConfig;
|
|
28
|
+
|
|
31
29
|
/** Navigation links and structure. */
|
|
32
30
|
navigation?: NavConfig;
|
|
33
31
|
/** Custom theme overrides for light and dark modes. */
|
|
34
32
|
theme?: DualThemeConfig;
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
/**
|
|
34
|
+
* High-level CSS class overrides for structural wrapper elements.
|
|
35
|
+
* For fine-grained nav/logo element classes, use the nested `xxx__class`
|
|
36
|
+
* props inside `navigation` or `logo` instead.
|
|
37
|
+
* @example { header: "shadow-lg", container: "top-4" }
|
|
38
38
|
*/
|
|
39
|
-
classNames?:
|
|
39
|
+
classNames?: HeaderClassNames;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const {
|
|
43
43
|
headerType = "floating",
|
|
44
44
|
preset = "auto",
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
navigation = {},
|
|
47
47
|
theme = {},
|
|
48
48
|
classNames = {},
|
|
49
49
|
} = Astro.props;
|
|
50
50
|
|
|
51
51
|
if (import.meta.env.DEV) {
|
|
52
|
-
|
|
53
|
-
if (typeof logo === "string") {
|
|
54
|
-
console.warn(
|
|
55
|
-
"[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'logo' prop now expects an object. Please use logo={{ src: '...' }} instead.",
|
|
56
|
-
);
|
|
57
|
-
}
|
|
52
|
+
|
|
58
53
|
if (Array.isArray(navigation)) {
|
|
59
54
|
console.warn(
|
|
60
55
|
"[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'navigation' prop now expects an object with 'menuItems'. Please use navigation={{ menuItems: [...] }} instead.",
|
|
@@ -62,42 +57,36 @@ if (import.meta.env.DEV) {
|
|
|
62
57
|
}
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
//
|
|
66
|
-
const
|
|
67
|
-
light: {
|
|
68
|
-
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
69
|
-
backgroundColorOpaque: "rgb(255, 255, 255)",
|
|
70
|
-
backdropBlur: "blur(20px)",
|
|
71
|
-
zIndex: 10,
|
|
72
|
-
textColor: "#1a1a1a",
|
|
73
|
-
accentColor: "#3e1c71",
|
|
74
|
-
},
|
|
75
|
-
dark: {
|
|
76
|
-
backgroundColor: "#0d0d0dcc",
|
|
77
|
-
backgroundColorOpaque: "#0d0d0d",
|
|
78
|
-
backdropBlur: "blur(20px)",
|
|
79
|
-
zIndex: 10,
|
|
80
|
-
textColor: "#ffffff",
|
|
81
|
-
accentColor: "#00ffff",
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Merge with user overrides
|
|
86
|
-
const lightTheme = { ...defaultThemes.light, ...theme.light };
|
|
87
|
-
const darkTheme = { ...defaultThemes.dark, ...theme.dark };
|
|
60
|
+
// Map user theme overrides to CSS custom properties
|
|
61
|
+
const inlineStyles: Record<string, string> = {};
|
|
88
62
|
|
|
89
|
-
|
|
90
|
-
|
|
63
|
+
if (theme.light) {
|
|
64
|
+
if (theme.light.backgroundColor) inlineStyles["--l-bg"] = theme.light.backgroundColor;
|
|
65
|
+
if (theme.light.backgroundColorOpaque) inlineStyles["--l-bg-opaque"] = theme.light.backgroundColorOpaque;
|
|
66
|
+
if (theme.light.textColor) inlineStyles["--l-text"] = theme.light.textColor;
|
|
67
|
+
if (theme.light.accentColor) inlineStyles["--l-accent"] = theme.light.accentColor;
|
|
68
|
+
if (theme.light.backdropBlur) inlineStyles["--l-blur"] = theme.light.backdropBlur;
|
|
69
|
+
}
|
|
70
|
+
if (theme.dark) {
|
|
71
|
+
if (theme.dark.backgroundColor) inlineStyles["--d-bg"] = theme.dark.backgroundColor;
|
|
72
|
+
if (theme.dark.backgroundColorOpaque) inlineStyles["--d-bg-opaque"] = theme.dark.backgroundColorOpaque;
|
|
73
|
+
if (theme.dark.textColor) inlineStyles["--d-text"] = theme.dark.textColor;
|
|
74
|
+
if (theme.dark.accentColor) inlineStyles["--d-accent"] = theme.dark.accentColor;
|
|
75
|
+
if (theme.dark.backdropBlur) inlineStyles["--d-blur"] = theme.dark.backdropBlur;
|
|
76
|
+
}
|
|
91
77
|
|
|
92
|
-
|
|
78
|
+
const zIndex = theme.light?.zIndex ?? 10;
|
|
79
|
+
|
|
80
|
+
// Navigation configuration
|
|
93
81
|
const {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
homeUrl = "/",
|
|
83
|
+
menuItems = [],
|
|
84
|
+
header__menu__class,
|
|
85
|
+
header__item__class,
|
|
86
|
+
menu__link__class,
|
|
87
|
+
} = navigation;
|
|
88
|
+
|
|
89
|
+
|
|
101
90
|
|
|
102
91
|
const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
103
92
|
---
|
|
@@ -150,7 +139,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
150
139
|
`header__container--${headerType}`,
|
|
151
140
|
classNames.container,
|
|
152
141
|
]}
|
|
153
|
-
style={{ zIndex
|
|
142
|
+
style={{ zIndex }}
|
|
154
143
|
>
|
|
155
144
|
<header
|
|
156
145
|
class:list={[
|
|
@@ -159,40 +148,17 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
159
148
|
forcedClass,
|
|
160
149
|
classNames.header,
|
|
161
150
|
]}
|
|
162
|
-
style={
|
|
163
|
-
"--l-bg": lightTheme.backgroundColor,
|
|
164
|
-
"--l-bg-opaque": lightTheme.backgroundColorOpaque,
|
|
165
|
-
"--l-text": lightTheme.textColor,
|
|
166
|
-
"--l-accent": lightTheme.accentColor,
|
|
167
|
-
"--l-blur": lightTheme.backdropBlur,
|
|
168
|
-
"--d-bg": darkTheme.backgroundColor,
|
|
169
|
-
"--d-bg-opaque": darkTheme.backgroundColorOpaque,
|
|
170
|
-
"--d-text": darkTheme.textColor,
|
|
171
|
-
"--d-accent": darkTheme.accentColor,
|
|
172
|
-
"--d-blur": darkTheme.backdropBlur,
|
|
173
|
-
}}
|
|
151
|
+
style={Object.keys(inlineStyles).length > 0 ? inlineStyles : undefined}
|
|
174
152
|
>
|
|
175
|
-
<
|
|
176
|
-
<img
|
|
177
|
-
class="header__logo"
|
|
178
|
-
src={logoSrc}
|
|
179
|
-
alt={logoAlt}
|
|
180
|
-
style={{ width: logoWidth }}
|
|
181
|
-
/>
|
|
182
|
-
{
|
|
183
|
-
logoText && (
|
|
184
|
-
<span
|
|
185
|
-
class:list={["header__logo-text", classNames.logoText]}
|
|
186
|
-
style={{ color: logoTextColor, fontSize: logoTextSize }}
|
|
187
|
-
>
|
|
188
|
-
{logoText}
|
|
189
|
-
</span>
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
</a>
|
|
153
|
+
<slot name="logo" />
|
|
193
154
|
|
|
194
155
|
<div class:list={["nav-menu-wrapper", classNames.nav]}>
|
|
195
|
-
<NavMenu
|
|
156
|
+
<NavMenu
|
|
157
|
+
menuItems={menuItems}
|
|
158
|
+
header__menu__class={header__menu__class}
|
|
159
|
+
header__item__class={header__item__class}
|
|
160
|
+
menu__link__class={menu__link__class}
|
|
161
|
+
/>
|
|
196
162
|
</div>
|
|
197
163
|
|
|
198
164
|
{
|
|
@@ -205,7 +171,11 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
205
171
|
|
|
206
172
|
<HamburgerButton />
|
|
207
173
|
|
|
208
|
-
<MobileNav
|
|
174
|
+
<MobileNav
|
|
175
|
+
menuItems={menuItems}
|
|
176
|
+
type={headerType}
|
|
177
|
+
mobileNav__class={classNames.mobileNav}
|
|
178
|
+
>
|
|
209
179
|
{
|
|
210
180
|
Astro.slots.has("actions") && (
|
|
211
181
|
<div class="actions-mobile" slot="slot-panel">
|
|
@@ -219,11 +189,11 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
219
189
|
|
|
220
190
|
<style is:inline>
|
|
221
191
|
.header {
|
|
222
|
-
--bg-color: var(--l-bg);
|
|
223
|
-
--bg-color-opaque: var(--l-bg-opaque);
|
|
224
|
-
--text-color: var(--l-text);
|
|
225
|
-
--accent-color: var(--l-accent);
|
|
226
|
-
--backdrop-blur: var(--l-blur);
|
|
192
|
+
--bg-color: var(--l-bg, rgba(255, 255, 255, 0.9));
|
|
193
|
+
--bg-color-opaque: var(--l-bg-opaque, rgb(255, 255, 255));
|
|
194
|
+
--text-color: var(--l-text, #1a1a1a);
|
|
195
|
+
--accent-color: var(--l-accent, #3e1c71);
|
|
196
|
+
--backdrop-blur: var(--l-blur, blur(20px));
|
|
227
197
|
|
|
228
198
|
display: flex;
|
|
229
199
|
align-items: center;
|
|
@@ -247,28 +217,28 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
247
217
|
|
|
248
218
|
/* Automatic dark mode detection via root class */
|
|
249
219
|
:root.dark .header:not(.header--force-light) {
|
|
250
|
-
--bg-color: var(--d-bg);
|
|
251
|
-
--bg-color-opaque: var(--d-bg-opaque);
|
|
252
|
-
--text-color: var(--d-text);
|
|
253
|
-
--accent-color: var(--d-accent);
|
|
254
|
-
--backdrop-blur: var(--d-blur);
|
|
220
|
+
--bg-color: var(--d-bg, #0d0d0dcc);
|
|
221
|
+
--bg-color-opaque: var(--d-bg-opaque, #0d0d0d);
|
|
222
|
+
--text-color: var(--d-text, #ffffff);
|
|
223
|
+
--accent-color: var(--d-accent, #00ffff);
|
|
224
|
+
--backdrop-blur: var(--d-blur, blur(20px));
|
|
255
225
|
}
|
|
256
226
|
|
|
257
227
|
/* Forced themes */
|
|
258
228
|
.header--force-dark {
|
|
259
|
-
--bg-color: var(--d-bg) !important;
|
|
260
|
-
--bg-color-opaque: var(--d-bg-opaque) !important;
|
|
261
|
-
--text-color: var(--d-text) !important;
|
|
262
|
-
--accent-color: var(--d-accent) !important;
|
|
263
|
-
--backdrop-blur: var(--d-blur) !important;
|
|
229
|
+
--bg-color: var(--d-bg, #0d0d0dcc) !important;
|
|
230
|
+
--bg-color-opaque: var(--d-bg-opaque, #0d0d0d) !important;
|
|
231
|
+
--text-color: var(--d-text, #ffffff) !important;
|
|
232
|
+
--accent-color: var(--d-accent, #00ffff) !important;
|
|
233
|
+
--backdrop-blur: var(--d-blur, blur(20px)) !important;
|
|
264
234
|
}
|
|
265
235
|
|
|
266
236
|
.header--force-light {
|
|
267
|
-
--bg-color: var(--l-bg) !important;
|
|
268
|
-
--bg-color-opaque: var(--l-bg-opaque) !important;
|
|
269
|
-
--text-color: var(--l-text) !important;
|
|
270
|
-
--accent-color: var(--l-accent) !important;
|
|
271
|
-
--backdrop-blur: var(--l-blur) !important;
|
|
237
|
+
--bg-color: var(--l-bg, rgba(255, 255, 255, 0.9)) !important;
|
|
238
|
+
--bg-color-opaque: var(--l-bg-opaque, rgb(255, 255, 255)) !important;
|
|
239
|
+
--text-color: var(--l-text, #1a1a1a) !important;
|
|
240
|
+
--accent-color: var(--l-accent, #3e1c71) !important;
|
|
241
|
+
--backdrop-blur: var(--l-blur, blur(20px)) !important;
|
|
272
242
|
}
|
|
273
243
|
|
|
274
244
|
.header__container {
|
|
@@ -313,20 +283,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
313
283
|
}
|
|
314
284
|
}
|
|
315
285
|
|
|
316
|
-
.logo__container {
|
|
317
|
-
display: flex;
|
|
318
|
-
align-items: center;
|
|
319
|
-
text-decoration: none;
|
|
320
|
-
color: inherit;
|
|
321
|
-
& span {
|
|
322
|
-
font-weight: 600;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
286
|
|
|
326
|
-
.header__logo {
|
|
327
|
-
margin-right: 1em;
|
|
328
|
-
object-fit: contain;
|
|
329
|
-
}
|
|
330
287
|
|
|
331
288
|
.actions-desktop {
|
|
332
289
|
@media (width < 768px) {
|
package/src/MobileNav.astro
CHANGED
|
@@ -10,21 +10,26 @@ export interface Props {
|
|
|
10
10
|
showHomeLink?: boolean;
|
|
11
11
|
/** Text for the home link (defaults to "Home") */
|
|
12
12
|
homeText?: string;
|
|
13
|
+
/** Extra CSS class(es) for the root mobile `<nav>` panel. */
|
|
14
|
+
mobileNav__class?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
const { type } = Astro.props;
|
|
16
|
-
|
|
17
17
|
const {
|
|
18
|
+
type,
|
|
19
|
+
mobileNav__class,
|
|
18
20
|
menuItems = [],
|
|
19
21
|
showHomeLink = true,
|
|
20
|
-
homeText = "Home"
|
|
22
|
+
homeText = "Home"
|
|
21
23
|
} = Astro.props;
|
|
22
24
|
|
|
23
25
|
const pagePathname = Astro.url.pathname;
|
|
24
26
|
---
|
|
25
27
|
|
|
26
28
|
<nav
|
|
27
|
-
class={
|
|
29
|
+
class:list={[
|
|
30
|
+
`mobile-header__menu mobile-header__menu--${type}`,
|
|
31
|
+
mobileNav__class,
|
|
32
|
+
]}
|
|
28
33
|
id="mobile-header-menu"
|
|
29
34
|
>
|
|
30
35
|
<ol class="mobile-menu">
|
|
@@ -47,7 +52,10 @@ const pagePathname = Astro.url.pathname;
|
|
|
47
52
|
return (
|
|
48
53
|
<li>
|
|
49
54
|
{item.submenu ? (
|
|
50
|
-
<details
|
|
55
|
+
<details
|
|
56
|
+
class="mobile-details"
|
|
57
|
+
open={item.submenu.some((sub) => pagePathname === sub.link)}
|
|
58
|
+
>
|
|
51
59
|
<summary class:list={["menu__summary", { active: isActive }]}>
|
|
52
60
|
{item.text}
|
|
53
61
|
<iconify-icon
|
|
@@ -64,8 +72,18 @@ const pagePathname = Astro.url.pathname;
|
|
|
64
72
|
return (
|
|
65
73
|
<li>
|
|
66
74
|
{sub.submenu && sub.submenu.length > 0 ? (
|
|
67
|
-
<details
|
|
68
|
-
|
|
75
|
+
<details
|
|
76
|
+
class="mobile-details"
|
|
77
|
+
open={sub.submenu.some(
|
|
78
|
+
(subsub) => pagePathname === subsub.link,
|
|
79
|
+
)}
|
|
80
|
+
>
|
|
81
|
+
<summary
|
|
82
|
+
class:list={[
|
|
83
|
+
"menu__summary",
|
|
84
|
+
{ active: isSubActive },
|
|
85
|
+
]}
|
|
86
|
+
>
|
|
69
87
|
{sub.text}
|
|
70
88
|
<iconify-icon
|
|
71
89
|
class="iconify-arrow"
|
|
@@ -77,11 +95,16 @@ const pagePathname = Astro.url.pathname;
|
|
|
77
95
|
</summary>
|
|
78
96
|
<ol class="mobile-subsubmenu">
|
|
79
97
|
{sub.submenu.map((subsub: TertiaryMenuItem) => {
|
|
80
|
-
const isSubSubActive =
|
|
98
|
+
const isSubSubActive =
|
|
99
|
+
pagePathname === subsub.link;
|
|
81
100
|
return (
|
|
82
101
|
<li class="mobile-submenu__item secondary">
|
|
83
102
|
<a
|
|
84
|
-
class:list={[
|
|
103
|
+
class:list={[
|
|
104
|
+
"menu__link",
|
|
105
|
+
"mobile-menu__link",
|
|
106
|
+
{ active: isSubSubActive },
|
|
107
|
+
]}
|
|
85
108
|
href={subsub.link}
|
|
86
109
|
>
|
|
87
110
|
{subsub.text}
|
|
@@ -92,7 +115,14 @@ const pagePathname = Astro.url.pathname;
|
|
|
92
115
|
</ol>
|
|
93
116
|
</details>
|
|
94
117
|
) : (
|
|
95
|
-
<a
|
|
118
|
+
<a
|
|
119
|
+
class:list={[
|
|
120
|
+
"menu__link",
|
|
121
|
+
"mobile-menu__link",
|
|
122
|
+
{ active: isSubActive },
|
|
123
|
+
]}
|
|
124
|
+
href={sub.link}
|
|
125
|
+
>
|
|
96
126
|
{sub.text}
|
|
97
127
|
</a>
|
|
98
128
|
)}
|
|
@@ -102,7 +132,10 @@ const pagePathname = Astro.url.pathname;
|
|
|
102
132
|
</ol>
|
|
103
133
|
</details>
|
|
104
134
|
) : (
|
|
105
|
-
<a
|
|
135
|
+
<a
|
|
136
|
+
class:list={["mobile-menu__link", { active: isActive }]}
|
|
137
|
+
href={item.link}
|
|
138
|
+
>
|
|
106
139
|
{item.text}
|
|
107
140
|
</a>
|
|
108
141
|
)}
|
|
@@ -128,7 +161,9 @@ const pagePathname = Astro.url.pathname;
|
|
|
128
161
|
left: 0;
|
|
129
162
|
background: var(--bg-color-opaque, #151515);
|
|
130
163
|
z-index: 20;
|
|
131
|
-
transition:
|
|
164
|
+
transition:
|
|
165
|
+
transform 0.3s ease-in-out,
|
|
166
|
+
background-color 0.3s ease;
|
|
132
167
|
transform: translateX(100%);
|
|
133
168
|
padding: 10rem 2rem 2rem;
|
|
134
169
|
color: var(--text-color, #fff);
|
package/src/NavMenu.astro
CHANGED
|
@@ -18,11 +18,23 @@ export interface Props {
|
|
|
18
18
|
* Text for the home link.
|
|
19
19
|
* @default "Home"
|
|
20
20
|
*/
|
|
21
|
-
|
|
22
21
|
homeText?: string;
|
|
22
|
+
/** Extra CSS class(es) for the `<nav>` element. */
|
|
23
|
+
header__menu__class?: string;
|
|
24
|
+
/** Extra CSS class(es) for each top-level `<li>` item. */
|
|
25
|
+
header__item__class?: string;
|
|
26
|
+
/** Extra CSS class(es) for each top-level menu `<a>` link. */
|
|
27
|
+
menu__link__class?: string;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
menuItems = [],
|
|
32
|
+
showHomeLink = true,
|
|
33
|
+
homeText = "Home",
|
|
34
|
+
header__menu__class,
|
|
35
|
+
header__item__class,
|
|
36
|
+
menu__link__class,
|
|
37
|
+
} = Astro.props;
|
|
26
38
|
|
|
27
39
|
const pagePathname = Astro.url.pathname;
|
|
28
40
|
---
|
|
@@ -129,12 +141,16 @@ const pagePathname = Astro.url.pathname;
|
|
|
129
141
|
});
|
|
130
142
|
</script>
|
|
131
143
|
|
|
132
|
-
<nav class="header__menu" id="header-menu">
|
|
144
|
+
<nav class:list={["header__menu", header__menu__class]} id="header-menu">
|
|
133
145
|
<ul class="menu">
|
|
134
146
|
{
|
|
135
147
|
showHomeLink && pagePathname !== "/" && (
|
|
136
|
-
<li class="header__item">
|
|
137
|
-
<a
|
|
148
|
+
<li class:list={["header__item", header__item__class]}>
|
|
149
|
+
<a
|
|
150
|
+
class:list={["menu__link", menu__link__class]}
|
|
151
|
+
href="/"
|
|
152
|
+
data-astro-prefetch="hover"
|
|
153
|
+
>
|
|
138
154
|
{homeText}
|
|
139
155
|
</a>
|
|
140
156
|
</li>
|
|
@@ -142,8 +158,8 @@ const pagePathname = Astro.url.pathname;
|
|
|
142
158
|
}
|
|
143
159
|
{
|
|
144
160
|
menuItems.map((item: MenuItem) => (
|
|
145
|
-
<li class="menu__item">
|
|
146
|
-
<a class="menu__link" href={item.link}>
|
|
161
|
+
<li class:list={["menu__item", header__item__class]}>
|
|
162
|
+
<a class:list={["menu__link", menu__link__class]} href={item.link}>
|
|
147
163
|
{item.text}
|
|
148
164
|
{item.submenu && (
|
|
149
165
|
<iconify-icon
|
package/src/defaults.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DualThemeConfig } from "./index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Built-in default theme tokens used by the Header component.
|
|
5
|
+
* These are merged with any user-supplied `theme` overrides.
|
|
6
|
+
*
|
|
7
|
+
* You can import this object if you want to build on top of the defaults
|
|
8
|
+
* rather than replacing them wholesale:
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { defaultThemes } from '@sofidevo/astro-dynamic-header/defaults';
|
|
13
|
+
* const theme = {
|
|
14
|
+
* light: { ...defaultThemes.light, accentColor: "#e11d48" },
|
|
15
|
+
* dark: { ...defaultThemes.dark, accentColor: "#f43f5e" },
|
|
16
|
+
* };
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export const defaultThemes: Required<DualThemeConfig> = {
|
|
20
|
+
light: {
|
|
21
|
+
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
|
22
|
+
backgroundColorOpaque: "rgb(255, 255, 255)",
|
|
23
|
+
backdropBlur: "blur(20px)",
|
|
24
|
+
zIndex: 10,
|
|
25
|
+
textColor: "#1a1a1a",
|
|
26
|
+
accentColor: "#3e1c71",
|
|
27
|
+
},
|
|
28
|
+
dark: {
|
|
29
|
+
backgroundColor: "#0d0d0dcc",
|
|
30
|
+
backgroundColorOpaque: "#0d0d0d",
|
|
31
|
+
backdropBlur: "blur(20px)",
|
|
32
|
+
zIndex: 10,
|
|
33
|
+
textColor: "#ffffff",
|
|
34
|
+
accentColor: "#00ffff",
|
|
35
|
+
},
|
|
36
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
export { defaultThemes } from "./defaults.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Represents a menu item in the navigation.
|
|
@@ -46,87 +46,70 @@ export interface MenuItem {
|
|
|
46
46
|
submenu?: SecondaryMenuItem[];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
/**
|
|
50
|
-
* Configuration for the site logo.
|
|
51
|
-
*/
|
|
52
|
-
export interface LogoConfig {
|
|
53
|
-
/**
|
|
54
|
-
* The URL of the logo image.
|
|
55
|
-
* @example "/assets/logo.png"
|
|
56
|
-
* @default "/logo.png"
|
|
57
|
-
*/
|
|
58
|
-
src?: string;
|
|
59
|
-
/** Alternative text for the logo image */
|
|
60
|
-
alt?: string;
|
|
61
|
-
/**
|
|
62
|
-
* CSS width for the logo.
|
|
63
|
-
* @example "150px" or "4rem"
|
|
64
|
-
* @default "55px"
|
|
65
|
-
*/
|
|
66
|
-
width?: string;
|
|
67
|
-
/**
|
|
68
|
-
* Optional text to display next to the logo image.
|
|
69
|
-
* @example "My Awesome Site"
|
|
70
|
-
*/
|
|
71
|
-
text?: string;
|
|
72
|
-
/**
|
|
73
|
-
* CSS font-size for the logo text.
|
|
74
|
-
* @default "1em"
|
|
75
|
-
*/
|
|
76
|
-
textSize?: string;
|
|
77
|
-
/**
|
|
78
|
-
* CSS color for the logo text.
|
|
79
|
-
* If not provided, it will inherit from the theme's text color.
|
|
80
|
-
* @default "inherit"
|
|
81
|
-
*/
|
|
82
|
-
textColor?: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
49
|
/**
|
|
86
50
|
* Configuration for the main navigation.
|
|
87
51
|
*/
|
|
88
52
|
export interface NavConfig {
|
|
89
|
-
/**
|
|
53
|
+
/**
|
|
90
54
|
* The URL for the home link.
|
|
91
55
|
* @default "/"
|
|
92
56
|
*/
|
|
93
57
|
homeUrl?: string;
|
|
94
|
-
/**
|
|
58
|
+
/**
|
|
95
59
|
* Array of top-level menu items.
|
|
96
60
|
* @example [{ link: "/about", text: "About Us" }]
|
|
97
61
|
*/
|
|
98
62
|
menuItems?: MenuItem[];
|
|
63
|
+
/**
|
|
64
|
+
* Fine-grained class override for the desktop `<nav>` element.
|
|
65
|
+
* Use this when you want the class to live alongside the rest of the
|
|
66
|
+
* navigation configuration rather than in the top-level `classNames` prop.
|
|
67
|
+
* @example "flex gap-4"
|
|
68
|
+
*/
|
|
69
|
+
header__menu__class?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Fine-grained class override applied to every top-level `<li>` item
|
|
72
|
+
* in the desktop navigation.
|
|
73
|
+
* @example "px-2 py-1"
|
|
74
|
+
*/
|
|
75
|
+
header__item__class?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Fine-grained class override applied to every top-level `<a>` link
|
|
78
|
+
* in the desktop navigation.
|
|
79
|
+
* @example "hover:underline font-medium"
|
|
80
|
+
*/
|
|
81
|
+
menu__link__class?: string;
|
|
99
82
|
}
|
|
100
83
|
|
|
101
84
|
/**
|
|
102
85
|
* Individual theme settings for a specific state (light/dark).
|
|
103
86
|
*/
|
|
104
87
|
export interface ThemeConfig {
|
|
105
|
-
/**
|
|
88
|
+
/**
|
|
106
89
|
* Main background color. Supports hex, rgb, rgba, etc.
|
|
107
90
|
* @example "rgba(255, 255, 255, 0.9)"
|
|
108
91
|
*/
|
|
109
92
|
backgroundColor?: string;
|
|
110
|
-
/**
|
|
93
|
+
/**
|
|
111
94
|
* Solid background color for submenus and mobile panels to ensure readability.
|
|
112
95
|
* @example "#ffffff"
|
|
113
96
|
*/
|
|
114
97
|
backgroundColorOpaque?: string;
|
|
115
|
-
/**
|
|
98
|
+
/**
|
|
116
99
|
* CSS backdrop-filter blur value.
|
|
117
100
|
* @default "blur(20px)"
|
|
118
101
|
*/
|
|
119
102
|
backdropBlur?: string;
|
|
120
|
-
/**
|
|
103
|
+
/**
|
|
121
104
|
* CSS z-index for the header container.
|
|
122
105
|
* @default 10
|
|
123
106
|
*/
|
|
124
107
|
zIndex?: number;
|
|
125
|
-
/**
|
|
108
|
+
/**
|
|
126
109
|
* Primary text color for navigation and logo.
|
|
127
110
|
*/
|
|
128
111
|
textColor?: string;
|
|
129
|
-
/**
|
|
112
|
+
/**
|
|
130
113
|
* Color for highlights, active states, underscores, and small borders.
|
|
131
114
|
*/
|
|
132
115
|
accentColor?: string;
|
|
@@ -143,33 +126,50 @@ export interface DualThemeConfig {
|
|
|
143
126
|
}
|
|
144
127
|
|
|
145
128
|
/**
|
|
146
|
-
* Custom CSS class names for
|
|
129
|
+
* Custom CSS class names for high-level layout & appearance customization.
|
|
130
|
+
*
|
|
131
|
+
* These target the structural wrapper elements of the Header. For fine-grained
|
|
132
|
+
* control over individual nav links or the logo internals, use the nested
|
|
133
|
+
* `xxx__class` props inside the `navigation` or `logo` config objects instead.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```astro
|
|
137
|
+
* <Header classNames={{ header: "shadow-xl", container: "top-4 px-6" }} />
|
|
138
|
+
* ```
|
|
147
139
|
*/
|
|
148
|
-
export interface
|
|
149
|
-
/**
|
|
140
|
+
export interface HeaderClassNames {
|
|
141
|
+
/** Outermost fixed `<div>` that positions the header on the page. */
|
|
150
142
|
container?: string;
|
|
151
|
-
/**
|
|
143
|
+
/** Inner `<header>` element — best place for shadows, borders, transitions. */
|
|
152
144
|
header?: string;
|
|
153
|
-
/**
|
|
145
|
+
/** Logo anchor `<a>` — add hover states or focus rings here. */
|
|
154
146
|
logo?: string;
|
|
155
|
-
/**
|
|
147
|
+
/** Logo text `<span>` — override typography here. */
|
|
156
148
|
logoText?: string;
|
|
157
|
-
/**
|
|
149
|
+
/** Desktop nav wrapper `<div>` — adjust spacing between logo and menu. */
|
|
158
150
|
nav?: string;
|
|
151
|
+
/** Mobile nav panel `<nav>` — add slide-in overrides or z-index tweaks. */
|
|
152
|
+
mobileNav?: string;
|
|
159
153
|
}
|
|
160
154
|
|
|
155
|
+
/**
|
|
156
|
+
* @deprecated Use {@link HeaderClassNames} instead.
|
|
157
|
+
* Kept as an alias for backwards compatibility.
|
|
158
|
+
*/
|
|
159
|
+
export type CustomClassNames = HeaderClassNames;
|
|
160
|
+
|
|
161
161
|
/**
|
|
162
162
|
* Main properties for the Header component.
|
|
163
163
|
*/
|
|
164
164
|
export interface HeaderProps {
|
|
165
|
-
/**
|
|
165
|
+
/**
|
|
166
166
|
* Layout style.
|
|
167
167
|
* - "floating": Centered with max-width and rounded corners.
|
|
168
168
|
* - "fullscreen": Full width with no border radius.
|
|
169
169
|
* @default "floating"
|
|
170
170
|
*/
|
|
171
171
|
headerType?: "floating" | "fullscreen";
|
|
172
|
-
/**
|
|
172
|
+
/**
|
|
173
173
|
* Theme behavior.
|
|
174
174
|
* - "light": Force light mode.
|
|
175
175
|
* - "dark": Force dark mode.
|
|
@@ -177,17 +177,18 @@ export interface HeaderProps {
|
|
|
177
177
|
* @default "auto"
|
|
178
178
|
*/
|
|
179
179
|
preset?: "light" | "dark" | "auto";
|
|
180
|
-
|
|
181
|
-
logo?: LogoConfig;
|
|
180
|
+
|
|
182
181
|
/** Navigation links and structure. */
|
|
183
182
|
navigation?: NavConfig;
|
|
184
|
-
/** Custom theme overrides. See @
|
|
183
|
+
/** Custom theme overrides. See {@link DualThemeConfig} */
|
|
185
184
|
theme?: DualThemeConfig;
|
|
186
|
-
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
185
|
+
/**
|
|
186
|
+
* High-level CSS class overrides for structural wrapper elements.
|
|
187
|
+
* For fine-grained nav/logo element classes, use the nested `xxx__class`
|
|
188
|
+
* props inside `navigation` or `logo` instead.
|
|
189
|
+
* @example { header: "shadow-lg", container: "top-4" }
|
|
189
190
|
*/
|
|
190
|
-
classNames?:
|
|
191
|
+
classNames?: HeaderClassNames;
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
export interface NavMenuProps {
|