@sofidevo/astro-dynamic-header 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -109
- package/package.json +1 -1
- package/src/Header.astro +28 -10
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
|
-
|
|
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={
|
|
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
|
|
108
|
-
|
|
|
109
|
-
| `backgroundColor`
|
|
110
|
-
| `backgroundColorOpaque
|
|
111
|
-
| `backdropBlur`
|
|
112
|
-
| `zIndex`
|
|
113
|
-
| `textColor`
|
|
114
|
-
| `accentColor`
|
|
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**.
|
|
@@ -137,6 +145,32 @@ const theme = {
|
|
|
137
145
|
| `logoText` | `string` | Logo text class |
|
|
138
146
|
| `nav` | `string` | Desktop navigation wrapper class |
|
|
139
147
|
|
|
148
|
+
#### LogoConfig
|
|
149
|
+
|
|
150
|
+
| Property | Type | Description |
|
|
151
|
+
| ----------- | -------- | ------------------------------------------------ |
|
|
152
|
+
| `src` | `string` | URL of the logo image |
|
|
153
|
+
| `alt` | `string` | Alternative text for the logo image |
|
|
154
|
+
| `width` | `string` | Width of the logo (e.g., "50px", "5rem") |
|
|
155
|
+
| `text` | `string` | Text to display next to or instead of logo image |
|
|
156
|
+
| `textSize` | `string` | Font size for the logo text |
|
|
157
|
+
| `textColor` | `string` | Color for the logo text |
|
|
158
|
+
|
|
159
|
+
#### NavConfig
|
|
160
|
+
|
|
161
|
+
| Property | Type | Description |
|
|
162
|
+
| ----------- | ------------ | --------------------------------------- |
|
|
163
|
+
| `homeUrl` | `string` | URL for the home link (defaults to `/`) |
|
|
164
|
+
| `menuItems` | `MenuItem[]` | Array of navigation menu items |
|
|
165
|
+
|
|
166
|
+
#### MenuItem
|
|
167
|
+
|
|
168
|
+
| Property | Type | Description |
|
|
169
|
+
| --------- | --------------------- | ----------------------------------- |
|
|
170
|
+
| `link` | `string` | URL the menu item points to |
|
|
171
|
+
| `text` | `string` | Label text for the menu item |
|
|
172
|
+
| `submenu` | `SecondaryMenuItem[]` | Optional array of nested menu items |
|
|
173
|
+
|
|
140
174
|
## Slots Support
|
|
141
175
|
|
|
142
176
|
The Header component provides a flexible slot system that allows you to add additional content:
|
|
@@ -165,46 +199,64 @@ const navigation = {
|
|
|
165
199
|
</Header>
|
|
166
200
|
```
|
|
167
201
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
```css
|
|
171
|
-
.header-actions {
|
|
172
|
-
display: flex;
|
|
173
|
-
gap: 0.5em;
|
|
174
|
-
align-items: center;
|
|
175
|
-
}
|
|
202
|
+
## Comprehensive Example
|
|
176
203
|
|
|
177
|
-
.
|
|
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
|
-
}
|
|
204
|
+
Below is a complete implementation example showcasing custom logo configuration, navigation with a home URL, and theme overrides.
|
|
188
205
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
border: 1px solid #ffffff;
|
|
193
|
-
}
|
|
206
|
+
```astro
|
|
207
|
+
---
|
|
208
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
194
209
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
210
|
+
const menuItems = [
|
|
211
|
+
{
|
|
212
|
+
link: "#",
|
|
213
|
+
text: "Services",
|
|
214
|
+
submenu: [
|
|
215
|
+
{ link: "/design", text: "Design" },
|
|
216
|
+
{ link: "/consulting", text: "Consulting" },
|
|
217
|
+
{
|
|
218
|
+
link: "#",
|
|
219
|
+
text: "Web Development",
|
|
220
|
+
submenu: [
|
|
221
|
+
{ link: "/web/frontend", text: "Frontend" },
|
|
222
|
+
{ link: "/web/backend", text: "Backend" },
|
|
223
|
+
{ link: "/web/fullstack", text: "Full Stack" },
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
{ link: "/about", text: "About" },
|
|
229
|
+
{ link: "/contact", text: "Contact" },
|
|
230
|
+
];
|
|
199
231
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
+
};
|
|
242
|
+
---
|
|
204
243
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
244
|
+
<Header
|
|
245
|
+
headerType="floating"
|
|
246
|
+
preset="dark"
|
|
247
|
+
logo={{
|
|
248
|
+
src: "https://itssofi.dev/img/icons/sofi-icon.webp",
|
|
249
|
+
alt: "My Site Logo",
|
|
250
|
+
width: "44px",
|
|
251
|
+
}}
|
|
252
|
+
navigation={{
|
|
253
|
+
homeUrl: "/",
|
|
254
|
+
menuItems: menuItems,
|
|
255
|
+
}}
|
|
256
|
+
theme={theme}
|
|
257
|
+
>
|
|
258
|
+
<button slot="actions">Login</button>
|
|
259
|
+
</Header>
|
|
208
260
|
```
|
|
209
261
|
|
|
210
262
|
## Header Types
|
|
@@ -242,17 +294,17 @@ The package provides full TypeScript support. You can import types to ensure you
|
|
|
242
294
|
```astro
|
|
243
295
|
---
|
|
244
296
|
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
245
|
-
import type {
|
|
246
|
-
NavConfig,
|
|
247
|
-
DualThemeConfig,
|
|
297
|
+
import type {
|
|
298
|
+
NavConfig,
|
|
299
|
+
DualThemeConfig,
|
|
248
300
|
MenuItem,
|
|
249
301
|
SecondaryMenuItem
|
|
250
302
|
} from '@sofidevo/astro-dynamic-header';
|
|
251
303
|
|
|
252
304
|
const navigation: NavConfig = {
|
|
253
305
|
menuItems: [
|
|
254
|
-
{
|
|
255
|
-
link: '/products',
|
|
306
|
+
{
|
|
307
|
+
link: '/products',
|
|
256
308
|
text: 'Products',
|
|
257
309
|
submenu: [
|
|
258
310
|
{ link: '/software', text: 'Software' },
|
|
@@ -276,25 +328,25 @@ const theme: DualThemeConfig = {
|
|
|
276
328
|
};
|
|
277
329
|
---
|
|
278
330
|
|
|
279
|
-
<Header
|
|
280
|
-
navigation={navigation}
|
|
281
|
-
theme={theme}
|
|
282
|
-
preset="auto"
|
|
331
|
+
<Header
|
|
332
|
+
navigation={navigation}
|
|
333
|
+
theme={theme}
|
|
334
|
+
preset="auto"
|
|
283
335
|
/>
|
|
284
336
|
```
|
|
285
337
|
|
|
286
338
|
### Available Types
|
|
287
339
|
|
|
288
|
-
| Type
|
|
289
|
-
|
|
290
|
-
| `MenuItem`
|
|
291
|
-
| `SecondaryMenuItem` | Second-level menu item
|
|
292
|
-
| `TertiaryMenuItem`
|
|
293
|
-
| `NavConfig`
|
|
294
|
-
| `ThemeConfig`
|
|
295
|
-
| `DualThemeConfig`
|
|
296
|
-
| `LogoConfig`
|
|
297
|
-
| `HeaderProps`
|
|
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 |
|
|
298
350
|
|
|
299
351
|
## Browser Support
|
|
300
352
|
|
|
@@ -324,7 +376,7 @@ If you encounter import errors, try these solutions:
|
|
|
324
376
|
// tsconfig.json
|
|
325
377
|
{
|
|
326
378
|
"compilerOptions": {
|
|
327
|
-
"moduleResolution": "bundler",
|
|
379
|
+
"moduleResolution": "bundler",
|
|
328
380
|
"allowImportingTsExtensions": true
|
|
329
381
|
}
|
|
330
382
|
}
|
|
@@ -341,47 +393,6 @@ If you encounter import errors, try these solutions:
|
|
|
341
393
|
|
|
342
394
|
Visit our demo website to see the component in action with interactive examples and complete documentation.
|
|
343
395
|
|
|
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
396
|
## License
|
|
386
397
|
|
|
387
398
|
MIT License - see the [LICENSE](./LICENSE) file for details.
|
package/package.json
CHANGED
package/src/Header.astro
CHANGED
|
@@ -28,6 +28,20 @@ const {
|
|
|
28
28
|
classNames = {},
|
|
29
29
|
} = Astro.props;
|
|
30
30
|
|
|
31
|
+
if (import.meta.env.DEV) {
|
|
32
|
+
// Check if logo is being passed as a string (legacy)
|
|
33
|
+
if (typeof logo === "string") {
|
|
34
|
+
console.warn(
|
|
35
|
+
"[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'logo' prop now expects an object. Please use logo={{ src: '...' }} instead.",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(navigation)) {
|
|
39
|
+
console.warn(
|
|
40
|
+
"[@sofidevo/astro-dynamic-header] BREAKING CHANGE: The 'navigation' prop now expects an object with 'menuItems'. Please use navigation={{ menuItems: [...] }} instead.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
// Default theme configuration
|
|
32
46
|
const defaultThemes = {
|
|
33
47
|
light: {
|
|
@@ -119,7 +133,12 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
119
133
|
style={{ zIndex: lightTheme.zIndex }}
|
|
120
134
|
>
|
|
121
135
|
<header
|
|
122
|
-
class:list={[
|
|
136
|
+
class:list={[
|
|
137
|
+
"header",
|
|
138
|
+
`header--${headerType}`,
|
|
139
|
+
forcedClass,
|
|
140
|
+
classNames.header,
|
|
141
|
+
]}
|
|
123
142
|
style={{
|
|
124
143
|
"--l-bg": lightTheme.backgroundColor,
|
|
125
144
|
"--l-bg-opaque": lightTheme.backgroundColorOpaque,
|
|
@@ -151,7 +170,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
151
170
|
)
|
|
152
171
|
}
|
|
153
172
|
</a>
|
|
154
|
-
|
|
173
|
+
|
|
155
174
|
<div class:list={["nav-menu-wrapper", classNames.nav]}>
|
|
156
175
|
<NavMenu menuItems={menuItems} />
|
|
157
176
|
</div>
|
|
@@ -163,13 +182,10 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
163
182
|
</div>
|
|
164
183
|
)
|
|
165
184
|
}
|
|
166
|
-
|
|
185
|
+
|
|
167
186
|
<HamburgerButton />
|
|
168
|
-
|
|
169
|
-
<MobileNav
|
|
170
|
-
menuItems={menuItems}
|
|
171
|
-
type={headerType}
|
|
172
|
-
>
|
|
187
|
+
|
|
188
|
+
<MobileNav menuItems={menuItems} type={headerType}>
|
|
173
189
|
{
|
|
174
190
|
Astro.slots.has("actions") && (
|
|
175
191
|
<div class="actions-mobile" slot="slot-panel">
|
|
@@ -198,7 +214,9 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
198
214
|
backdrop-filter: var(--backdrop-blur);
|
|
199
215
|
-webkit-backdrop-filter: var(--backdrop-blur);
|
|
200
216
|
color: var(--text-color);
|
|
201
|
-
transition:
|
|
217
|
+
transition:
|
|
218
|
+
background-color 0.3s ease,
|
|
219
|
+
color 0.3s ease;
|
|
202
220
|
|
|
203
221
|
@media (width < 768px) {
|
|
204
222
|
align-self: flex-end;
|
|
@@ -284,7 +302,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
|
|
|
284
302
|
font-weight: 600;
|
|
285
303
|
}
|
|
286
304
|
}
|
|
287
|
-
|
|
305
|
+
|
|
288
306
|
.header__logo {
|
|
289
307
|
margin-right: 1em;
|
|
290
308
|
object-fit: contain;
|