@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sofidevo/astro-dynamic-header",
3
- "version": "3.0.1",
3
+ "version": "4.0.0",
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",
@@ -14,25 +14,20 @@
14
14
  "./NavMenu": "./src/NavMenu.astro",
15
15
  "./MobileNav": "./src/MobileNav.astro",
16
16
  "./HamburgerButton": "./src/HamburgerButton.astro",
17
+ "./ChevronIcon": "./src/ChevronIcon.astro",
18
+ "./defaults": "./src/defaults.ts",
17
19
  "./Header.astro": "./src/Header.astro",
18
20
  "./NavMenu.astro": "./src/NavMenu.astro",
19
21
  "./MobileNav.astro": "./src/MobileNav.astro",
20
- "./HamburgerButton.astro": "./src/HamburgerButton.astro"
22
+ "./HamburgerButton.astro": "./src/HamburgerButton.astro",
23
+ "./ChevronIcon.astro": "./src/ChevronIcon.astro"
21
24
  },
22
25
  "files": [
23
26
  "src/*.ts",
27
+ "src/scripts/*.ts",
24
28
  "src/*.astro",
25
29
  "README.md"
26
30
  ],
27
- "scripts": {
28
- "build": "astro build",
29
- "dev": "astro dev",
30
- "check": "astro check",
31
- "check:watch": "astro check --watch",
32
- "test": "vitest run",
33
- "test:watch": "vitest",
34
- "test:coverage": "vitest run --coverage"
35
- },
36
31
  "keywords": [
37
32
  "astro",
38
33
  "component",
@@ -50,13 +45,13 @@
50
45
  "author": "SofiDev contact@sofidev.top",
51
46
  "license": "MIT",
52
47
  "peerDependencies": {
53
- "astro": "^6.0.6"
48
+ "astro": "^7.0.0"
54
49
  },
55
50
  "devDependencies": {
56
- "@astrojs/check": "^0.9.9",
51
+ "@astrojs/check": "^0.9.10",
57
52
  "@types/jsdom": "^21.1.7",
58
53
  "@vitest/coverage-v8": "^3.2.4",
59
- "astro": "^6.3.7",
54
+ "astro": "^7.3.6",
60
55
  "jsdom": "^26.1.0",
61
56
  "typescript": "^5.0.0",
62
57
  "vitest": "^3.2.4"
@@ -68,5 +63,14 @@
68
63
  "bugs": {
69
64
  "url": "https://github.com/SofiDevO/astro-dynamic-header/issues"
70
65
  },
71
- "homepage": "https://base-astro-psi.vercel.app/fullscreen-demo"
72
- }
66
+ "homepage": "https://base-astro-psi.vercel.app/fullscreen-demo",
67
+ "scripts": {
68
+ "build": "astro build",
69
+ "dev": "astro dev",
70
+ "check": "astro check",
71
+ "check:watch": "astro check --watch",
72
+ "test": "vitest run",
73
+ "test:watch": "vitest",
74
+ "test:coverage": "vitest run --coverage"
75
+ }
76
+ }
@@ -0,0 +1,33 @@
1
+ ---
2
+ const { class: className, ...rest } = Astro.props;
3
+
4
+ const viewBox = "0 0 24 24";
5
+ const content = `<path class="hlhawq"/>`;
6
+ ---
7
+
8
+ <svg
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ width="1em"
11
+ height="1em"
12
+ class={className}
13
+ viewBox={viewBox}
14
+ {...rest}
15
+ set:html={content}
16
+ />
17
+
18
+ <style is:inline>
19
+ /*
20
+ * base (resets) < components (ours) < utilities (consumer classNames).
21
+ * Order statement keeps it deterministic regardless of bundling order.
22
+ */
23
+ @layer theme, base, components, utilities;
24
+
25
+ @layer components {
26
+ .hlhawq {
27
+ fill: currentColor;
28
+ d: path(
29
+ "M11.646 15.146L5.854 9.354a.5.5 0 0 1 .353-.854h11.586a.5.5 0 0 1 .353.854l-5.793 5.792a.5.5 0 0 1-.707 0"
30
+ );
31
+ }
32
+ }
33
+ </style>
@@ -13,110 +13,118 @@ export interface Props {}
13
13
  </button>
14
14
 
