@sofidevo/astro-dynamic-header 2.0.1 → 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 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**: 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.
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
- - **Customizable**: Extensive customization options for colors, sizes, and behavior
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
- const = menuItems: [
50
- { link: '/about', text: 'About' },
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
- | `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 |
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,33 +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.
146
+
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.
148
+
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. |
157
+
158
+ ##### Usage Examples
159
+
160
+ **Premium Shadow & Border:**
161
+
162
+ ```astro
163
+ <Header
164
+ classNames={{
165
+ header: "shadow-xl border-b border-black/5 dark:border-white/10 transition-all duration-500",
166
+ container: "top-4 px-6",
167
+ mobileNav: "backdrop-blur-md",
168
+ }}
169
+ />
170
+ ```
171
+
172
+ **Custom Typography & Nav Spacing:**
173
+
174
+ ```astro
175
+ <Header
176
+ classNames={{
177
+ logoText: "tracking-tighter font-black italic uppercase",
178
+ nav: "ml-auto gap-8",
179
+ }}
180
+ />
181
+ ```
139
182
 
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 |
183
+ > [!TIP]
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.
147
185
 
148
- #### LogoConfig
149
186
 
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 |
158
187
 
159
188
  #### NavConfig
160
189
 
161
- | Property | Type | Description |
162
- | ----------- | ------------ | --------------------------------------- |
163
- | `homeUrl` | `string` | URL for the home link (defaults to `/`) |
164
- | `menuItems` | `MenuItem[]` | Array of navigation menu items |
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 |
165
197
 
166
198
  #### MenuItem
167
199
 
@@ -179,6 +211,7 @@ The Header component provides a flexible slot system that allows you to add addi
179
211
 
180
212
  | Slot Name | Location | Visibility | Description |
181
213
  | --------- | --------------------- | --------------------- | ---------------------------------------- |
214
+ | `logo` | Header Desktop/Mobile | Always | Add your custom logo HTML/Components |
182
215
  | `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.) |
183
216
 
184
217
  ### Example with Slots
@@ -229,32 +262,38 @@ const menuItems = [
229
262
  { link: "/contact", text: "Contact" },
230
263
  ];
231
264
 
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
- };
265
+ ];
242
266
  ---
243
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
+
244
279
  <Header
245
280
  headerType="floating"
246
281
  preset="dark"
247
- logo={{
248
- src: "https://itssofi.dev/img/icons/sofi-icon.webp",
249
- alt: "My Site Logo",
250
- width: "44px",
251
- }}
252
282
  navigation={{
253
283
  homeUrl: "/",
254
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",
255
291
  }}
256
- theme={theme}
257
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>
258
297
  <button slot="actions">Login</button>
259
298
  </Header>
260
299
  ```
@@ -294,9 +333,11 @@ The package provides full TypeScript support. You can import types to ensure you
294
333
  ```astro
295
334
  ---
296
335
  import Header from '@sofidevo/astro-dynamic-header/Header';
336
+ import { defaultThemes } from '@sofidevo/astro-dynamic-header';
297
337
  import type {
298
338
  NavConfig,
299
339
  DualThemeConfig,
340
+ HeaderClassNames,
300
341
  MenuItem,
301
342
  SecondaryMenuItem
302
343
  } from '@sofidevo/astro-dynamic-header';
@@ -311,42 +352,43 @@ const navigation: NavConfig = {
311
352
  { link: '/hardware', text: 'Hardware' }
312
353
  ]
313
354
  }
314
- ]
355
+ ],
356
+ header__menu__class: "flex gap-6",
315
357
  };
316
358
 
359
+ // Prefer CSS custom properties globally rather than passing the `theme` object.
360
+ // But you can still use the theme prop if needed:
317
361
  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
- }
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",
328
369
  };
329
370
  ---
330
371
 
331
372
  <Header
332
373
  navigation={navigation}
333
- theme={theme}
374
+ classNames={classNames}
334
375
  preset="auto"
335
376
  />
