@sofidevo/astro-dynamic-header 3.0.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +543 -337
- package/package.json +20 -16
- package/src/ChevronIcon.astro +33 -0
- package/src/HamburgerButton.astro +100 -92
- package/src/Header.astro +105 -156
- package/src/MobileNav.astro +133 -129
- package/src/MoonIcon.astro +114 -0
- package/src/NavMenu.astro +175 -281
- package/src/SunIcon.astro +42 -0
- package/src/index.ts +15 -8
- package/src/scripts/hamburger.ts +55 -0
- package/src/scripts/nav-menu.ts +119 -0
package/README.md
CHANGED
|
@@ -1,52 +1,47 @@
|
|
|
1
1
|
# @sofidevo/astro-dynamic-header
|
|
2
2
|
|
|
3
|
-
A dynamic, responsive header component for Astro projects
|
|
3
|
+
A dynamic, responsive header component for Astro projects. Supports floating and fullscreen layouts, multi-level dropdown navigation, native CSS variable customization, dark mode, and TypeScript — all with zero external icon dependencies.
|
|
4
4
|
|
|
5
|
-
|
|
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.
|
|
5
|
+
As of v4.0.0 the component styles are shipped inside CSS cascade layers, so any utility class you pass to the component wins over the built-in styles **without `!important`**.
|
|
13
6
|
|
|
14
7
|
## Features
|
|
15
8
|
|
|
16
|
-
- **
|
|
17
|
-
- **Fully Responsive
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
9
|
+
- **Floating & Fullscreen layouts** — switch layouts with a single prop.
|
|
10
|
+
- **Fully Responsive** — mobile-first accordion dropdowns with optimized hit targets.
|
|
11
|
+
- **3-level Dropdowns** — top, secondary, and tertiary nested navigation items.
|
|
12
|
+
- **Dark Mode Ready** — auto-detects `.dark` on `<html>`, or forces a state with `preset`.
|
|
13
|
+
- **Inline SVG Icons** — no external CDNs, no extra network requests, no flash of missing icons.
|
|
14
|
+
- **Slot Support** — inject your custom logo and header actions directly into slots.
|
|
15
|
+
- **Pure CSS Customization** — customize background, blur, and colors using native CSS variables.
|
|
16
|
+
- **Cascade-layer friendly** — component styles live in `@layer components`, so your utility classes override them without `!important` and without `twMerge()`.
|
|
17
|
+
- **Full TypeScript** — all props and config interfaces are fully typed.
|
|
24
18
|
|
|
25
|
-
### Live
|
|
19
|
+
### Live Demo
|
|
26
20
|
|
|
27
21
|
[https://base-astro-psi.vercel.app/fullscreen-demo](https://base-astro-psi.vercel.app/fullscreen-demo)
|
|
28
22
|
|
|
23
|
+
---
|
|
24
|
+
|
|
29
25
|
## Installation
|
|
30
26
|
|
|
31
27
|
```bash
|
|
32
|
-
npm
|
|
33
|
-
|
|
28
|
+
# npm
|
|
29
|
+
npm i @sofidevo/astro-dynamic-header
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
# pnpm
|
|
32
|
+
pnpm add @sofidevo/astro-dynamic-header
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```html
|
|
40
|
-
<script src="https://code.iconify.design/iconify-icon/3.0.0/iconify-icon.min.js"></script>
|
|
34
|
+
# yarn
|
|
35
|
+
yarn add @sofidevo/astro-dynamic-header
|
|
41
36
|
```
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
- Peer dependency: `astro ^7.0.0`.
|
|
39
|
+
- No external CDN scripts or stylesheet additions are required.
|
|
40
|
+
- No CSS framework is required. Tailwind, UnoCSS, or plain CSS all work.
|
|
46
41
|
|
|
47
|
-
|
|
42
|
+
---
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
## Quick Start
|
|
50
45
|
|
|
51
46
|
```astro
|
|
52
47
|
---
|
|
@@ -54,452 +49,663 @@ import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
|
54
49
|
|
|
55
50
|
const menuItems = [
|
|
56
51
|
{ link: '/about', text: 'About' },
|
|
52
|
+
{ link: '/contact', text: 'Contact' },
|
|
57
53
|
];
|
|
54
|
+
|
|
55
|
+
const navigation = { menuItems };
|
|
58
56
|
---
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
<
|
|
58
|
+
<Header navigation={navigation}>
|
|
59
|
+
<a slot="logo" href="/">MyLogo</a>
|
|
60
|
+
</Header>
|
|
62
61
|
```
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Breaking Changes in v4.0.0
|
|
66
|
+
|
|
67
|
+
> [!WARNING]
|
|
68
|
+
> v4.0.0 changes how the component's CSS participates in the cascade. Read this section before upgrading from `3.x`.
|
|
65
69
|
|
|
66
|
-
|
|
70
|
+
### 1. `classNames.logo` and `classNames.logoText` were removed
|
|
71
|
+
|
|
72
|
+
`HeaderClassNames` no longer accepts `logo` or `logoText`. Those keys never matched any rendered markup (the logo has always been a slot), but passing them now fails type-checking.
|
|
73
|
+
|
|
74
|
+
Before (v3) — never actually styled anything:
|
|
67
75
|
|
|
68
76
|
```astro
|
|
77
|
+
<Header classNames={{ logo: "hover:opacity-80", logoText: "font-bold" }} />
|
|
78
|
+
```
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
72
|
-
const navigation = {
|
|
73
|
-
menuItems: [
|
|
74
|
-
{ link: '/about', text: 'About' },
|
|
75
|
-
]
|
|
76
|
-
};
|
|
77
|
-
const theme = {
|
|
78
|
-
light: {
|
|
79
|
-
accentColor: "#3e1c71",
|
|
80
|
-
backgroundColor: "rgba(255, 255, 255, 0.8)"
|
|
81
|
-
},
|
|
82
|
-
dark: {
|
|
83
|
-
accentColor: "#00ffff",
|
|
84
|
-
backgroundColor: "rgba(20, 20, 20, 0.9)"
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
---
|
|
80
|
+
After (v4) — style the markup you put in the slot:
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
82
|
+
```astro
|
|
83
|
+
<Header>
|
|
84
|
+
<a slot="logo" href="/" class="logo-link hover:opacity-80">
|
|
85
|
+
<span class="font-bold">MyLogo</span>
|
|
86
|
+
</a>
|
|
87
|
+
</Header>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. Component styles are now scoped and layered
|
|
91
|
+
|
|
92
|
+
| | v3.x | v4.0.0 |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| Style block | `<style is:inline>` (global) | `<style>` (scoped to the component) |
|
|
95
|
+
| Layer | none (unlayered) | `@layer components` |
|
|
96
|
+
| Order statement | none | `@layer theme, base, components, utilities;` |
|
|
97
|
+
| Override your header with utilities | required `!important` | works out of the box |
|
|
98
|
+
| Your unlayered CSS vs. component | component usually won (specificity + document order) | **your unlayered CSS wins** |
|
|
99
|
+
|
|
100
|
+
Two practical consequences:
|
|
101
|
+
|
|
102
|
+
- **Utilities now win.** `classNames`, Tailwind classes, and any rule you put in `@layer utilities` beat the built-in styles even at equal or lower specificity. You can delete the `!important`s you may have added.
|
|
103
|
+
- **Unlayered CSS now wins over the component.** Global resets, hand-written element selectors (`header { ... }`, `a { ... }`), and Tailwind v3 Preflight are emitted outside any layer, and unlayered rules beat *every* layered rule. If your reset should sit *under* the header, wrap it in `@layer base` (see the [Styling Guide](#styling-guide) and the [FAQ](#my-overrides-do-not-apply)).
|
|
104
|
+
|
|
105
|
+
### 3. Other behavior changes
|
|
106
|
+
|
|
107
|
+
- **`theme.dark.zIndex` is now honored.** The container z-index resolves as `theme.light.zIndex ?? theme.dark.zIndex ?? 10`; previously only `light.zIndex` was read.
|
|
108
|
+
- **Forced theme rules no longer use `!important`.** `.header.header--force-light` / `.header.header--force-dark` rely on specificity instead, so they can be overridden by your own layered CSS if you ever need to.
|
|
109
|
+
- **The mobile panel's `.active` rule no longer uses `!important`**, so active-link styles are overridable like everything else.
|
|
110
|
+
- **`MobileNav` defaults to `type="floating"`** when rendered on its own (previously it produced an undefined modifier class).
|
|
111
|
+
- **Theme variable fallbacks now match `defaultThemes`** exactly (for example `rgba(255, 255, 255, 0.9)` for `--l-bg`).
|
|
112
|
+
|
|
113
|
+
### Migration checklist
|
|
114
|
+
|
|
115
|
+
```md
|
|
116
|
+
- [ ] Update the package: npm i -U @sofidevo/astro-dynamic-header
|
|
117
|
+
- [ ] Remove `logo` / `logoText` from `classNames` (style the `logo` slot instead).
|
|
118
|
+
- [ ] Remove the `!important` declarations you added to override the header (they are no longer needed).
|
|
119
|
+
- [ ] Move your global resets / element selectors into `@layer base`.
|
|
120
|
+
- [ ] Using Tailwind v3? Read "Preflight changed my nav link colors" in the FAQ.
|
|
121
|
+
- [ ] Setting only `dark.zIndex`? It now takes effect — double-check stacking.
|
|
94
122
|
```
|
|
95
123
|
|
|
124
|
+
---
|
|
125
|
+
|
|
96
126
|
## Component Props
|
|
97
127
|
|
|
98
|
-
### Header
|
|
128
|
+
### `<Header>`
|
|
99
129
|
|
|
100
|
-
| Prop | Type | Default | Description
|
|
101
|
-
| ------------ | ----------------------------- | ------------ |
|
|
102
|
-
| `headerType` | `"floating" \| "fullscreen"` | `"floating"` |
|
|
103
|
-
| `preset` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme
|
|
104
|
-
| `navigation` | `NavConfig` | `{}` |
|
|
105
|
-
| `theme` | `DualThemeConfig` | `{}` |
|
|
106
|
-
| `classNames` | `HeaderClassNames` | `{}` |
|
|
130
|
+
| Prop | Type | Default | Description |
|
|
131
|
+
| ------------ | ----------------------------- | ------------ | ------------------------------------------------- |
|
|
132
|
+
| `headerType` | `"floating" \| "fullscreen"` | `"floating"` | Layout style |
|
|
133
|
+
| `preset` | `"light" \| "dark" \| "auto"` | `"auto"` | Theme mode. `"auto"` follows `.dark` on `<html>`. |
|
|
134
|
+
| `navigation` | `NavConfig` | `{}` | Menu items, home link, and custom CSS classes |
|
|
135
|
+
| `theme` | `DualThemeConfig` | `{}` | Optional theme overrides (prefer CSS variables) |
|
|
136
|
+
| `classNames` | `HeaderClassNames` | `{}` | Inject CSS classes into structural elements |
|
|
107
137
|
|
|
108
|
-
|
|
138
|
+
---
|
|
109
139
|
|
|
110
|
-
|
|
140
|
+
## Configuration Objects
|
|
111
141
|
|
|
112
|
-
|
|
113
|
-
| ------- | ------------- | ---------------------------- |
|
|
114
|
-
| `light` | `ThemeConfig` | Styles applied in light mode |
|
|
115
|
-
| `dark` | `ThemeConfig` | Styles applied in dark mode |
|
|
142
|
+
### `NavConfig`
|
|
116
143
|
|
|
117
|
-
|
|
144
|
+
| Property | Type | Default | Description |
|
|
145
|
+
| --------------------- | ------------ | ------- | ------------------------------------------------------- |
|
|
146
|
+
| `menuItems` | `MenuItem[]` | `[]` | Top-level navigation items |
|
|
147
|
+
| `homeUrl` | `string` | `"/"` | URL for the home link |
|
|
148
|
+
| `header__menu__class` | `string` | — | Extra CSS class(es) for the desktop `<nav>` element |
|
|
149
|
+
| `header__item__class` | `string` | — | Extra CSS class(es) for each top-level `<li>` menu item |
|
|
150
|
+
| `menu__link__class` | `string` | — | Extra CSS class(es) for each top-level `<a>` link |
|
|
118
151
|
|
|
119
|
-
|
|
120
|
-
| ----------------------- | -------- | ---------------- |
|
|
121
|
-
| `backgroundColor` | `string` | _Preset default_ |
|
|
122
|
-
| `backgroundColorOpaque` | `string` | _Preset default_ |
|
|
123
|
-
| `backdropBlur` | `string` | `"blur(20px)"` |
|
|
124
|
-
| `zIndex` | `number` | `10` |
|
|
125
|
-
| `textColor` | `string` | _Preset default_ |
|
|
126
|
-
| `accentColor` | `string` | _Preset default_ |
|
|
152
|
+
### `MenuItem`
|
|
127
153
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
| Property | Type | Required | Description |
|
|
155
|
+
| --------- | --------------------- | -------- | --------------------------------- |
|
|
156
|
+
| `link` | `string` | Yes | URL the item points to |
|
|
157
|
+
| `text` | `string` | Yes | Display label |
|
|
158
|
+
| `submenu` | `SecondaryMenuItem[]` | No | Optional nested items (2nd level) |
|
|
159
|
+
|
|
160
|
+
### `SecondaryMenuItem`
|
|
161
|
+
|
|
162
|
+
| Property | Type | Required | Description |
|
|
163
|
+
| --------- | -------------------- | -------- | --------------------------------- |
|
|
164
|
+
| `link` | `string` | Yes | URL the item points to |
|
|
165
|
+
| `text` | `string` | Yes | Display label |
|
|
166
|
+
| `submenu` | `TertiaryMenuItem[]` | No | Optional nested items (3rd level) |
|
|
167
|
+
|
|
168
|
+
### `TertiaryMenuItem`
|
|
169
|
+
|
|
170
|
+
| Property | Type | Required | Description |
|
|
171
|
+
| -------- | -------- | -------- | ---------------------- |
|
|
172
|
+
| `link` | `string` | Yes | URL the item points to |
|
|
173
|
+
| `text` | `string` | Yes | Display label |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Slots
|
|
178
|
+
|
|
179
|
+
| Slot name | Visible on | Description |
|
|
180
|
+
| --------- | ---------------- | ---------------------------------------------------------- |
|
|
181
|
+
| `logo` | Header | Render your logo exactly as you need (native HTML/widgets) |
|
|
182
|
+
| `actions` | Desktop + mobile | Add buttons, links, or utility widgets |
|
|
132
183
|
|
|
133
184
|
```astro
|
|
134
|
-
<Header
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
185
|
+
<Header navigation={{ menuItems }}>
|
|
186
|
+
<a slot="logo" href="/" class="logo-link">
|
|
187
|
+
<img src="/logo.svg" alt="Branding" width="40" />
|
|
188
|
+
<span>MyBrand</span>
|
|
189
|
+
</a>
|
|
190
|
+
<div slot="actions">
|
|
191
|
+
<a href="/login" class="btn">Log in</a>
|
|
192
|
+
</div>
|
|
193
|
+
</Header>
|
|
140
194
|
```
|
|
141
195
|
|
|
142
|
-
|
|
196
|
+
Slotted markup is rendered by *your* page, so your page's CSS applies to it normally. Style the logo with your own classes (this replaces the removed `classNames.logo` / `classNames.logoText`).
|
|
143
197
|
|
|
144
|
-
|
|
145
|
-
> `CustomClassNames` was renamed to `HeaderClassNames` in v2.1. The old name still works as a type alias — no migration required.
|
|
198
|
+
---
|
|
146
199
|
|
|
147
|
-
|
|
200
|
+
## Custom Class Names
|
|
148
201
|
|
|
149
|
-
|
|
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. |
|
|
202
|
+
The `classNames` prop injects CSS classes into specific structural elements.
|
|
157
203
|
|
|
158
|
-
|
|
204
|
+
### `HeaderClassNames`
|
|
159
205
|
|
|
160
|
-
|
|
206
|
+
| Property | Target element | Common use cases |
|
|
207
|
+
| ----------- | -------------------------------------- | ------------------------------------------------- |
|
|
208
|
+
| `container` | Outer `<div>` wrapping the header | Positioning, padding, outer gutters |
|
|
209
|
+
| `header` | Inner `<header>` element | Shadows, borders, transitions, radius, backdrop |
|
|
210
|
+
| `nav` | `<div>` wrapping the desktop nav items | Spacing, alignment, responsive visibility |
|
|
211
|
+
| `mobileNav` | Root `<nav>` of the mobile panel | Backdrop blur, slide-in overrides, panel width |
|
|
161
212
|
|
|
162
213
|
```astro
|
|
214
|
+
<!-- Tailwind example -->
|
|
163
215
|
<Header
|
|
164
216
|
classNames={{
|
|
165
|
-
header: "shadow-xl border-b border-black/5 dark:border-white/10
|
|
217
|
+
header: "shadow-xl border-b border-black/5 dark:border-white/10",
|
|
166
218
|
container: "top-4 px-6",
|
|
167
219
|
mobileNav: "backdrop-blur-md",
|
|
168
220
|
}}
|
|
169
221
|
/>
|
|
170
222
|
```
|
|
171
223
|
|
|
172
|
-
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Styling Guide
|
|
227
|
+
|
|
228
|
+
Everything below assumes you want to change how the header looks or behaves. Pick the lightest tool that solves your case:
|
|
229
|
+
|
|
230
|
+
| Goal | Use |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| One-off tweaks (shadow, border, padding, radius) | `classNames` with utilities |
|
|
233
|
+
| A different overall design (square, full-width, compact) | Your own CSS in `@layer utilities` |
|
|
234
|
+
| Restyle nav links, dropdowns, or the mobile panel | Selectors targeting the internal class hooks |
|
|
235
|
+
| Brand colors, blur, background | CSS variables (`--l-*` / `--d-*`) |
|
|
236
|
+
| Per-instance tokens or z-index | `theme` prop |
|
|
237
|
+
| Logo / buttons markup | Slots |
|
|
238
|
+
|
|
239
|
+
### Style precedence
|
|
240
|
+
|
|
241
|
+
The component ships this statement ahead of its own rules:
|
|
242
|
+
|
|
243
|
+
```css
|
|
244
|
+
@layer theme, base, components, utilities;
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Priority, lowest to highest:
|
|
248
|
+
|
|
249
|
+
```text
|
|
250
|
+
base < components (this header) < utilities (your classNames/overrides) < unlayered CSS
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Rules of thumb:
|
|
254
|
+
|
|
255
|
+
- Put your resets and element defaults in `@layer base`.
|
|
256
|
+
- Put overrides for this header in `@layer utilities` (or pass them as `classNames`).
|
|
257
|
+
- Keep in mind that CSS **outside** any layer beats everything layered, including this component. That is the spec, not a bug.
|
|
258
|
+
- If you write plain CSS with no framework, declare the order statement at the top of your global stylesheet so the order is fixed before any layer is used. Tailwind v4 does this for you (`@layer theme, base, components, utilities;`).
|
|
259
|
+
|
|
260
|
+
### Example: redesign with plain CSS
|
|
261
|
+
|
|
262
|
+
Square, full-width, no glass effect — the classic "make it look like a different component" case:
|
|
173
263
|
|
|
174
264
|
```astro
|
|
175
265
|
<Header
|
|
176
266
|
classNames={{
|
|
177
|
-
|
|
178
|
-
|
|
267
|
+
container: "header-wide",
|
|
268
|
+
header: "header-square",
|
|
179
269
|
}}
|
|
180
270
|
/>
|
|
181
271
|
```
|
|
182
272
|
|
|
183
|
-
|
|
184
|
-
|
|
273
|
+
```css
|
|
274
|
+
@layer utilities {
|
|
275
|
+
.header-wide {
|
|
276
|
+
padding: 0;
|
|
277
|
+
}
|
|
185
278
|
|
|
279
|
+
.header-square {
|
|
280
|
+
max-width: 100%;
|
|
281
|
+
border-radius: 0;
|
|
282
|
+
box-shadow: none;
|
|
283
|
+
backdrop-filter: none;
|
|
284
|
+
background-color: #ffffff;
|
|
285
|
+
border-bottom: 1px solid #e5e7eb;
|
|
286
|
+
padding: 0.75rem 2rem;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
186
290
|
|
|
291
|
+
No `!important`, and the same classes work whether you are on Tailwind or not.
|
|
187
292
|
|
|
188
|
-
|
|
293
|
+
### Example: compact sticky bar
|
|
189
294
|
|
|
190
|
-
|
|
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 |
|
|
295
|
+
The container is `position: fixed` by default. To make the header participate in normal flow (or stick to the top while scrolling):
|
|
197
296
|
|
|
198
|
-
|
|
297
|
+
```astro
|
|
298
|
+
<Header classNames={{ container: "nav-sticky", header: "nav-compact" }} />
|
|
299
|
+
```
|
|
199
300
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
301
|
+
```css
|
|
302
|
+
@layer utilities {
|
|
303
|
+
.nav-sticky {
|
|
304
|
+
position: sticky;
|
|
305
|
+
top: 0;
|
|
306
|
+
}
|
|
205
307
|
|
|
206
|
-
|
|
308
|
+
.nav-compact {
|
|
309
|
+
padding-top: 0.25rem;
|
|
310
|
+
padding-bottom: 0.25rem;
|
|
311
|
+
border-radius: 0;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Example: override internal elements
|
|
317
|
+
|
|
318
|
+
The header renders regular DOM (no shadow root), so any selector reaches the internals. Wrap the header in a class to scope your rules to one page:
|
|
319
|
+
|
|
320
|
+
```astro
|
|
321
|
+
<div class="docs-header">
|
|
322
|
+
<Header navigation={{ menuItems }} />
|
|
323
|
+
</div>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```css
|
|
327
|
+
@layer utilities {
|
|
328
|
+
/* Typography of the desktop links */
|
|
329
|
+
.docs-header .menu__link {
|
|
330
|
+
text-transform: uppercase;
|
|
331
|
+
letter-spacing: 0.02em;
|
|
332
|
+
font-size: 0.875rem;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* Dropdown surfaces */
|
|
336
|
+
.docs-header .submenu,
|
|
337
|
+
.docs-header .subsubmenu {
|
|
338
|
+
border-radius: 12px;
|
|
339
|
+
padding: 0.75rem;
|
|
340
|
+
box-shadow: 0 12px 32px rgb(0 0 0 / 0.18);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Hover underline color */
|
|
344
|
+
.docs-header .menu__link::after {
|
|
345
|
+
background: #7c3aed;
|
|
346
|
+
height: 3px;
|
|
347
|
+
top: 30px;
|
|
348
|
+
}
|
|
207
349
|
|
|
208
|
-
|
|
350
|
+
/* Active link in the mobile panel */
|
|
351
|
+
.docs-header .mobile-menu__link.active {
|
|
352
|
+
color: #7c3aed;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
209
356
|
|
|
210
|
-
|
|
357
|
+
Because these live in `utilities`, they beat the component's `components` layer regardless of specificity.
|
|
211
358
|
|
|
212
|
-
|
|
213
|
-
| --------- | --------------------- | --------------------- | ---------------------------------------- |
|
|
214
|
-
| `logo` | Header Desktop/Mobile | Always | Add your custom logo HTML/Components |
|
|
215
|
-
| `actions` | Header & Mobile panel | Responsive visibility | Add action buttons (login, signup, etc.) |
|
|
359
|
+
### Example: custom responsive breakpoint
|
|
216
360
|
|
|
217
|
-
|
|
361
|
+
The desktop nav hides below `768px`. To keep it visible down to `640px`:
|
|
362
|
+
|
|
363
|
+
```css
|
|
364
|
+
@layer utilities {
|
|
365
|
+
@media (width >= 640px) and (width < 768px) {
|
|
366
|
+
.docs-header .header__menu {
|
|
367
|
+
display: flex;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.docs-header .hamburger {
|
|
371
|
+
display: none;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Media queries live inside the rule, so you re-declare the property at the breakpoints you care about; the layer decides which declaration wins.
|
|
378
|
+
|
|
379
|
+
### Example: dark-mode-only tweaks
|
|
380
|
+
|
|
381
|
+
```css
|
|
382
|
+
@layer utilities {
|
|
383
|
+
:root.dark .docs-header .header {
|
|
384
|
+
--d-bg: rgb(17 24 39 / 0.85);
|
|
385
|
+
box-shadow: 0 8px 30px rgb(0 0 0 / 0.35);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Example: colors with CSS variables
|
|
391
|
+
|
|
392
|
+
See [Customization & Theme Config](#customization--theme-config) for the full variable reference. Global (whole site):
|
|
393
|
+
|
|
394
|
+
```css
|
|
395
|
+
:root {
|
|
396
|
+
--l-accent: #7c3aed;
|
|
397
|
+
--l-bg: rgb(255 255 255 / 0.85);
|
|
398
|
+
--d-accent: #a78bfa;
|
|
399
|
+
--d-bg: rgb(10 10 10 / 0.85);
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Scoped to one section (marketing page gets a purple tint, the docs stay neutral):
|
|
404
|
+
|
|
405
|
+
```css
|
|
406
|
+
.marketing-hero {
|
|
407
|
+
--l-bg: rgb(124 58 237 / 0.14);
|
|
408
|
+
--l-blur: blur(28px);
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
```astro
|
|
413
|
+
<section class="marketing-hero">
|
|
414
|
+
<Header navigation={{ menuItems }} />
|
|
415
|
+
</section>
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Variables are inherited, so defining them on any ancestor of the header works.
|
|
419
|
+
|
|
420
|
+
### Example: z-index and per-instance tokens with the `theme` prop
|
|
421
|
+
|
|
422
|
+
The container renders an **inline** `style="z-index: N"`, and inline styles beat any non-`!important` declaration — so `classNames.container="z-50"` (or any z-index utility) will not win. Use the prop:
|
|
218
423
|
|
|
219
424
|
```astro
|
|
220
425
|
---
|
|
221
|
-
import
|
|
426
|
+
import { defaultThemes } from '@sofidevo/astro-dynamic-header';
|
|
222
427
|
|
|
223
|
-
const
|
|
224
|
-
|
|
428
|
+
const theme = {
|
|
429
|
+
light: { ...defaultThemes.light, zIndex: 60 },
|
|
430
|
+
dark: { ...defaultThemes.dark, zIndex: 60 },
|
|
225
431
|
};
|
|
226
432
|
---
|
|
227
433
|
|
|
228
|
-
<Header
|
|
229
|
-
<div slot="actions">
|
|
230
|
-
<button class="login-btn">Login</button>
|
|
231
|
-
</div>
|
|
232
|
-
</Header>
|
|
434
|
+
<Header theme={theme} classNames={{ header: "shadow-2xl" }} />
|
|
233
435
|
```
|
|
234
436
|
|
|
235
|
-
|
|
437
|
+
> [!NOTE]
|
|
438
|
+
> `zIndex` resolves as `theme.light.zIndex ?? theme.dark.zIndex ?? 10`, so setting it in either block is enough. The value is a single inline number; it does not change between light and dark mode.
|
|
236
439
|
|
|
237
|
-
|
|
440
|
+
### Example: mobile panel
|
|
238
441
|
|
|
239
442
|
```astro
|
|
240
|
-
|
|
241
|
-
|
|
443
|
+
<Header classNames={{ mobileNav: "mobile-wide" }} />
|
|
444
|
+
```
|
|
242
445
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
{ link: "/consulting", text: "Consulting" },
|
|
250
|
-
{
|
|
251
|
-
link: "#",
|
|
252
|
-
text: "Web Development",
|
|
253
|
-
submenu: [
|
|
254
|
-
{ link: "/web/frontend", text: "Frontend" },
|
|
255
|
-
{ link: "/web/backend", text: "Backend" },
|
|
256
|
-
{ link: "/web/fullstack", text: "Full Stack" },
|
|
257
|
-
],
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
},
|
|
261
|
-
{ link: "/about", text: "About" },
|
|
262
|
-
{ link: "/contact", text: "Contact" },
|
|
263
|
-
];
|
|
446
|
+
```css
|
|
447
|
+
@layer utilities {
|
|
448
|
+
/* Full-bleed panel instead of the default over-wide slide-in */
|
|
449
|
+
.mobile-wide {
|
|
450
|
+
width: 100vw;
|
|
451
|
+
}
|
|
264
452
|
|
|
265
|
-
|
|
266
|
-
|
|
453
|
+
.mobile-wide.is-active {
|
|
454
|
+
transform: translateX(0);
|
|
455
|
+
}
|
|
267
456
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
--l-bg: rgba(255, 255, 255, 0.8);
|
|
273
|
-
|
|
274
|
-
--d-accent: #00ffff;
|
|
275
|
-
--d-bg: rgba(20, 20, 20, 0.9);
|
|
457
|
+
/* Softer surface + accent links */
|
|
458
|
+
.mobile-wide .mobile-menu li a {
|
|
459
|
+
border-color: rgb(124 58 237 / 0.35);
|
|
460
|
+
font-size: 1.1rem;
|
|
276
461
|
}
|
|
277
|
-
|
|
462
|
+
}
|
|
463
|
+
```
|
|
278
464
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
mobileNav: "backdrop-blur-md",
|
|
291
|
-
}}
|
|
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>
|
|
297
|
-
<button slot="actions">Login</button>
|
|
298
|
-
</Header>
|
|
465
|
+
If you prefer the fullscreen slide behavior everywhere, render with `headerType="fullscreen"` — the panel then uses `translate(0)` when open.
|
|
466
|
+
|
|
467
|
+
### Example: hamburger and icons
|
|
468
|
+
|
|
469
|
+
The hamburger lines follow the header text color (`--l-text` / `--d-text`), so recoloring the text recolors the lines:
|
|
470
|
+
|
|
471
|
+
```css
|
|
472
|
+
:root {
|
|
473
|
+
--l-text: #111827;
|
|
474
|
+
--d-text: #f9fafb;
|
|
475
|
+
}
|
|
299
476
|
```
|
|
300
477
|
|
|
301
|
-
|
|
478
|
+
Shape and size of the button itself:
|
|
302
479
|
|
|
303
|
-
|
|
480
|
+
```astro
|
|
481
|
+
<Header classNames={{ header: "header-round-btn" }} />
|
|
482
|
+
```
|
|
304
483
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
-
|
|
308
|
-
-
|
|
484
|
+
```css
|
|
485
|
+
@layer utilities {
|
|
486
|
+
#hamburger-btn {
|
|
487
|
+
border-radius: 999px;
|
|
488
|
+
padding: 0.6rem 0.9rem;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
The sun and chevron icons use `currentColor`, so they follow the surrounding text color automatically. The moon glyph uses `var(--svg-color--fff, #fff)` instead, so it stays white unless you override that variable.
|
|
494
|
+
|
|
495
|
+
### Internal class hooks
|
|
309
496
|
|
|
310
|
-
|
|
497
|
+
| Element | Classes / id |
|
|
498
|
+
| --- | --- |
|
|
499
|
+
| Outer wrapper | `.header__container`, `.header__container--floating`, `.header__container--fullscreen` |
|
|
500
|
+
| Header bar | `.header`, `.header--floating`, `.header--fullscreen`, `.header--force-light`, `.header--force-dark` |
|
|
501
|
+
| Desktop nav wrapper | `.nav-menu-wrapper` |
|
|
502
|
+
| Desktop nav | `#header-menu`, `.header__menu`, `.menu`, `.menu__item`, `.header__item`, `.menu__link` |
|
|
503
|
+
| Dropdowns | `.submenu`, `.subsubmenu`, `.submenu__item--secondary`, `.submenu__item--tertiary` |
|
|
504
|
+
| Hamburger | `#hamburger-btn`, `.hamburger`, `.hamburger-box`, `.hamburger-inner` |
|
|
505
|
+
| Mobile panel | `#mobile-header-menu`, `.mobile-header__menu`, `.mobile-header__menu--floating`, `.mobile-header__menu--fullscreen` |
|
|
506
|
+
| Mobile list | `.mobile-menu`, `.mobile-menu__link`, `.mobile-details`, `.menu__summary`, `.mobile-submenu`, `.mobile-subsubmenu` |
|
|
507
|
+
| Slot wrappers | `.actions-desktop`, `.actions-mobile` |
|
|
311
508
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
-
|
|
315
|
-
-
|
|
509
|
+
### Styling caveats
|
|
510
|
+
|
|
511
|
+
- **z-index is inline.** Use `theme.zIndex`, not a utility class, on `container`.
|
|
512
|
+
- **`!important` in a layer still works** (important declarations reverse layer order), but you should not need it.
|
|
513
|
+
- **Unlayered CSS beats everything layered.** If a global rule seems "too strong", that is why — move it into `@layer base`.
|
|
514
|
+
- **Astro scopes component styles with `data-astro-cid-*` attributes.** Your selectors do not need them; plain class selectors work.
|
|
515
|
+
|
|
516
|
+
---
|
|
316
517
|
|
|
317
|
-
##
|
|
518
|
+
## Customization & Theme Config
|
|
318
519
|
|
|
319
|
-
|
|
520
|
+
You can fully customize the color scheme using **CSS Custom Properties** (recommended) or the `theme` prop.
|
|
521
|
+
|
|
522
|
+
### CSS variable reference
|
|
523
|
+
|
|
524
|
+
Input variables (set them wherever the header lives — `:root`, a wrapper, or the `theme` prop):
|
|
525
|
+
|
|
526
|
+
| Variable | Used for | Light default | Dark default |
|
|
527
|
+
| --- | --- | --- | --- |
|
|
528
|
+
| `--l-bg` / `--d-bg` | Header background (translucent) | `rgba(255, 255, 255, 0.9)` | `#0d0d0dcc` |
|
|
529
|
+
| `--l-bg-opaque` / `--d-bg-opaque` | Solid background of dropdowns and the mobile panel | `rgb(255, 255, 255)` | `#0d0d0d` |
|
|
530
|
+
| `--l-text` / `--d-text` | Text, hamburger lines, icons | `#1a1a1a` | `#ffffff` |
|
|
531
|
+
| `--l-accent` / `--d-accent` | Hover underline, active links, dashed borders | `#3e1c71` | `#00ffff` |
|
|
532
|
+
| `--l-blur` / `--d-blur` | `backdrop-filter` value | `blur(20px)` | `blur(20px)` |
|
|
533
|
+
|
|
534
|
+
Derived variables (resolved by the component per theme state; override them only if you need to target internals directly):
|
|
535
|
+
|
|
536
|
+
| Variable | Resolves to |
|
|
537
|
+
| --- | --- |
|
|
538
|
+
| `--bg-color` | `--l-bg` or `--d-bg` |
|
|
539
|
+
| `--bg-color-opaque` | `--l-bg-opaque` or `--d-bg-opaque` |
|
|
540
|
+
| `--text-color` | `--l-text` or `--d-text` |
|
|
541
|
+
| `--accent-color` | `--l-accent` or `--d-accent` |
|
|
542
|
+
| `--backdrop-blur` | `--l-blur` or `--d-blur` |
|
|
543
|
+
|
|
544
|
+
### Option 1: Native CSS Variables (Recommended)
|
|
320
545
|
|
|
321
546
|
```css
|
|
322
547
|
:root {
|
|
323
|
-
|
|
324
|
-
--
|
|
325
|
-
--
|
|
548
|
+
/* Light theme overrides */
|
|
549
|
+
--l-accent: #7c3aed;
|
|
550
|
+
--l-bg: rgba(255, 255, 255, 0.85);
|
|
551
|
+
--l-bg-opaque: #ffffff;
|
|
552
|
+
--l-text: #1a1a1a;
|
|
553
|
+
--l-blur: blur(20px);
|
|
554
|
+
|
|
555
|
+
/* Dark theme overrides */
|
|
556
|
+
--d-accent: #a78bfa;
|
|
557
|
+
--d-bg: rgba(10, 10, 10, 0.85);
|
|
558
|
+
--d-bg-opaque: #0a0a0a;
|
|
559
|
+
--d-text: #f5f5f5;
|
|
560
|
+
--d-blur: blur(20px);
|
|
326
561
|
}
|
|
327
562
|
```
|
|
328
563
|
|
|
329
|
-
|
|
564
|
+
The hamburger lines and the sun/chevron icons follow `--l-text` / `--d-text`, so text color drives them too. The moon glyph keeps its own white fill (`--svg-color--fff`, default `#fff`).
|
|
330
565
|
|
|
331
|
-
|
|
566
|
+
### Option 2: JS Theme Prop
|
|
332
567
|
|
|
333
568
|
```astro
|
|
334
569
|
---
|
|
335
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
336
570
|
import { defaultThemes } from '@sofidevo/astro-dynamic-header';
|
|
571
|
+
|
|
572
|
+
const theme = {
|
|
573
|
+
light: {
|
|
574
|
+
...defaultThemes.light,
|
|
575
|
+
accentColor: "#7c3aed",
|
|
576
|
+
backgroundColor: "rgba(255, 255, 255, 0.85)",
|
|
577
|
+
},
|
|
578
|
+
dark: {
|
|
579
|
+
...defaultThemes.dark,
|
|
580
|
+
accentColor: "#a78bfa",
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
<Header theme={theme} />
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
The prop writes the variables as inline styles on the header element, so it wins over `:root` values for that instance.
|
|
589
|
+
|
|
590
|
+
> [!IMPORTANT]
|
|
591
|
+
> When using transparent backgrounds, always supply a solid fallback in `backgroundColorOpaque`. Submenus and mobile panels utilize this solid color to prevent visual glitches with nested blur effects.
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## TypeScript
|
|
596
|
+
|
|
597
|
+
```astro
|
|
598
|
+
---
|
|
599
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
337
600
|
import type {
|
|
601
|
+
MenuItem,
|
|
338
602
|
NavConfig,
|
|
339
|
-
DualThemeConfig,
|
|
340
603
|
HeaderClassNames,
|
|
341
|
-
|
|
342
|
-
SecondaryMenuItem
|
|
604
|
+
HeaderProps,
|
|
343
605
|
} from '@sofidevo/astro-dynamic-header';
|
|
344
606
|
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
link: '/products',
|
|
349
|
-
text: 'Products',
|
|
350
|
-
submenu: [
|
|
351
|
-
{ link: '/software', text: 'Software' },
|
|
352
|
-
{ link: '/hardware', text: 'Hardware' }
|
|
353
|
-
]
|
|
354
|
-
}
|
|
355
|
-
],
|
|
356
|
-
header__menu__class: "flex gap-6",
|
|
357
|
-
};
|
|
607
|
+
const menuItems: MenuItem[] = [
|
|
608
|
+
{ link: '/about', text: 'About' }
|
|
609
|
+
];
|
|
358
610
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
dark: { ...defaultThemes.dark, accentColor: "#00ffff", backgroundColor: "rgba(10, 10, 10, 0.9)" },
|
|
611
|
+
const navigation: NavConfig = {
|
|
612
|
+
menuItems,
|
|
613
|
+
homeUrl: "/",
|
|
614
|
+
header__menu__class: "flex gap-4"
|
|
364
615
|
};
|
|
365
616
|
|
|
366
617
|
const classNames: HeaderClassNames = {
|
|
367
|
-
header: "shadow-
|
|
368
|
-
mobileNav: "backdrop-blur-md",
|
|
618
|
+
header: "shadow-lg"
|
|
369
619
|
};
|
|
370
620
|
---
|
|
371
621
|
|
|
372
622
|
<Header
|
|
373
623
|
navigation={navigation}
|
|
374
624
|
classNames={classNames}
|
|
375
|
-
preset="auto"
|
|
376
625
|
/>
|
|
377
626
|
```
|
|
378
627
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
| Type | Description |
|
|
382
|
-
| ------------------- | ----------------------------------------------- |
|
|
383
|
-
| `MenuItem` | Top-level menu item with optional properties |
|
|
384
|
-
| `SecondaryMenuItem` | Second-level menu item |
|
|
385
|
-
| `TertiaryMenuItem` | Third-level menu item |
|
|
386
|
-
| `NavConfig` | Navigation config (items + nested class props) |
|
|
387
|
-
| `ThemeConfig` | Individual theme settings (colors, blur, etc.) |
|
|
388
|
-
| `DualThemeConfig` | Combined settings for light and dark modes |
|
|
389
|
-
| `HeaderClassNames` | Class overrides for structural wrapper elements |
|
|
390
|
-
| `CustomClassNames` | **Deprecated alias** for `HeaderClassNames` |
|
|
391
|
-
| `HeaderProps` | Main props for the Header component |
|
|
392
|
-
|
|
393
|
-
## Browser Support
|
|
394
|
-
|
|
395
|
-
- All modern browsers (Chrome, Firefox, Safari, Edge)
|
|
396
|
-
- Mobile responsive design with optimized touch targets
|
|
397
|
-
- Supports CSS `backdrop-filter` for glassmorphism
|
|
398
|
-
- Automatic theme switching based on OS or site preference via `.dark` class
|
|
399
|
-
|
|
400
|
-
## Troubleshooting
|
|
401
|
-
|
|
402
|
-
### Import Issues
|
|
403
|
-
|
|
404
|
-
If you encounter import errors, try these solutions:
|
|
405
|
-
|
|
406
|
-
1. **Use direct subpath import:**
|
|
407
|
-
|
|
408
|
-
```astro
|
|
409
|
-
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
2. **Check relative imports in TS:**
|
|
413
|
-
In some environments (like `node16`), you might need to use the `.js` extension even for TypeScript files when importing from the package internals, though the main entry point handles this for you.
|
|
414
|
-
|
|
415
|
-
3. **Verify TypeScript configuration:**
|
|
628
|
+
---
|
|
416
629
|
|
|
417
|
-
|
|
418
|
-
// tsconfig.json
|
|
419
|
-
{
|
|
420
|
-
"compilerOptions": {
|
|
421
|
-
"moduleResolution": "bundler",
|
|
422
|
-
"allowImportingTsExtensions": true
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
```
|
|
630
|
+
## Development
|
|
426
631
|
|
|
427
|
-
|
|
632
|
+
```bash
|
|
633
|
+
pnpm install
|
|
634
|
+
pnpm dev # demo at localhost:4321
|
|
635
|
+
pnpm check # astro check (0 errors expected)
|
|
636
|
+
pnpm test # vitest: unit + rendering + built-CSS tests
|
|
637
|
+
pnpm build # builds the demo site
|
|
638
|
+
```
|
|
428
639
|
|
|
429
|
-
|
|
430
|
-
- SSG Projects (Static Site Generation)
|
|
431
|
-
- SSR Projects (Server-Side Rendering)
|
|
432
|
-
- Hybrid Projects (output: 'hybrid')
|
|
640
|
+
The test suite runs in two Vitest projects: `dom` (jsdom, exercises the extracted behaviour scripts) and `astro` (Node, renders `Header.astro` through Astro's Container API and asserts the built CSS contract — layer order, rules inside `@layer components`, no `!important`). See `tests/README.md`.
|
|
433
641
|
|
|
434
|
-
|
|
642
|
+
---
|
|
435
643
|
|
|
436
|
-
|
|
644
|
+
## Troubleshooting & FAQ
|
|
437
645
|
|
|
438
|
-
|
|
646
|
+
### Import issues
|
|
439
647
|
|
|
440
|
-
|
|
648
|
+
Import using the direct subpath:
|
|
441
649
|
|
|
442
|
-
|
|
650
|
+
```astro
|
|
651
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
652
|
+
```
|
|
443
653
|
|
|
444
|
-
|
|
654
|
+
Other components are exported similarly:
|
|
445
655
|
|
|
446
|
-
|
|
656
|
+
```astro
|
|
657
|
+
import NavMenu from '@sofidevo/astro-dynamic-header/NavMenu';
|
|
658
|
+
import MobileNav from '@sofidevo/astro-dynamic-header/MobileNav';
|
|
659
|
+
import ChevronIcon from '@sofidevo/astro-dynamic-header/ChevronIcon';
|
|
660
|
+
```
|
|
447
661
|
|
|
448
|
-
|
|
662
|
+
### My overrides do not apply
|
|
449
663
|
|
|
450
|
-
|
|
664
|
+
Check these in order:
|
|
451
665
|
|
|
452
|
-
|
|
666
|
+
1. **Which layer is your rule in?** Overrides belong in `@layer utilities`, or in a class passed through `classNames`. Rules in `@layer base` (and your resets) lose to the component by design.
|
|
667
|
+
2. **Are you fighting the inline z-index?** `container` always renders `style="z-index: N"`. Use `theme.zIndex` instead of a z-index utility.
|
|
668
|
+
3. **Are you selecting the right hook?** See the [internal class hooks](#internal-class-hooks) table; plain class selectors are enough (you do not need `data-astro-cid-*`).
|
|
669
|
+
4. **Is an inline style winning?** Inline styles beat every non-`!important` declaration, layered or not. The header renders inline styles for `z-index` and for any `theme` values you pass.
|
|
670
|
+
5. **Tailwind v4?** Its layer order matches this component exactly, so utilities work automatically. Make sure `@import "tailwindcss"` comes first in your entry CSS.
|
|
453
671
|
|
|
454
|
-
|
|
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.
|
|
672
|
+
### Preflight changed my nav link colors (Tailwind v3)
|
|
456
673
|
|
|
457
|
-
|
|
674
|
+
Tailwind v3 emits Preflight and utilities **outside** native cascade layers (v3 hijacks `@layer` as its own directive), and CSS outside a layer beats CSS inside a layer. Preflight's `a { color: inherit; ... }` therefore overrides the component's layered link colors.
|
|
458
675
|
|
|
459
|
-
|
|
460
|
-
- **Dead Code Elimination**: Cleaned up repetitive prop destructuring inside `MobileNav.astro`.
|
|
676
|
+
Options:
|
|
461
677
|
|
|
462
|
-
|
|
678
|
+
1. **Upgrade to Tailwind v4** (recommended) — native layers with the exact order `theme, base, components, utilities`, so everything lines up.
|
|
679
|
+
2. **Disable Preflight** in v3: `module.exports = { corePlugins: { preflight: false } }`.
|
|
680
|
+
3. **Compensate** with your own unlayered rule (unlayered wins):
|
|
463
681
|
|
|
464
|
-
|
|
682
|
+
```css
|
|
683
|
+
.header__menu a,
|
|
684
|
+
.mobile-menu a {
|
|
685
|
+
color: var(--accent-color, #3e1c71);
|
|
686
|
+
}
|
|
687
|
+
```
|
|
465
688
|
|
|
466
|
-
|
|
467
|
-
New `mobileNav` slot targets the mobile slide-in `<nav>` panel.
|
|
689
|
+
Note that v3 *utilities* still work as expected, because unlayered utilities also beat the component.
|
|
468
690
|
|
|
469
|
-
|
|
470
|
-
| Prop | Targets |
|
|
471
|
-
|------|---------|
|
|
472
|
-
| `logo__container__class` | Logo `<a>` wrapper |
|
|
473
|
-
| `logo__text__class` | Logo text `<span>` |
|
|
691
|
+
### Accordion icons not showing up
|
|
474
692
|
|
|
475
|
-
|
|
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>` |
|
|
693
|
+
Icons are rendered as inline SVG components. If you are upgrading from `v1.x` or `v2.0` and have the old ChevronIcon CDN `<script>` tag in your layout `<head>`, you can safely remove it.
|
|
481
694
|
|
|
482
|
-
|
|
483
|
-
```ts
|
|
484
|
-
import { defaultThemes } from "@sofidevo/astro-dynamic-header";
|
|
485
|
-
const theme = { dark: { ...defaultThemes.dark, accentColor: "#f43f5e" } };
|
|
486
|
-
```
|
|
695
|
+
### The header sits behind my other content
|
|
487
696
|
|
|
488
|
-
|
|
697
|
+
Set a z-index through the `theme` prop (`theme={{ light: { zIndex: 60 } }}`), not through a class on `container`. See [z-index and per-instance tokens](#example-z-index-and-per-instance-tokens-with-the-theme-prop).
|
|
489
698
|
|
|
490
|
-
|
|
491
|
-
- `HamburgerButton` color is now explicitly wired from the resolved theme (no visual change).
|
|
699
|
+
---
|
|
492
700
|
|
|
493
|
-
|
|
701
|
+
## Compatibility
|
|
494
702
|
|
|
495
|
-
-
|
|
703
|
+
- **Astro 7.x** (peer dependency), SSG, SSR, and CSS builds (no client framework required).
|
|
704
|
+
- **CSS cascade layers** require a modern browser: Chrome/Edge 99+, Firefox 97+, Safari 15.4+.
|
|
705
|
+
- Works with Tailwind v4 out of the box; Tailwind v3 works with the Preflight caveat above.
|
|
496
706
|
|
|
497
|
-
|
|
707
|
+
---
|
|
498
708
|
|
|
499
|
-
|
|
500
|
-
> Breaking change: `logo` changed from `string` to `LogoConfig`, `navigation` changed from `MenuItem[]` to `NavConfig`.
|
|
709
|
+
## License
|
|
501
710
|
|
|
502
|
-
|
|
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`).
|
|
711
|
+
MIT License.
|