@sofidevo/astro-dynamic-header 2.0.0 → 2.0.2

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
@@ -2,6 +2,9 @@
2
2
 
3
3
  A dynamic, responsive header component for Astro projects that can switch between floating and fullscreen styles with multi-level dropdown navigation support.
4
4
 
5
+ > [!WARNING]
6
+ > **Breaking Changes**: Version 2.0+ introduces a restructured configuration object. If you are upgrading from an older version, please review the [Component Props](#component-props) and the [Comprehensive Example](#comprehensive-example) to migrate your configuration.
7
+
5
8
  ## Features
6
9
 
7
10
  - **Dynamic Styles**: Switch between floating and fullscreen header layouts
@@ -42,15 +45,15 @@ By default, the header uses `preset="auto"`, which automatically detects the the
42
45
  ---
43
46
  import Header from '@sofidevo/astro-dynamic-header/Header';
44
47
 
45
- const navigation = {
46
- menuItems: [
48
+
49
+ const = menuItems: [
47
50
  { link: '/about', text: 'About' },
48
51
  ]
49
- };
52
+
50
53
  ---
51
54
 
52
55
  <!-- Detects .dark class on root automatically -->
53
- <Header navigation={navigation} />
56
+ <Header navigation={{ menuItems }} />
54
57
  ```
55
58
 
56
59
  ### Advanced Usage (Dual-Theme Customization)
@@ -58,9 +61,14 @@ const navigation = {
58
61
  You can provide custom colors for both light and dark modes simultaneously.
59
62
 
60
63
  ```astro
64
+
61
65
  ---
62
66
  import Header from '@sofidevo/astro-dynamic-header/Header';
63
-
67
+ const navigation = {
68
+ menuItems: [
69
+ { link: '/about', text: 'About' },
70
+ ]
71
+ };
64
72
  const theme = {
65
73
  light: {
66
74
  accentColor: "#3e1c71",
@@ -104,14 +112,14 @@ const theme = {
104
112
 
105
113
  #### ThemeConfig
106
114
 
107
- | Propery | Type | Default |
108
- | ---------------------- | -------- | ---------------- |
109
- | `backgroundColor` | `string` | _Preset default_ |
110
- | `backgroundColorOpaque`| `string` | _Preset default_ |
111
- | `backdropBlur` | `string` | `"blur(20px)"` |
112
- | `zIndex` | `number` | `10` |
113
- | `textColor` | `string` | _Preset default_ |
114
- | `accentColor` | `string` | _Preset default_ |
115
+ | Propery | Type | Default |
116
+ | ----------------------- | -------- | ---------------- |
117
+ | `backgroundColor` | `string` | _Preset default_ |
118
+ | `backgroundColorOpaque` | `string` | _Preset default_ |
119
+ | `backdropBlur` | `string` | `"blur(20px)"` |
120
+ | `zIndex` | `number` | `10` |
121
+ | `textColor` | `string` | _Preset default_ |
122
+ | `accentColor` | `string` | _Preset default_ |
115
123
 
116
124
  > [!IMPORTANT]
117
125
  > **Transparency vs Solid Submenus**: To ensure the best UI and avoid rendering bugs with `backdrop-filter` on nested elements, submenus and the mobile navigation panel are **solid/opaque**.
@@ -129,13 +137,70 @@ const theme = {
129
137
 
130
138
  #### CustomClassNames
131
139
 
132
- | Propery | Type | Description |
133
- | ----------- | -------- | -------------------------------- |
134
- | `container` | `string` | Main container class |
135
- | `header` | `string` | Header element class |
136
- | `logo` | `string` | Logo link container class |
137
- | `logoText` | `string` | Logo text class |
138
- | `nav` | `string` | Desktop navigation wrapper class |
140
+ The `classNames` prop allows you to inject custom CSS classes (such as Tailwind CSS utility classes) into specific high-level elements of the Header component. This provides a bridge between the component's internal styles and your project's global styling system.
141
+
142
+ | Property | Target Element | Purpose & Common Use Cases |
143
+ | ----------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
144
+ | `container` | Outer `div` wrapping the header | **Positioning & Layout**: Use for `top-0`, `z-50`, `fixed`, or adjusting the `max-width` and `mx-auto` logic. |
145
+ | `header` | Inner `<header>` element | **Appearance**: The best place for shadows (`shadow-md`), borders (`border-b`), or custom transition durations. |
146
+ | `logo` | `<a>` tag surrounding the logo | **Interactions**: Add hover states, custom focus rings, or adjust the flex alignment of the logo group. |
147
+ | `logoText` | `<span>` tag containing the logo text | **Typography**: Override font weights, apply text shadows, or use specific tracking/leading classes. |
148
+ | `nav` | `div` wrapping the desktop navigation items | **Desktop Layout**: Adjust spacing between the logo and the menu, or add responsive visibility classes (`hidden md:flex`). |
149
+
150
+ ##### Advanced Usage Examples
151
+
152
+ **Implementing a Premium Shadow & Border (Tailwind):**
153
+ Ideal for creating a modern "glass" effect with a subtle border and shadow that adapts to dark mode.
154
+
155
+ ```astro
156
+ <Header
157
+ classNames={{
158
+ header: "shadow-xl border-b border-black/5 dark:border-white/10 transition-all duration-500",
159
+ container: "top-4 px-6"
160
+ }}
161
+ />
162
+ ```
163
+
164
+ **Custom Typography for Logo & Nav Spacing:**
165
+ Perfect for matching the header with your brand's specific typography and layout requirements.
166
+
167
+ ```astro
168
+ <Header
169
+ classNames={{
170
+ logoText: "tracking-tighter font-black italic uppercase",
171
+ nav: "ml-auto gap-8" /* Moves menu to the right and increases gap */
172
+ }}
173
+ />
174
+ ```
175
+
176
+ > [!TIP]
177
+ > 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
+
179
+ #### LogoConfig
180
+
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
+
190
+ #### NavConfig
191
+
192
+ | Property | Type | Description |
193
+ | ----------- | ------------ | --------------------------------------- |
194
+ | `homeUrl` | `string` | URL for the home link (defaults to `/`) |
195
+ | `menuItems` | `MenuItem[]` | Array of navigation menu items |
196
+
197
+ #### MenuItem
198
+
199
+ | Property | Type | Description |
200
+ | --------- | --------------------- | ----------------------------------- |
201
+ | `link` | `string` | URL the menu item points to |
202
+ | `text` | `string` | Label text for the menu item |
203
+ | `submenu` | `SecondaryMenuItem[]` | Optional array of nested menu items |
139
204
 
140
205
  ## Slots Support
141
206
 
@@ -165,46 +230,64 @@ const navigation = {
165
230
  </Header>
166
231
  ```
167
232
 
168
- #### Styling Action Buttons
169
-
170
- ```css
171
- .header-actions {
172
- display: flex;
173
- gap: 0.5em;
174
- align-items: center;
175
- }
233
+ ## Comprehensive Example
176
234
 
177
- .btn {
178
- padding: 0.5em 1em;
179
- border: none;
180
- border-radius: 6px;
181
- cursor: pointer;
182
- font-weight: 500;
183
- text-decoration: none;
184
- display: inline-flex;
185
- align-items: center;
186
- transition: all 0.2s ease;
187
- }
235
+ Below is a complete implementation example showcasing custom logo configuration, navigation with a home URL, and theme overrides.
188
236
 
189
- .btn-outline {
190
- background: transparent;
191
- color: #ffffff;
192
- border: 1px solid #ffffff;
193
- }
237
+ ```astro
238
+ ---
239
+ import Header from '@sofidevo/astro-dynamic-header/Header';
194
240
 
195
- .btn-outline:hover {
196
- background: #ffffff;
197
- color: #000000;
198
- }
241
+ const menuItems = [
242
+ {
243
+ link: "#",
244
+ text: "Services",
245
+ submenu: [
246
+ { link: "/design", text: "Design" },
247
+ { link: "/consulting", text: "Consulting" },
248
+ {
249
+ link: "#",
250
+ text: "Web Development",
251
+ submenu: [
252
+ { link: "/web/frontend", text: "Frontend" },
253
+ { link: "/web/backend", text: "Backend" },
254
+ { link: "/web/fullstack", text: "Full Stack" },
255
+ ],
256
+ },
257
+ ],
258
+ },
259
+ { link: "/about", text: "About" },
260
+ { link: "/contact", text: "Contact" },
261
+ ];
199
262
 
200
- .btn-primary {
201
- background: #00ffff;
202
- color: #000000;
203
- }
263
+ const theme = {
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
+ };
273
+ ---
204
274
 
205
- .btn-primary:hover {
206
- background: #00cccc;
207
- }
275
+ <Header
276
+ headerType="floating"
277
+ preset="dark"
278
+ logo={{
279
+ src: "https://itssofi.dev/img/icons/sofi-icon.webp",
280
+ alt: "My Site Logo",
281
+ width: "44px",
282
+ }}
283
+ navigation={{
284
+ homeUrl: "/",
285
+ menuItems: menuItems,
286
+ }}
287
+ theme={theme}
288
+ >
289
+ <button slot="actions">Login</button>
290
+ </Header>
208
291
  ```
209
292
 
210
293
  ## Header Types
@@ -242,17 +325,17 @@ The package provides full TypeScript support. You can import types to ensure you
242
325
  ```astro
243
326
  ---
244
327
  import Header from '@sofidevo/astro-dynamic-header/Header';
245
- import type {
246
- NavConfig,
247
- DualThemeConfig,
328
+ import type {
329
+ NavConfig,
330
+ DualThemeConfig,
248
331
  MenuItem,
249
332
  SecondaryMenuItem
250
333
  } from '@sofidevo/astro-dynamic-header';
251
334
 
252
335
  const navigation: NavConfig = {
253
336
  menuItems: [
254
- {
255
- link: '/products',
337
+ {
338
+ link: '/products',
256
339
  text: 'Products',
257
340
  submenu: [
258
341
  { link: '/software', text: 'Software' },
@@ -276,25 +359,25 @@ const theme: DualThemeConfig = {
276
359
  };
277
360
  ---
278
361
 
279
- <Header
280
- navigation={navigation}
281
- theme={theme}
282
- preset="auto"
362
+ <Header
363
+ navigation={navigation}
364
+ theme={theme}
365
+ preset="auto"
283
366
  />
284
367
  ```
285
368
 
286
369
  ### Available Types
287
370
 
288
- | Type | Description |
289
- |------|-------------|
290
- | `MenuItem` | Top-level menu item with optional properties |
291
- | `SecondaryMenuItem` | Second-level menu item |
292
- | `TertiaryMenuItem` | Third-level menu item |
293
- | `NavConfig` | Main navigation configuration object |
294
- | `ThemeConfig` | Individual theme settings (colors, blur, etc.) |
295
- | `DualThemeConfig` | Combined settings for light and dark modes |
296
- | `LogoConfig` | Logo image and text configuration |
297
- | `HeaderProps` | Main props for the Header component |
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` | Main navigation configuration object |
377
+ | `ThemeConfig` | Individual theme settings (colors, blur, etc.) |
378
+ | `DualThemeConfig` | Combined settings for light and dark modes |
379
+ | `LogoConfig` | Logo image and text configuration |
380
+ | `HeaderProps` | Main props for the Header component |
298
381
 
299
382
  ## Browser Support
300
383
 
@@ -324,7 +407,7 @@ If you encounter import errors, try these solutions:
324
407
  // tsconfig.json
325
408
  {
326
409
  "compilerOptions": {
327
- "moduleResolution": "bundler",
410
+ "moduleResolution": "bundler",
328
411
  "allowImportingTsExtensions": true
329
412
  }
330
413
  }
@@ -341,47 +424,6 @@ If you encounter import errors, try these solutions:
341
424
 
342
425
  Visit our demo website to see the component in action with interactive examples and complete documentation.
343
426
 
344
- ## Testing
345
-
346
- This project includes a comprehensive test suite with 34 tests covering all critical functionality.
347
-
348
- ### Running Tests
349
-
350
- ```bash
351
- # Run all tests
352
- npm test
353
-
354
- # Run tests in watch mode
355
- npm run test:watch
356
-
357
- # Run tests with coverage report
358
- npm run test:coverage
359
- ```
360
-
361
- ### Test Coverage
362
-
363
- The test suite covers:
364
-
365
- #### Component Logic Tests
366
-
367
- - **Header Component** (4 tests): Hamburger controller functionality, menu toggle behavior
368
- - **HamburgerButton Component** (10 tests): Button states, responsive behavior, accessibility
369
- - **MobileNav Component** (7 tests): Dropdown structure, nested submenus, conditional rendering
370
- - **NavMenu Component** (6 tests): Dynamic positioning, submenu interactions, viewport adjustments
371
-
372
- #### Integration Tests (7 tests)
373
-
374
- - Component interaction flows
375
- - Responsive behavior between mobile/desktop
376
- - Keyboard navigation and accessibility
377
- - Menu state management during navigation
378
-
379
- ### Test Technologies
380
-
381
- - **Vitest**: Fast testing framework
382
- - **jsdom**: DOM simulation for component testing
383
- - **TypeScript**: Type-safe test writing
384
-
385
427
  ## License
386
428
 
387
429
  MIT License - see the [LICENSE](./LICENSE) file for details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sofidevo/astro-dynamic-header",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
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",
@@ -1,9 +1,11 @@
1
1
  ---
2
2
  export interface Props {
3
+ /** Override the default color of the hamburger lines */
3
4
  color?: string;
4
5
  }
5
6
 
6
- const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
7
+ const { color = "var(--text-color, var(--color-hamburger-lines, #fff))" } =
8
+ Astro.props;
7
9
  ---
8
10
 
9
11
  <button
@@ -23,14 +25,16 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
23
25
  font: inherit;
24
26
  overflow: visible;
25
27
  margin: 0;
26
- padding: 15px;
28
+ padding: 10px;
27
29
  cursor: pointer;
28
30
  transition-timing-function: linear;
29
31
  transition-duration: 0.15s;
30
- transition-property: opacity, filter;
32
+ transition-property: opacity, filter, border-color;
31
33
  text-transform: none;
32
34
  color: inherit;
33
- border: 0;
35
+ border: 1px solid
36
+ color-mix(in srgb, var(--text-color, #fff) 25%, transparent);
37
+ border-radius: 8px;
34
38
  background-color: transparent;
35
39
 
36
40
  @media screen and (max-width: 768px) {
@@ -61,7 +65,10 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
61
65
  .hamburger-inner,
62
66
  .hamburger-inner::before,
63
67
  .hamburger-inner::after {
64
- background-color: var(--hamburger-color, var(--color-hamburger-lines, #fff));
68
+ background-color: var(
69
+ --hamburger-color,
70
+ var(--color-hamburger-lines, #fff)
71
+ );
65
72
  position: absolute;
66
73
  width: 40px;
67
74
  height: 4px;
@@ -88,12 +95,16 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
88
95
  }
89
96
 
90
97
  .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);
98
+ transition:
99
+ top 0.12s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s,
100
+ transform 0.13s cubic-bezier(0.55, 0.055, 0.675, 0.19);
92
101
  }
93
102
 
94
103
  .hamburger--collapse .hamburger-inner:after {
95
104
  top: -20px;
96
- transition: top 0.2s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s, opacity 0.1s linear;
105
+ transition:
106
+ top 0.2s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s,
107
+ opacity 0.1s linear;
97
108
  }
98
109
 
99
110
  /* Animation */
@@ -105,13 +116,17 @@ const { color = "var(--color-hamburger-lines, #fff)" } = Astro.props;
105
116
 
106
117
  .hamburger--collapse.is-active .hamburger-inner:before {
107
118
  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;
119
+ transition:
120
+ top 0.1s cubic-bezier(0.33333, 0, 0.66667, 0.33333) 0.16s,
121
+ transform 0.13s cubic-bezier(0.215, 0.61, 0.355, 1) 0.25s;
109
122
  transform: rotate(-90deg);
110
123
  }
111
124
 
112
125
  .hamburger--collapse.is-active .hamburger-inner:after {
113
126
  top: 0;
114
- transition: top 0.2s cubic-bezier(0.33333, 0, 0.66667, 0.33333), opacity 0.1s linear 0.22s;
127
+ transition:
128
+ top 0.2s cubic-bezier(0.33333, 0, 0.66667, 0.33333),
129
+ opacity 0.1s linear 0.22s;
115
130
  opacity: 0;
116
131
  }
117
132
  </style>
package/src/Header.astro CHANGED
@@ -11,11 +11,31 @@ import type {
11
11
  } from "./index.js";
12
12
 
13
13
  export interface Props {
14
+ /**
15
+ * Layout style.
16
+ * - "floating": Centered with max-width and rounded corners.
17
+ * - "fullscreen": Full width with no border radius.
18
+ * @default "floating"
19
+ */
14
20
  headerType?: "floating" | "fullscreen";
21
+ /**
22
+ * Theme behavior.
23
+ * - "light": Force light mode.
24
+ * - "dark": Force dark mode.
25
+ * - "auto": Detects .dark class on the root element.
26
+ * @default "auto"
27
+ */
15
28
  preset?: "light" | "dark" | "auto";
29
+ /** Logo configuration object (src, alt, text, etc.) */
16
30
  logo?: LogoConfig;
31
+ /** Navigation links and structure. */
17
32
  navigation?: NavConfig;
33
+ /** Custom theme overrides for light and dark modes. */
18
34
  theme?: DualThemeConfig;
35
+ /**
36
+ * Custom CSS classes for specific internal elements.
37
+ * @example { header: "shadow-lg", nav: "ml-auto" }
38
+ */
19
39
  classNames?: CustomClassNames;
20
40
  }
21
41
 
@@ -28,6 +48,20 @@ const {
28
48
  classNames = {},
29
49
  } = Astro.props;
30
50
 
51
+ if (import.meta.env.DEV) {
52
+ // Check if logo is being passed as a string (legacy)
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
+ }
58
+ if (Array.isArray(navigation)) {
59
+ console.warn(
60
+ "[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'navigation' prop now expects an object with 'menuItems'. Please use navigation={{ menuItems: [...] }} instead.",
61
+ );
62
+ }
63
+ }
64
+
31
65
  // Default theme configuration
32
66
  const defaultThemes = {
33
67
  light: {
@@ -119,7 +153,12 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
119
153
  style={{ zIndex: lightTheme.zIndex }}
120
154
  >
121
155
  <header
122
- class:list={["header", `header--${headerType}`, forcedClass, classNames.header]}
156
+ class:list={[
157
+ "header",
158
+ `header--${headerType}`,
159
+ forcedClass,
160
+ classNames.header,
161
+ ]}
123
162
  style={{
124
163
  "--l-bg": lightTheme.backgroundColor,
125
164
  "--l-bg-opaque": lightTheme.backgroundColorOpaque,
@@ -151,7 +190,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
151
190
  )
152
191
  }
153
192
  </a>
154
-
193
+
155
194
  <div class:list={["nav-menu-wrapper", classNames.nav]}>
156
195
  <NavMenu menuItems={menuItems} />
157
196
  </div>
@@ -163,13 +202,10 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
163
202
  </div>
164
203
  )
165
204
  }
166
-
205
+
167
206
  <HamburgerButton />
168
-
169
- <MobileNav
170
- menuItems={menuItems}
171
- type={headerType}
172
- >
207
+
208
+ <MobileNav menuItems={menuItems} type={headerType}>
173
209
  {
174
210
  Astro.slots.has("actions") && (
175
211
  <div class="actions-mobile" slot="slot-panel">
@@ -198,7 +234,9 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
198
234
  backdrop-filter: var(--backdrop-blur);
199
235
  -webkit-backdrop-filter: var(--backdrop-blur);
200
236
  color: var(--text-color);
201
- transition: background-color 0.3s ease, color 0.3s ease;
237
+ transition:
238
+ background-color 0.3s ease,
239
+ color 0.3s ease;
202
240
 
203
241
  @media (width < 768px) {
204
242
  align-self: flex-end;
@@ -284,7 +322,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
284
322
  font-weight: 600;
285
323
  }
286
324
  }
287
-
325
+
288
326
  .header__logo {
289
327
  margin-right: 1em;
290
328
  object-fit: contain;
@@ -2,9 +2,13 @@
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;
9
13
  }
10
14
 
@@ -38,64 +42,73 @@ const pagePathname = Astro.url.pathname;
38
42
  )
39
43
  }
40
44
  {
41
- menuItems?.map((item: MenuItem) => (
42
- <li>
43
- {item.submenu ? (
44
- <details class="mobile-details">
45
- <summary class="menu__summary">
45
+ menuItems?.map((item: MenuItem) => {
46
+ const isActive = pagePathname === item.link;
47
+ return (
48
+ <li>
49
+ {item.submenu ? (
50
+ <details class="mobile-details" open={item.submenu.some(sub => pagePathname === sub.link)}>
51
+ <summary class:list={["menu__summary", { active: isActive }]}>
52
+ {item.text}
53
+ <iconify-icon
54
+ class="iconify-arrow"
55
+ icon="codicon:triangle-down"
56
+ width="15"
57
+ height="15"
58
+ style="color: var(--accent-color);"
59
+ />
60
+ </summary>
61
+ <ol class="mobile-submenu">
62
+ {item?.submenu?.map((sub: SecondaryMenuItem) => {
63
+ const isSubActive = pagePathname === sub.link;
64
+ return (
65
+ <li>
66
+ {sub.submenu && sub.submenu.length > 0 ? (
67
+ <details class="mobile-details" open={sub.submenu.some(subsub => pagePathname === subsub.link)}>
68
+ <summary class:list={["menu__summary", { active: isSubActive }]}>
69
+ {sub.text}
70
+ <iconify-icon
71
+ class="iconify-arrow"
72
+ icon="codicon:triangle-down"
73
+ width="15"
74
+ height="15"
75
+ style="color: var(--accent-color);"
76
+ />
77
+ </summary>
78
+ <ol class="mobile-subsubmenu">
79
+ {sub.submenu.map((subsub: TertiaryMenuItem) => {
80
+ const isSubSubActive = pagePathname === subsub.link;
81
+ return (
82
+ <li class="mobile-submenu__item secondary">
83
+ <a
84
+ class:list={["menu__link", "mobile-menu__link", { active: isSubSubActive }]}
85
+ href={subsub.link}
86
+ >
87
+ {subsub.text}
88
+ </a>
89
+ </li>
90
+ );
91
+ })}
92
+ </ol>
93
+ </details>
94
+ ) : (
95
+ <a class:list={["menu__link", "mobile-menu__link", { active: isSubActive }]} href={sub.link}>
96
+ {sub.text}
97
+ </a>
98
+ )}
99
+ </li>
100
+ );
101
+ })}
102
+ </ol>
103
+ </details>
104
+ ) : (
105
+ <a class:list={["mobile-menu__link", { active: isActive }]} href={item.link}>
46
106
  {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
- ))
107
+ </a>
108
+ )}
109
+ </li>
110
+ );
111
+ })
99
112
  }
100
113
  </ol>
101
114
  <slot name="slot-panel" />
@@ -122,7 +135,15 @@ const pagePathname = Astro.url.pathname;
122
135
  }
123
136
 
124
137
  a {
125
- color: var(--text-color, #fff);
138
+ color: color-mix(
139
+ in srgb,
140
+ var(--accent-color, #00ffff) 30%,
141
+ var(--text-color, #ffffff) 70%
142
+ );
143
+ }
144
+
145
+ .active {
146
+ color: var(--accent-color, #00ffff) !important;
126
147
  }
127
148
  }
128
149
 
@@ -139,7 +160,7 @@ const pagePathname = Astro.url.pathname;
139
160
 
140
161
  .mobile-menu li a {
141
162
  border: solid 1px
142
- color-mix(in srgb, var(--accent-color, #00ffff) 20%, transparent);
163
+ color-mix(in srgb, var(--accent-color, #00ffff) 15%, transparent);
143
164
  border-radius: 4px;
144
165
  width: 100%;
145
166
  height: 4rem;
package/src/NavMenu.astro CHANGED
@@ -2,8 +2,23 @@
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
+ */
21
+
7
22
  homeText?: string;
8
23
  }
9
24
 
package/src/index.ts CHANGED
@@ -1,71 +1,192 @@
1
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
 
49
+ /**
50
+ * Configuration for the site logo.
51
+ */
27
52
  export interface LogoConfig {
53
+ /**
54
+ * The URL of the logo image.
55
+ * @example "/assets/logo.png"
56
+ * @default "/logo.png"
57
+ */
28
58
  src?: string;
59
+ /** Alternative text for the logo image */
29
60
  alt?: string;
61
+ /**
62
+ * CSS width for the logo.
63
+ * @example "150px" or "4rem"
64
+ * @default "55px"
65
+ */
30
66
  width?: string;
67
+ /**
68
+ * Optional text to display next to the logo image.
69
+ * @example "My Awesome Site"
70
+ */
31
71
  text?: string;
72
+ /**
73
+ * CSS font-size for the logo text.
74
+ * @default "1em"
75
+ */
32
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
+ */
33
82
  textColor?: string;
34
83
  }
35
84
 
85
+ /**
86
+ * Configuration for the main navigation.
87
+ */
36
88
  export interface NavConfig {
89
+ /**
90
+ * The URL for the home link.
91
+ * @default "/"
92
+ */
37
93
  homeUrl?: string;
94
+ /**
95
+ * Array of top-level menu items.
96
+ * @example [{ link: "/about", text: "About Us" }]
97
+ */
38
98
  menuItems?: MenuItem[];
39
99
  }
40
100
 
101
+ /**
102
+ * Individual theme settings for a specific state (light/dark).
103
+ */
41
104
  export interface ThemeConfig {
105
+ /**
106
+ * Main background color. Supports hex, rgb, rgba, etc.
107
+ * @example "rgba(255, 255, 255, 0.9)"
108
+ */
42
109
  backgroundColor?: string;
110
+ /**
111
+ * Solid background color for submenus and mobile panels to ensure readability.
112
+ * @example "#ffffff"
113
+ */
43
114
  backgroundColorOpaque?: string;
115
+ /**
116
+ * CSS backdrop-filter blur value.
117
+ * @default "blur(20px)"
118
+ */
44
119
  backdropBlur?: string;
120
+ /**
121
+ * CSS z-index for the header container.
122
+ * @default 10
123
+ */
45
124
  zIndex?: number;
125
+ /**
126
+ * Primary text color for navigation and logo.
127
+ */
46
128
  textColor?: string;
129
+ /**
130
+ * Color for highlights, active states, underscores, and small borders.
131
+ */
47
132
  accentColor?: string;
48
133
  }
49
134
 
135
+ /**
136
+ * Combined theme configuration for both light and dark modes.
137
+ */
50
138
  export interface DualThemeConfig {
139
+ /** Settings applied when light mode is active. */
51
140
  light?: ThemeConfig;
141
+ /** Settings applied when dark mode is active. */
52
142
  dark?: ThemeConfig;
53
143
  }
54
144
 
145
+ /**
146
+ * Custom CSS class names for deep customization.
147
+ */
55
148
  export interface CustomClassNames {
149
+ /** Class for the outermost fixed container */
56
150
  container?: string;
151
+ /** Class for the main header element */
57
152
  header?: string;
153
+ /** Class for the logo anchor tag */
58
154
  logo?: string;
155
+ /** Class for the logo span text */
59
156
  logoText?: string;
157
+ /** Class for the desktop navigation wrapper */
60
158
  nav?: string;
61
159
  }
62
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";
180
+ /** Logo configuration object. */
66
181
  logo?: LogoConfig;
182
+ /** Navigation links and structure. */
67
183
  navigation?: NavConfig;
184
+ /** Custom theme overrides. See @interface DualThemeConfig */
68
185
  theme?: DualThemeConfig;
186
+ /**
187
+ * Custom CSS classes for injecting utility classes (e.g., Tailwind).
188
+ * @example { header: "shadow-lg", logoText: "font-bold" }
189
+ */
69
190
  classNames?: CustomClassNames;
70
191
  }
71
192