336
377
  ```
337
378
 
338
379
  ### Available Types
339
380
 
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 |
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 |
350
392
 
351
393
  ## Browser Support
352
394
 
@@ -400,3 +442,64 @@ MIT License - see the [LICENSE](./LICENSE) file for details.
400
442
  ## Support
401
443
 
402
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": "2.0.1",
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.8",
56
+ "@astrojs/check": "^0.9.9",
57
57
  "@types/jsdom": "^21.1.7",
58
58
  "@vitest/coverage-v8": "^3.2.4",
59
- "astro": "^6.0.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,16 +1,11 @@
1
1
  ---
2
- export interface Props {
3
- color?: string;
4
- }
5
-
6
- const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
2
+ export interface Props {}
7
3
  ---
8
4
 
9
5
  <button
10
6
  class="hamburger hamburger--collapse"
11
7
  type="button"
12
8
  id="hamburger-btn"
13
- style={`--hamburger-color: ${color};`}
14
9
  >
15
10
  <span class="hamburger-box">
16
11
  <span class="hamburger-inner"></span>
@@ -23,14 +18,16 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
23
18
  font: inherit;
24
19
  overflow: visible;
25
20
  margin: 0;
26
- padding: 15px;
21
+ padding: 10px;
27
22
  cursor: pointer;
28
23
  transition-timing-function: linear;
29
24
  transition-duration: 0.15s;
30
- transition-property: opacity, filter;
25
+ transition-property: opacity, filter, border-color;
31
26
  text-transform: none;
32
27
  color: inherit;
33
- border: 0;
28
+ border: 1px solid
29
+ color-mix(in srgb, var(--text-color, #fff) 25%, transparent);
30
+ border-radius: 8px;
34
31
  background-color: transparent;
35
32
 
36
33
  @media screen and (max-width: 768px) {
@@ -61,7 +58,7 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
61
58
  .hamburger-inner,
62
59
  .hamburger-inner::before,
63
60
  .hamburger-inner::after {
64
- background-color: var(--hamburger-color, var(--color-hamburger-lines, #fff));
61
+ background-color: var(--text-color, inherit);
65
62
  position: absolute;
66
63
  width: 40px;
67
64
  height: 4px;
@@ -88,12 +85,16 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
88
85
  }
89
86
 
90
87
  .hamburger--collapse .hamburger-inner:before {
91
- transition: top 0.12s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s, transform 0.13s cubic-bezier(0.55, 0.055, 0.675, 0.19);
88
+ transition:
89
+ top 0.12s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s,
90
+ transform 0.13s cubic-bezier(0.55, 0.055, 0.675, 0.19);
92
91
  }
93
92
 
94
93
  .hamburger--collapse .hamburger-inner:after {
95
94
  top: -20px;
96
- transition: top 0.2s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s, opacity 0.1s linear;
95
+ transition:
96
+ top 0.2s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s,
97
+ opacity 0.1s linear;
97
98
  }
98
99
 
99
100
  /* Animation */
@@ -105,13 +106,17 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
105
106
 
106
107
  .hamburger--collapse.is-active .hamburger-inner:before {
107
108
  top: 0;
108
- transition: top 0.1s cubic-bezier(0.33333, 0, 0.66667, 0.33333) 0.16s, transform 0.13s cubic-bezier(0.215, 0.61, 0.355, 1) 0.25s;
109
+ transition:
110
+ top 0.1s cubic-bezier(0.33333, 0, 0.66667, 0.33333) 0.16s,
111
+ transform 0.13s cubic-bezier(0.215, 0.61, 0.355, 1) 0.25s;
109
112
  transform: rotate(-90deg);
110
113
  }
111
114
 
112
115
  .hamburger--collapse.is-active .hamburger-inner:after {
113
116
  top: 0;
114
- transition: top 0.2s cubic-bezier(0.33333, 0, 0.66667, 0.33333), opacity 0.1s linear 0.22s;
117
+ transition:
118
+ top 0.2s cubic-bezier(0.33333, 0, 0.66667, 0.33333),
119
+ opacity 0.1s linear 0.22s;
115
120
  opacity: 0;
116
121
  }
117
122
  </style>
package/src/Header.astro CHANGED
@@ -4,37 +4,52 @@ 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
- CustomClassNames,
9
+ HeaderClassNames,
11
10
  } from "./index.js";
12
11
 
13
12
  export interface Props {
13
+ /**
14
+ * Layout style.
15
+ * - "floating": Centered with max-width and rounded corners.
16
+ * - "fullscreen": Full width with no border radius.
17
+ * @default "floating"
18
+ */
14
19
  headerType?: "floating" | "fullscreen";
20
+ /**
21
+ * Theme behavior.
22
+ * - "light": Force light mode.
23
+ * - "dark": Force dark mode.
24
+ * - "auto": Detects .dark class on the root element.
25
+ * @default "auto"
26
+ */
15
27
  preset?: "light" | "dark" | "auto";
16
- logo?: LogoConfig;
28
+
29
+ /** Navigation links and structure. */
17
30
  navigation?: NavConfig;
31
+ /** Custom theme overrides for light and dark modes. */
18
32
  theme?: DualThemeConfig;
19
- classNames?: CustomClassNames;
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
+ */
39
+ classNames?: HeaderClassNames;
20
40
  }
21
41
 
22
42
  const {
23
43
  headerType = "floating",
24
44
  preset = "auto",
25
- logo = {},
45
+
26
46
  navigation = {},
27
47
  theme = {},
28
48
  classNames = {},
29
49
  } = Astro.props;
30
50
 
31
51
  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
- }
52
+
38
53
  if (Array.isArray(navigation)) {
39
54
  console.warn(
40
55
  "[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'navigation' prop now expects an object with 'menuItems'. Please use navigation={{ menuItems: [...] }} instead.",
@@ -42,42 +57,36 @@ if (import.meta.env.DEV) {
42
57
  }
43
58
  }
44
59
 
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 };
60
+ // Map user theme overrides to CSS custom properties
61
+ const inlineStyles: Record<string, string> = {};
68
62
 
69
- // Navigation configuration
70
- const { homeUrl = "/", menuItems = [] } = navigation;
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
+ }
71
77
 
72
- // Logo configuration
78
+ const zIndex = theme.light?.zIndex ?? 10;
79
+
80
+ // Navigation configuration
73
81
  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;
82
+ homeUrl = "/",
83
+ menuItems = [],
84
+ header__menu__class,
85
+ header__item__class,
86
+ menu__link__class,
87
+ } = navigation;
88
+
89
+
81
90
 
82
91
  const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
83
92
  ---
@@ -130,7 +139,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
130
139
  `header__container--${headerType}`,
131
140
  classNames.container,
132
141
  ]}
133
- style={{ zIndex: lightTheme.zIndex }}
142
+ style={{ zIndex }}
134
143
  >
135
144
  <header
136
145
  class:list={[
@@ -139,40 +148,17 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
139
148
  forcedClass,
140
149
  classNames.header,
141
150
  ]}
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
- }}
151
+ style={Object.keys(inlineStyles).length > 0 ? inlineStyles : undefined}
154
152
  >
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
- }
172
- </a>
153
+ <slot name="logo" />
173
154
 
174
155
  <div class:list={["nav-menu-wrapper", classNames.nav]}>
175
- <NavMenu menuItems={menuItems} />
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
+ />
176
162
  </div>
177
163
 
178
164
  {
@@ -185,7 +171,11 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
185
171
 
186
172
  <HamburgerButton />
187
173
 
188
- <MobileNav menuItems={menuItems} type={headerType}>
174
+ <MobileNav
175
+ menuItems={menuItems}
176
+ type={headerType}
177
+ mobileNav__class={classNames.mobileNav}
178
+ >
189
179
  {
190
180
  Astro.slots.has("actions") && (
191
181
  <div class="actions-mobile" slot="slot-panel">
@@ -199,11 +189,11 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
199
189
 
200
190
  <style is:inline>
201
191
  .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);
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));
207
197
 
208
198
  display: flex;
209
199
  align-items: center;
@@ -227,28 +217,28 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
227
217
 
228
218
  /* Automatic dark mode detection via root class */
229
219
  :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);
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));
235
225
  }
236
226
 
237
227
  /* Forced themes */
238
228
  .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;
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;
244
234
  }
245
235
 
246
236
  .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;
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;
252
242
  }
253
243
 
254
244
  .header__container {
@@ -293,20 +283,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
293
283
  }
294
284
  }
295
285
 
296
- .logo__container {
297
- display: flex;
298
- align-items: center;
299
- text-decoration: none;
300
- color: inherit;
301
- & span {
302
- font-weight: 600;
303
- }
304
- }
305
286
 
306
- .header__logo {
307
- margin-right: 1em;
308
- object-fit: contain;
309
- }
310
287
 
311
288
  .actions-desktop {
312
289
  @media (width < 768px) {
@@ -2,25 +2,34 @@
2
2
  import type { MenuItem, SecondaryMenuItem, TertiaryMenuItem } from "./index.js";
3
3
 
4
4
  export interface Props {
5
+ /** Header type for styling: "floating" or "fullscreen" */
5
6
  type?: "floating" | "fullscreen";
7
+ /** Array of menu items to display in the mobile menu */
6
8
  menuItems?: MenuItem[];
9
+ /** Whether to show the home link (defaults to true) */
7
10
  showHomeLink?: boolean;
11
+ /** Text for the home link (defaults to "Home") */
8
12
  homeText?: string;
13
+ /** Extra CSS class(es) for the root mobile `<nav>` panel. */
14
+ mobileNav__class?: string;
9
15
  }
10
16
 
11
- const { type } = Astro.props;
12
-
13
17
  const {
18
+ type,
19
+ mobileNav__class,
14
20
  menuItems = [],
15
21
  showHomeLink = true,
16
- homeText = "Home",
22
+ homeText = "Home"
17
23
  } = Astro.props;
18
24
 
19
25
  const pagePathname = Astro.url.pathname;
20
26
  ---
21
27
 
22
28
  <nav
23
- class={`mobile-header__menu mobile-header__menu--${type}`}
29
+ class:list={[
30
+ `mobile-header__menu mobile-header__menu--${type}`,
31
+ mobileNav__class,
32
+ ]}
24
33
  id="mobile-header-menu"
25
34
  >
26
35
  <ol class="mobile-menu">
@@ -38,64 +47,101 @@ const pagePathname = Astro.url.pathname;
38
47
  )
39
48
  }
40
49
  {
41
- menuItems?.map((item: MenuItem) => (
42
- <li>
43
- {item.submenu ? (
44
- <details class="mobile-details">
45
- <summary class="menu__summary">
50
+ menuItems?.map((item: MenuItem) => {
51
+ const isActive = pagePathname === item.link;
52
+ return (
53
+ <li>
54
+ {item.submenu ? (
55
+ <details
56
+ class="mobile-details"
57
+ open={item.submenu.some((sub) => pagePathname === sub.link)}
58
+ >
59
+ <summary class:list={["menu__summary", { active: isActive }]}>
60
+ {item.text}
61
+ <iconify-icon
62
+ class="iconify-arrow"
63
+ icon="codicon:triangle-down"
64
+ width="15"
65
+ height="15"
66
+ style="color: var(--accent-color);"
67
+ />
68
+ </summary>
69
+ <ol class="mobile-submenu">
70
+ {item?.submenu?.map((sub: SecondaryMenuItem) => {
71
+ const isSubActive = pagePathname === sub.link;
72
+ return (
73
+ <li>
74
+ {sub.submenu && sub.submenu.length > 0 ? (
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
+ >
87
+ {sub.text}
88
+ <iconify-icon
89
+ class="iconify-arrow"
90
+ icon="codicon:triangle-down"
91
+ width="15"
92
+ height="15"
93
+ style="color: var(--accent-color);"
94
+ />
95
+ </summary>
96
+ <ol class="mobile-subsubmenu">
97
+ {sub.submenu.map((subsub: TertiaryMenuItem) => {
98
+ const isSubSubActive =
99
+ pagePathname === subsub.link;
100
+ return (
101
+ <li class="mobile-submenu__item secondary">
102
+ <a
103
+ class:list={[
104
+ "menu__link",
105
+ "mobile-menu__link",
106
+ { active: isSubSubActive },
107
+ ]}
108
+ href={subsub.link}
109
+ >
110
+ {subsub.text}
111
+ </a>
112
+ </li>
113
+ );
114
+ })}
115
+ </ol>
116
+ </details>
117
+ ) : (
118
+ <a
119
+ class:list={[
120
+ "menu__link",
121
+ "mobile-menu__link",
122
+ { active: isSubActive },
123
+ ]}
124
+ href={sub.link}
125
+ >
126
+ {sub.text}
127
+ </a>
128
+ )}
129
+ </li>
130
+ );
131
+ })}
132
+ </ol>
133
+ </details>
134
+ ) : (
135
+ <a
136
+ class:list={["mobile-menu__link", { active: isActive }]}
137
+ href={item.link}
138
+ >
46
139
  {item.text}
47
- <iconify-icon
48
- class="iconify-arrow"
49
- icon="codicon:triangle-down"
50
- width="15"
51
- height="15"
52
- style="color: var(--accent-color);"
53
- />
54
- </summary>
55
- <ol class="mobile-submenu">
56
- {item?.submenu?.map((sub: SecondaryMenuItem) => (
57
- <li>
58
- {sub.submenu && sub.submenu.length > 0 ? (
59
- <details class="mobile-details">
60
- <summary class="menu__summary">
61
- {sub.text}
62
- <iconify-icon
63
- class="iconify-arrow"
64
- icon="codicon:triangle-down"
65
- width="15"
66
- height="15"
67
- style="color: var(--accent-color);"
68
- />
69
- </summary>
70
- <ol class="mobile-subsubmenu">
71
- {sub.submenu.map((subsub: TertiaryMenuItem) => (
72
- <li class="mobile-submenu__item secondary">
73
- <a
74
- class="menu__link mobile-menu__link"
75
- href={subsub.link}
76
- >
77
- {subsub.text}
78
- </a>
79
- </li>
80
- ))}
81
- </ol>
82
- </details>
83
- ) : (
84
- <a class="menu__link mobile-menu__link" href={sub.link}>
85
- {sub.text}
86
- </a>
87
- )}
88
- </li>
89
- ))}
90
- </ol>
91
- </details>
92
- ) : (
93
- <a class="mobile-menu__link" href={item.link}>
94
- {item.text}
95
- </a>
96
- )}
97
- </li>
98
- ))
140
+ </a>
141
+ )}
142
+ </li>
143
+ );
144
+ })
99
145
  }
100
146
  </ol>
101
147
  <slot name="slot-panel" />
@@ -115,14 +161,24 @@ const pagePathname = Astro.url.pathname;
115
161
  left: 0;
116
162
  background: var(--bg-color-opaque, #151515);
117
163
  z-index: 20;
118
- transition: transform 0.3s ease-in-out, background-color 0.3s ease;
164
+ transition:
165
+ transform 0.3s ease-in-out,
166
+ background-color 0.3s ease;
119
167
  transform: translateX(100%);
120
168
  padding: 10rem 2rem 2rem;
121
169
  color: var(--text-color, #fff);
122
170
  }
123
171
 
124
172
  a {
125
- color: var(--text-color, #fff);
173
+ color: color-mix(
174
+ in srgb,
175
+ var(--accent-color, #00ffff) 30%,
176
+ var(--text-color, #ffffff) 70%
177
+ );
178
+ }
179
+
180
+ .active {
181
+ color: var(--accent-color, #00ffff) !important;
126
182
  }
127
183
  }
128
184
 
@@ -139,7 +195,7 @@ const pagePathname = Astro.url.pathname;
139
195
 
140
196
  .mobile-menu li a {
141
197
  border: solid 1px
142
- color-mix(in srgb, var(--accent-color, #00ffff) 20%, transparent);
198
+ color-mix(in srgb, var(--accent-color, #00ffff) 15%, transparent);
143
199
  border-radius: 4px;
144
200
  width: 100%;
145
201
  height: 4rem;
package/src/NavMenu.astro CHANGED
@@ -2,12 +2,39 @@
2
2
  import type { MenuItem, SecondaryMenuItem, TertiaryMenuItem } from "./index.js";
3
3
 
4
4
  export interface Props {
5
+ /**
6
+ * Header type for styling.
7
+ * @example "floating"
8
+ */
9
+ type?: "floating" | "fullscreen";
10
+ /** Array of menu items to display. */
5
11
  menuItems?: MenuItem[];
12
+ /**
13
+ * Whether to show the home link in the menu.
14
+ * @default true
15
+ */
6
16
  showHomeLink?: boolean;
17
+ /**
18
+ * Text for the home link.
19
+ * @default "Home"
20
+ */
7
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;
8
28
  }
9
29
 
10
- const { menuItems = [], showHomeLink = true, homeText = "Home" } = Astro.props;
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;
11
38
 
12
39
  const pagePathname = Astro.url.pathname;
13
40
  ---
@@ -114,12 +141,16 @@ const pagePathname = Astro.url.pathname;
114
141
  });
115
142
  </script>
116
143
 
117
- <nav class="header__menu" id="header-menu">
144
+ <nav class:list={["header__menu", header__menu__class]} id="header-menu">
118
145
  <ul class="menu">
119
146
  {
120
147
  showHomeLink && pagePathname !== "/" && (
121
- <li class="header__item">
122
- <a class="menu__link" href="/" data-astro-prefetch="hover">
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
+ >
123
154
  {homeText}
124
155
  </a>
125
156
  </li>
@@ -127,8 +158,8 @@ const pagePathname = Astro.url.pathname;
127
158
  }
128
159
  {
129
160
  menuItems.map((item: MenuItem) => (
130
- <li class="menu__item">
131
- <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}>
132
163
  {item.text}
133
164
  {item.submenu && (
134
165
  <iconify-icon
@@ -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,72 +1,194 @@
1
+ export { defaultThemes } from "./defaults.js";
1
2
 
2
-
3
- // Types
3
+ /**
4
+ * Represents a menu item in the navigation.
5
+ */
4
6
  export interface MenuItemType {
7
+ /** The URL path for the link */
5
8
  link: string;
9
+ /** The text label to display */
6
10
  text: string;
11
+ /** Optional nested submenu items */
7
12
  submenu?: MenuItemType[];
8
13
  }
9
14
 
15
+ /**
16
+ * Represents a third-level menu item.
17
+ */
10
18
  export interface TertiaryMenuItem {
19
+ /** The URL path for the link */
11
20
  link: string;
21
+ /** The text label to display */
12
22
  text: string;
13
23
  }
14
24
 
25
+ /**
26
+ * Represents a second-level menu item with optional nested tertiary items.
27
+ */
15
28
  export interface SecondaryMenuItem {
29
+ /** The URL path for the link */
16
30
  link: string;
31
+ /** The text label to display */
17
32
  text: string;
33
+ /** Optional nested tertiary menu items */
18
34
  submenu?: TertiaryMenuItem[];
19
35
  }
20
36
 
37
+ /**
38
+ * Represents a top-level menu item with optional nested secondary items.
39
+ */
21
40
  export interface MenuItem {
41
+ /** The URL path for the link */
22
42
  link: string;
43
+ /** The text label to display */
23
44
  text: string;
45
+ /** Optional nested secondary menu items */
24
46
  submenu?: SecondaryMenuItem[];
25
47
  }
26
48
 
27
- export interface LogoConfig {
28
- src?: string;
29
- alt?: string;
30
- width?: string;
31
- text?: string;
32
- textSize?: string;
33
- textColor?: string;
34
- }
35
-
49
+ /**
50
+ * Configuration for the main navigation.
51
+ */
36
52
  export interface NavConfig {
53
+ /**
54
+ * The URL for the home link.
55
+ * @default "/"
56
+ */
37
57
  homeUrl?: string;
58
+ /**
59
+ * Array of top-level menu items.
60
+ * @example [{ link: "/about", text: "About Us" }]
61
+ */
38
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;
39
82
  }
40
83
 
84
+ /**
85
+ * Individual theme settings for a specific state (light/dark).
86
+ */
41
87
  export interface ThemeConfig {
88
+ /**
89
+ * Main background color. Supports hex, rgb, rgba, etc.
90
+ * @example "rgba(255, 255, 255, 0.9)"
91
+ */
42
92
  backgroundColor?: string;
93
+ /**
94
+ * Solid background color for submenus and mobile panels to ensure readability.
95
+ * @example "#ffffff"
96
+ */
43
97
  backgroundColorOpaque?: string;
98
+ /**
99
+ * CSS backdrop-filter blur value.
100
+ * @default "blur(20px)"
101
+ */
44
102
  backdropBlur?: string;
103
+ /**
104
+ * CSS z-index for the header container.
105
+ * @default 10
106
+ */
45
107
  zIndex?: number;
108
+ /**
109
+ * Primary text color for navigation and logo.
110
+ */
46
111
  textColor?: string;
112
+ /**
113
+ * Color for highlights, active states, underscores, and small borders.
114
+ */
47
115
  accentColor?: string;
48
116
  }
49
117
 
118
+ /**
119
+ * Combined theme configuration for both light and dark modes.
120
+ */
50
121
  export interface DualThemeConfig {
122
+ /** Settings applied when light mode is active. */
51
123
  light?: ThemeConfig;
124
+ /** Settings applied when dark mode is active. */
52
125
  dark?: ThemeConfig;
53
126
  }
54
127
 
55
- export interface CustomClassNames {
128
+ /**
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
+ * ```
139
+ */
140
+ export interface HeaderClassNames {
141
+ /** Outermost fixed `<div>` that positions the header on the page. */
56
142
  container?: string;
143
+ /** Inner `<header>` element — best place for shadows, borders, transitions. */
57
144
  header?: string;
145
+ /** Logo anchor `<a>` — add hover states or focus rings here. */
58
146
  logo?: string;
147
+ /** Logo text `<span>` — override typography here. */
59
148
  logoText?: string;
149
+ /** Desktop nav wrapper `<div>` — adjust spacing between logo and menu. */
60
150
  nav?: string;
151
+ /** Mobile nav panel `<nav>` — add slide-in overrides or z-index tweaks. */
152
+ mobileNav?: string;
61
153
  }
