@sofidevo/astro-dynamic-header 3.1.0 → 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.
@@ -16,10 +16,18 @@ const content = `<path class="hlhawq"/>`;
16
16
  />
17
17
 
18
18
  <style is:inline>
19
- .hlhawq {
20
- fill: currentColor;
21
- d: path(
22
- "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"
23
- );
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
+ }
24
32
  }
25
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
@@ -189,118 +131,123 @@ const forcedClass = preset !== "auto" ? `header--force-${preset}` : "";
189
131
  </header>
190
132
  </div>
191
133
 
192
- <style is:inline>
193
- .header {
194
- --bg-color: var(--l-bg, rgba(255, 255, 255, 0.9));
195
- --bg-color-opaque: var(--l-bg-opaque, rgb(255, 255, 255));
196
- --text-color: var(--l-text, #1a1a1a);
197
- --accent-color: var(--l-accent, #3e1c71);
198
- --backdrop-blur: var(--l-blur, blur(20px));
199
-
200
- display: flex;
201
- align-items: center;
202
- width: 100%;
203
- justify-content: space-between;
204
- padding: 1em 2em;
205
- background-color: var(--bg-color);
206
- backdrop-filter: var(--backdrop-blur);
207
- -webkit-backdrop-filter: var(--backdrop-blur);
208
- color: var(--text-color);
209
- transition:
210
- background-color 0.3s ease,
211
- color 0.3s ease;
212
-
213
- @media (width < 768px) {
214
- align-self: flex-end;
215
- padding: 0.4em;
216
- 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
+ }
217
169
  }
218
- }
219
-
220
- /* Automatic dark mode detection via root class */
221
- :root.dark .header:not(.header--force-light) {
222
- --bg-color: var(--d-bg, #0d0d0dcc);
223
- --bg-color-opaque: var(--d-bg-opaque, #0d0d0d);
224
- --text-color: var(--d-text, #ffffff);
225
- --accent-color: var(--d-accent, #00ffff);
226
- --backdrop-blur: var(--d-blur, blur(20px));
227
- }
228
170
 
229
- /* Forced themes */
230
- .header--force-dark {
231
- --bg-color: var(--d-bg, #0d0d0dcc) !important;
232
- --bg-color-opaque: var(--d-bg-opaque, #0d0d0d) !important;
233
- --text-color: var(--d-text, #ffffff) !important;
234
- --accent-color: var(--d-accent, #00ffff) !important;
235
- --backdrop-blur: var(--d-blur, blur(20px)) !important;
236
- }
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
+ }
237
179
 
238
- .header--force-light {
239
- --bg-color: var(--l-bg, rgba(255, 255, 255, 0.9)) !important;
240
- --bg-color-opaque: var(--l-bg-opaque, rgb(255, 255, 255)) !important;
241
- --text-color: var(--l-text, #1a1a1a) !important;
242
- --accent-color: var(--l-accent, #3e1c71) !important;
243
- --backdrop-blur: var(--l-blur, blur(20px)) !important;
244
- }
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
+ }
245
188
 
246
- .header__container {
247
- width: 100%;
248
- display: flex;
249
- justify-content: center;
250
- align-items: center;
251
- margin: 0 auto;
252
- position: fixed;
253
-
254
- @media (width < 768px) {
255
- justify-content: flex-end;
256
- 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));
257
195
  }
258
- }
259
196
 
260
- .header__container--floating {
261
- padding: 0.5em;
262
- }
197
+ .header__container {
198
+ width: 100%;
199
+ display: flex;
200
+ justify-content: center;
201
+ align-items: center;
202
+ margin: 0 auto;
203
+ position: fixed;
263
204
 
264
- .header__container--fullscreen {
265
- padding: 0;
266
- }
205
+ @media (width < 768px) {
206
+ justify-content: flex-end;
207
+ align-items: flex-end;
208
+ }
209
+ }
267
210
 
268
- .header--floating {
269
- max-width: 1100px;
270
- border-radius: 20px;
271
- padding: 0.7em 1em;
211
+ .header__container--floating {
212
+ padding: 0.5em;
213
+ }
272
214
 
273
- @media (width < 768px) {
274
- max-width: 95%;
215
+ .header__container--fullscreen {
216
+ padding: 0;
275
217
  }
276
- }
277
218
 
278
- .header--fullscreen {
279
- border-radius: 0;
280
- padding: 1em 2em;
219
+ .header--floating {
220
+ max-width: 1100px;
221
+ border-radius: 20px;
222
+ padding: 0.7em 1em;
281
223
 
282
- @media (width < 768px) {
283
- max-width: 100%;
284
- padding: 0.8em 1em;
224
+ @media (width < 768px) {
225
+ max-width: 95%;
226
+ }
285
227
  }
286
- }
287
228
 
229
+ .header--fullscreen {
230
+ border-radius: 0;
231
+ padding: 1em 2em;
288
232
 
289
-
290
- .actions-desktop {
291
- @media (width < 768px) {
292
- display: none;
233
+ @media (width < 768px) {
234
+ max-width: 100%;
235
+ padding: 0.8em 1em;
236
+ }
293
237
  }
294
- }
295
238
 
296
- .actions-mobile {
297
- margin-top: 20px;
298
- @media (width >= 768px) {
299
- display: none;
239
+ .actions-desktop {
240
+ @media (width < 768px) {
241
+ display: none;
242
+ }
300
243
  }
301
- }
302
244
 
303
- .header--fullscreen .mobile-header__menu.is-active {
304
- transform: translate(0%);
245
+ .actions-mobile {
246
+ margin-top: 20px;
247
+
248
+ @media (width >= 768px) {
249
+ display: none;
250
+ }
251
+ }
305
252
  }
306
253
  </style>