15
15
  <style>
16
- .hamburger {
17
- display: none;
18
- font: inherit;
19
- overflow: visible;
20
- margin: 0;
21
- padding: 10px;
22
- cursor: pointer;
23
- transition-timing-function: linear;
24
- transition-duration: 0.15s;
25
- transition-property: opacity, filter, border-color;
26
- text-transform: none;
27
- color: inherit;
28
- border: 1px solid
29
- color-mix(in srgb, var(--text-color, #fff) 25%, transparent);
30
- border-radius: 8px;
31
- background-color: transparent;
32
-
33
- @media screen and (max-width: 768px) {
34
- z-index: 1000;
35
- display: inline-block;
16
+ /*
17
+ * base (resets) < components (ours) < utilities (consumer classNames).
18
+ * Order statement keeps it deterministic regardless of bundling order.
19
+ */
20
+ @layer theme, base, components, utilities;
21
+
22
+ @layer components {
23
+ .hamburger {
24
+ display: none;
25
+ font: inherit;
26
+ overflow: visible;
27
+ margin: 0;
28
+ padding: 10px;
29
+ cursor: pointer;
30
+ transition-timing-function: linear;
31
+ transition-duration: 0.15s;
32
+ transition-property: opacity, filter, border-color;
33
+ text-transform: none;
34
+ color: inherit;
35
+ border: 1px solid
36
+ color-mix(in srgb, var(--text-color, #fff) 25%, transparent);
37
+ border-radius: 8px;
38
+ background-color: transparent;
39
+
40
+ @media screen and (max-width: 768px) {
41
+ z-index: 1000;
42
+ display: inline-block;
43
+ }
36
44
  }
37
- }
38
45
 
39
- .hamburger-box {
40
- position: relative;
41
- display: inline-block;
42
- width: 40px;
43
- height: 24px;
44
- }
46
+ .hamburger-box {
47
+ position: relative;
48
+ display: inline-block;
49
+ width: 40px;
50
+ height: 24px;
51
+ }
45
52
 
46
- .hamburger-inner {
47
- top: 50%;
48
- display: block;
49
- margin-top: -2px;
50
- }
53
+ .hamburger-inner {
54
+ top: 50%;
55
+ display: block;
56
+ margin-top: -2px;
57
+ }
51
58
 
52
- .hamburger-inner:after,
53
- .hamburger-inner:before {
54
- display: block;
55
- content: "";
56
- }
59
+ .hamburger-inner:after,
60
+ .hamburger-inner:before {
61
+ display: block;
62
+ content: "";
63
+ }
57
64
 
58
- .hamburger-inner,
59
- .hamburger-inner::before,
60
- .hamburger-inner::after {
61
- background-color: var(--text-color, inherit);
62
- position: absolute;
63
- width: 40px;
64
- height: 4px;
65
- transition-timing-function: ease;
66
- transition-duration: 0.15s;
67
- transition-property: transform;
68
- border-radius: 4px;
69
- }
65
+ .hamburger-inner,
66
+ .hamburger-inner::before,
67
+ .hamburger-inner::after {
68
+ background-color: var(--text-color, inherit);
69
+ position: absolute;
70
+ width: 40px;
71
+ height: 4px;
72
+ transition-timing-function: ease;
73
+ transition-duration: 0.15s;
74
+ transition-property: transform;
75
+ border-radius: 4px;
76
+ }
70
77
 
71
- .hamburger-inner:before {
72
- top: -10px;
73
- }
78
+ .hamburger-inner:before {
79
+ top: -10px;
80
+ }
74
81
 
75
- .hamburger-inner:after {
76
- bottom: -10px;
77
- }
82
+ .hamburger-inner:after {
83
+ bottom: -10px;
84
+ }
78
85
 
79
- .hamburger--collapse .hamburger-inner {
80
- top: auto;
81
- bottom: 0;
82
- transition-delay: 0.13s;
83
- transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
84
- transition-duration: 0.13s;
85
- }
86
+ .hamburger--collapse .hamburger-inner {
87
+ top: auto;
88
+ bottom: 0;
89
+ transition-delay: 0.13s;
90
+ transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
91
+ transition-duration: 0.13s;
92
+ }
86
93
 
87
- .hamburger--collapse .hamburger-inner:before {
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);
91
- }
94
+ .hamburger--collapse .hamburger-inner:before {
95
+ transition:
96
+ top 0.12s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s,
97
+ transform 0.13s cubic-bezier(0.55, 0.055, 0.675, 0.19);
98
+ }
92
99
 
93
- .hamburger--collapse .hamburger-inner:after {
94
- top: -20px;
95
- transition:
96
- top 0.2s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s,
97
- opacity 0.1s linear;
98
- }
100
+ .hamburger--collapse .hamburger-inner:after {
101
+ top: -20px;
102
+ transition:
103
+ top 0.2s cubic-bezier(0.33333, 0.66667, 0.66667, 1) 0.2s,
104
+ opacity 0.1s linear;
105
+ }
99
106
 
100
- /* Animation */
101
- .hamburger--collapse.is-active .hamburger-inner {
102
- transition-delay: 0.22s;
103
- transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
104
- transform: translate3d(0, -10px, 0) rotate(-45deg);
105
- }
107
+ /* Animation */
108
+ .hamburger--collapse.is-active .hamburger-inner {
109
+ transition-delay: 0.22s;
110
+ transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
111
+ transform: translate3d(0, -10px, 0) rotate(-45deg);
112
+ }
106
113
 
107
- .hamburger--collapse.is-active .hamburger-inner:before {
108
- top: 0;
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;
112
- transform: rotate(-90deg);
113
- }
114
+ .hamburger--collapse.is-active .hamburger-inner:before {
115
+ top: 0;
116
+ transition:
117
+ top 0.1s cubic-bezier(0.33333, 0, 0.66667, 0.33333) 0.16s,
118
+ transform 0.13s cubic-bezier(0.215, 0.61, 0.355, 1) 0.25s;
119
+ transform: rotate(-90deg);
120
+ }
114
121
 
115
- .hamburger--collapse.is-active .hamburger-inner:after {
116
- top: 0;
117
- transition:
118
- top 0.2s cubic-bezier(0.33333, 0, 0.66667, 0.33333),
119
- opacity 0.1s linear 0.22s;
120
- opacity: 0;
122
+ .hamburger--collapse.is-active .hamburger-inner:after {
123
+ top: 0;
124
+ transition:
125
+ top 0.2s cubic-bezier(0.33333, 0, 0.66667, 0.33333),
126
+ opacity 0.1s linear 0.22s;
127
+ opacity: 0;
128
+ }
121
129
  }
122
130
  </style>
package/src/Header.astro CHANGED
@@ -10,32 +10,10 @@ import type {
10
10
  } from "./index.js";
11
11
 
12
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
- */
19
13
  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
- */
27
14
  preset?: "light" | "dark" | "auto";
28
-
29
- /** Navigation links and structure. */
30
15
  navigation?: NavConfig;
31
- /** Custom theme overrides for light and dark modes. */
32
16
  theme?: DualThemeConfig;
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
17
  classNames?: HeaderClassNames;
40
18
  }
41
19
 
@@ -57,7 +35,7 @@ if (import.meta.env.DEV) {
57
35
  }
58
36
  }
59
37
 
60
- // Map user theme overrides to CSS custom properties
38
+
61
39
  const inlineStyles: Record<string, string> = {};
62
40
 
63
41
  if (theme.light) {
@@ -75,7 +53,7 @@ if (theme.dark) {
75
53
  if (theme.dark.backdropBlur) inlineStyles["--d-blur"] = theme.dark.backdropBlur;
76
54
  }
77
55
 
78
- const zIndex = theme.light?.zIndex ?? 10;
56
+ const zIndex = theme.light?.zIndex ?? theme.dark?.zIndex ?? 10;
79
57
 
80
58
  // Navigation configuration
81
59
  const {
@@ -92,45 +70,9 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
92
70
  ---
93
71
 
94
72
  <script>
95
- class HamburgerController {
96
- private hamburgerBtn: HTMLElement | null;
97
- private mobileMenu: HTMLElement | null;
98
- private links: NodeListOf<HTMLElement>;
99
-
100
- constructor() {
101
- this.hamburgerBtn = document.getElementById("hamburger-btn");
102
- this.mobileMenu = document.getElementById("mobile-header-menu");
103
- this.links = document.querySelectorAll(".mobile-menu__link");
104
- this.init();
105
- }
106
-
107
- private init(): void {
108
- if (this.hamburgerBtn && this.mobileMenu) {
109
- this.hamburgerBtn.addEventListener("click", () => this.toggleMenu());
110
- this.links.forEach((link) => {
111
- link.addEventListener("click", () => this.closeMenu());
112
- });
113
- }
114
- }
115
-
116
- private closeMenu(): void {
117
- if (this.hamburgerBtn && this.mobileMenu) {
118
- this.hamburgerBtn.classList.remove("is-active");
119
- this.mobileMenu.classList.remove("is-active");
120
- }
121
- }
122
-
123
- private toggleMenu(): void {
124
- if (this.hamburgerBtn && this.mobileMenu) {
125
- this.hamburgerBtn.classList.toggle("is-active");
126
- this.mobileMenu.classList.toggle("is-active");
127
- }
128
- }
129
- }
73
+ import { initHamburger } from "./scripts/hamburger.js";
130
74
 
131
- document.addEventListener("DOMContentLoaded", () => {
132
- new HamburgerController();
133
- });
75
+ initHamburger();
134
76
  </script>
135
77
 
136
78
  <div
@@ -155,6 +97,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
155
97
  <div class:list={["nav-menu-wrapper", classNames.nav]}>
156
98
  <NavMenu
157
99
  menuItems={menuItems}
100
+ homeUrl={homeUrl}
158
101
  header__menu__class={header__menu__class}
159
102
  header__item__class={header__item__class}
160
103
  menu__link__class={menu__link__class}
@@ -173,6 +116,7 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
173
116
 
174
117
  <MobileNav
175
118
  menuItems={menuItems}
119
+ homeUrl={homeUrl}
176
120
  type={headerType}
177
121
  mobileNav__class={classNames.mobileNav}
178
122
  >
@@ -187,118 +131,123 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
187
131
  </header>
188
132
  </div>
189
133
 
190
- <style is:inline>
191
- .header {
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));
197
-
198
- display: flex;
199
- align-items: center;
200
- width: 100%;
201
- justify-content: space-between;
202
- padding: 1em 2em;
203
- background-color: var(--bg-color);
204
- backdrop-filter: var(--backdrop-blur);
205
- -webkit-backdrop-filter: var(--backdrop-blur);
206
- color: var(--text-color);
207
- transition:
208
- background-color 0.3s ease,
209
- color 0.3s ease;
210
-
211
- @media (width < 768px) {
212
- align-self: flex-end;
213
- padding: 0.4em;
214
- margin: 0 auto;
134
+ <style>
135
+ /*
136
+ * Cascade layers keep this component overridable:
137
+ * base (resets/preflight) < components (ours) < utilities (your classNames)
138
+ * The order statement below makes that order deterministic no matter how
139
+ * the consumer's CSS is bundled. No `!important` is needed to override us.
140
+ */
141
+ @layer theme, base, components, utilities;
142
+
143
+ @layer components {
144
+ .header {
145
+ --bg-color: var(--l-bg, rgba(255, 255, 255, 0.9));
146
+ --bg-color-opaque: var(--l-bg-opaque, rgb(255, 255, 255));
147
+ --text-color: var(--l-text, #1a1a1a);
148
+ --accent-color: var(--l-accent, #3e1c71);
149
+ --backdrop-blur: var(--l-blur, blur(20px));
150
+
151
+ display: flex;
152
+ align-items: center;
153
+ width: 100%;
154
+ justify-content: space-between;
155
+ padding: 1em 2em;
156
+ background-color: var(--bg-color);
157
+ backdrop-filter: var(--backdrop-blur);
158
+ -webkit-backdrop-filter: var(--backdrop-blur);
159
+ color: var(--text-color);
160
+ transition:
161
+ background-color 0.3s ease,
162
+ color 0.3s ease;
163
+
164
+ @media (width < 768px) {
165
+ align-self: flex-end;
166
+ padding: 0.4em;
167
+ margin: 0 auto;
168
+ }
215
169
  }
216
- }
217
-
218
- /* Automatic dark mode detection via root class */
219
- :root.dark .header:not(.header--force-light) {
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));
225
- }
226
170
 
227
- /* Forced themes */
228
- .header--force-dark {
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;
234
- }
171
+ /* Automatic dark mode detection via root class */
172
+ :root.dark .header:not(.header--force-light):not(.header--force-dark) {
173
+ --bg-color: var(--d-bg, #0d0d0dcc);
174
+ --bg-color-opaque: var(--d-bg-opaque, #0d0d0d);
175
+ --text-color: var(--d-text, #ffffff);
176
+ --accent-color: var(--d-accent, #00ffff);
177
+ --backdrop-blur: var(--d-blur, blur(20px));
178
+ }
235
179
 
236
- .header--force-light {
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;
242
- }
180
+ /* Forced themes: higher specificity than .header, no !important needed */
181
+ .header.header--force-dark {
182
+ --bg-color: var(--d-bg, #0d0d0dcc);
183
+ --bg-color-opaque: var(--d-bg-opaque, #0d0d0d);
184
+ --text-color: var(--d-text, #ffffff);
185
+ --accent-color: var(--d-accent, #00ffff);
186
+ --backdrop-blur: var(--d-blur, blur(30px));
187
+ }
243
188
 
244
- .header__container {
245
- width: 100%;
246
- display: flex;
247
- justify-content: center;
248
- align-items: center;
249
- margin: 0 auto;
250
- position: fixed;
251
-
252
- @media (width < 768px) {
253
- justify-content: flex-end;
254
- align-items: flex-end;
189
+ .header.header--force-light {
190
+ --bg-color: var(--l-bg, rgba(255, 255, 255, 0.9));
191
+ --bg-color-opaque: var(--l-bg-opaque, rgb(255, 255, 255));
192
+ --text-color: var(--l-text, #1a1a1a);
193
+ --accent-color: var(--l-accent, #3e1c71);
194
+ --backdrop-blur: var(--l-blur, blur(30px));
255
195
  }
256
- }
257
196
 
258
- .header__container--floating {
259
- padding: 0.5em;
260
- }
197
+ .header__container {
198
+ width: 100%;
199
+ display: flex;
200
+ justify-content: center;
201
+ align-items: center;
202
+ margin: 0 auto;
203
+ position: fixed;
261
204
 
262
- .header__container--fullscreen {
263
- padding: 0;
264
- }
205
+ @media (width < 768px) {
206
+ justify-content: flex-end;
207
+ align-items: flex-end;
208
+ }
209
+ }
265
210
 
266
- .header--floating {
267
- max-width: 1100px;
268
- border-radius: 20px;
269
- padding: 0.7em 1em;
211
+ .header__container--floating {
212
+ padding: 0.5em;
213
+ }
270
214
 
271
- @media (width < 768px) {
272
- max-width: 95%;
215
+ .header__container--fullscreen {
216
+ padding: 0;
273
217
  }
274
- }
275
218
 
276
- .header--fullscreen {
277
- border-radius: 0;
278
- padding: 1em 2em;
219
+ .header--floating {
220
+ max-width: 1100px;
221
+ border-radius: 20px;
222
+ padding: 0.7em 1em;
279
223
 
280
- @media (width < 768px) {
281
- max-width: 100%;
282
- padding: 0.8em 1em;
224
+ @media (width < 768px) {
225
+ max-width: 95%;
226
+ }
283
227
  }
284
- }
285
228
 
229
+ .header--fullscreen {
230
+ border-radius: 0;
231
+ padding: 1em 2em;
286
232
 
287
-
288
- .actions-desktop {
289
- @media (width < 768px) {
290
- display: none;
233
+ @media (width < 768px) {
234
+ max-width: 100%;
235
+ padding: 0.8em 1em;
236
+ }
291
237
  }
292
- }
293
238
 
294
- .actions-mobile {
295
- margin-top: 20px;
296
- @media (width >= 768px) {
297
- display: none;
239
+ .actions-desktop {
240
+ @media (width < 768px) {
241
+ display: none;
242
+ }
298
243
  }
299
- }
300
244
 
301
- .header--fullscreen .mobile-header__menu.is-active {
302
- transform: translate(0%);
245
+ .actions-mobile {
246
+ margin-top: 20px;
247
+
248
+ @media (width >= 768px) {
249
+ display: none;
250
+ }
251
+ }
303
252
  }
304
253
  </style>