62
154
 
155
+ /**
156
+ * @deprecated Use {@link HeaderClassNames} instead.
157
+ * Kept as an alias for backwards compatibility.
158
+ */
159
+ export type CustomClassNames = HeaderClassNames;
160
+
161
+ /**
162
+ * Main properties for the Header component.
163
+ */
63
164
  export interface HeaderProps {
165
+ /**
166
+ * Layout style.
167
+ * - "floating": Centered with max-width and rounded corners.
168
+ * - "fullscreen": Full width with no border radius.
169
+ * @default "floating"
170
+ */
64
171
  headerType?: "floating" | "fullscreen";
172
+ /**
173
+ * Theme behavior.
174
+ * - "light": Force light mode.
175
+ * - "dark": Force dark mode.
176
+ * - "auto": Detects .dark class on the root element.
177
+ * @default "auto"
178
+ */
65
179
  preset?: "light" | "dark" | "auto";
66
- logo?: LogoConfig;
180
+
181
+ /** Navigation links and structure. */
67
182
  navigation?: NavConfig;
183
+ /** Custom theme overrides. See {@link DualThemeConfig} */
68
184
  theme?: DualThemeConfig;
69
- classNames?: CustomClassNames;
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" }
190
+ */
191
+ classNames?: HeaderClassNames;
70
192
  }
71
193
 
72
194
  export interface NavMenuProps {