@sofidevo/astro-dynamic-header 1.0.3 → 1.0.5
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 +67 -2
- package/package.json +7 -8
- package/src/NavMenu.astro +74 -1
- package/src/hamburger.ts +56 -3
- package/src/index.ts +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,11 @@ A dynamic, responsive header component for Astro projects that can switch betwee
|
|
|
27
27
|
|
|
28
28
|
```astro
|
|
29
29
|
---
|
|
30
|
-
|
|
30
|
+
// Option 1: Import from direct subpath (recommended)
|
|
31
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
32
|
+
|
|
33
|
+
// Option 2: Import from main entry point with types
|
|
34
|
+
import { HeaderProps, type MenuItemType } from '@sofidevo/astro-dynamic-header';
|
|
31
35
|
|
|
32
36
|
const menuItems = [
|
|
33
37
|
{ link: '/about', text: 'About' },
|
|
@@ -42,11 +46,29 @@ const menuItems = [
|
|
|
42
46
|
/>
|
|
43
47
|
```
|
|
44
48
|
|
|
49
|
+
### TypeScript Configuration
|
|
50
|
+
|
|
51
|
+
To ensure imports work correctly in your Astro project, make sure your `tsconfig.json` has the appropriate configuration:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"compilerOptions": {
|
|
56
|
+
"moduleResolution": "bundler",
|
|
57
|
+
"allowImportingTsExtensions": true,
|
|
58
|
+
"strict": true,
|
|
59
|
+
"noEmit": true,
|
|
60
|
+
"jsx": "preserve"
|
|
61
|
+
},
|
|
62
|
+
"extends": "astro/tsconfigs/strict"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
45
66
|
### Advanced Usage
|
|
46
67
|
|
|
47
68
|
```astro
|
|
48
69
|
---
|
|
49
|
-
import Header from '@sofidevo/astro-dynamic-header/Header
|
|
70
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
71
|
+
import type { MenuItemType } from '@sofidevo/astro-dynamic-header';
|
|
50
72
|
|
|
51
73
|
const menuItems = [
|
|
52
74
|
{
|
|
@@ -130,6 +152,12 @@ You can also import and use components individually:
|
|
|
130
152
|
|
|
131
153
|
```astro
|
|
132
154
|
---
|
|
155
|
+
// Import individual components
|
|
156
|
+
import NavMenu from '@sofidevo/astro-dynamic-header/NavMenu';
|
|
157
|
+
import MobileNav from '@sofidevo/astro-dynamic-header/MobileNav';
|
|
158
|
+
import HamburgerButton from '@sofidevo/astro-dynamic-header/HamburgerButton';
|
|
159
|
+
|
|
160
|
+
// Or use the .astro extension explicitly
|
|
133
161
|
import NavMenu from '@sofidevo/astro-dynamic-header/NavMenu.astro';
|
|
134
162
|
import MobileNav from '@sofidevo/astro-dynamic-header/MobileNav.astro';
|
|
135
163
|
import HamburgerButton from '@sofidevo/astro-dynamic-header/HamburgerButton.astro';
|
|
@@ -173,6 +201,43 @@ import type {
|
|
|
173
201
|
- Supports CSS `backdrop-filter`
|
|
174
202
|
- Graceful degradation for older browsers
|
|
175
203
|
|
|
204
|
+
## Troubleshooting
|
|
205
|
+
|
|
206
|
+
### Import Issues
|
|
207
|
+
|
|
208
|
+
If you encounter import errors, try these solutions:
|
|
209
|
+
|
|
210
|
+
1. **Use direct subpath import:**
|
|
211
|
+
```astro
|
|
212
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
2. **Verify TypeScript configuration:**
|
|
216
|
+
```json
|
|
217
|
+
// tsconfig.json
|
|
218
|
+
{
|
|
219
|
+
"compilerOptions": {
|
|
220
|
+
"moduleResolution": "bundler", // or "nodenext"
|
|
221
|
+
"allowImportingTsExtensions": true
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
3. **Import types separately:**
|
|
227
|
+
```astro
|
|
228
|
+
---
|
|
229
|
+
import Header from '@sofidevo/astro-dynamic-header/Header';
|
|
230
|
+
import type { MenuItemType } from '@sofidevo/astro-dynamic-header';
|
|
231
|
+
---
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Compatibility
|
|
235
|
+
|
|
236
|
+
- ✅ Astro 4.x and 5.x
|
|
237
|
+
- ✅ SSG Projects (Static Site Generation)
|
|
238
|
+
- ✅ SSR Projects (Server-Side Rendering)
|
|
239
|
+
- ✅ Hybrid Projects (output: 'hybrid')
|
|
240
|
+
|
|
176
241
|
## Live Examples
|
|
177
242
|
|
|
178
243
|
Visit our demo website to see the component in action with interactive examples and complete documentation.
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sofidevo/astro-dynamic-header",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A dynamic Astro header component that switches between floating and fullscreen styles",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
7
|
-
"types": "./
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"import": "./
|
|
11
|
-
"types": "./
|
|
10
|
+
"import": "./src/index.ts",
|
|
11
|
+
"types": "./src/index.ts"
|
|
12
12
|
},
|
|
13
13
|
"./Header": "./src/Header.astro",
|
|
14
14
|
"./NavMenu": "./src/NavMenu.astro",
|
|
@@ -20,13 +20,12 @@
|
|
|
20
20
|
"./HamburgerButton.astro": "./src/HamburgerButton.astro"
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
|
-
"dist",
|
|
24
23
|
"src",
|
|
25
24
|
"README.md"
|
|
26
25
|
],
|
|
27
26
|
"scripts": {
|
|
28
|
-
"build": "astro
|
|
29
|
-
"dev": "astro
|
|
27
|
+
"build": "astro build",
|
|
28
|
+
"dev": "astro dev",
|
|
30
29
|
"check": "astro check",
|
|
31
30
|
"check:watch": "astro check --watch"
|
|
32
31
|
},
|
package/src/NavMenu.astro
CHANGED
|
@@ -43,8 +43,81 @@ const pagePathname = Astro.url.pathname;
|
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function handleSubsubmenuInteraction() {
|
|
47
|
+
const parentItems = document.querySelectorAll('.submenu__item--secondary');
|
|
48
|
+
|
|
49
|
+
parentItems.forEach((parentItem) => {
|
|
50
|
+
const subsubmenu = parentItem.querySelector('.subsubmenu');
|
|
51
|
+
|
|
52
|
+
if (subsubmenu) {
|
|
53
|
+
const adjustPosition = () => {
|
|
54
|
+
const viewportWidth = window.innerWidth;
|
|
55
|
+
|
|
56
|
+
const originalDisplay = (subsubmenu as HTMLElement).style.display;
|
|
57
|
+
const originalVisibility = (subsubmenu as HTMLElement).style.visibility;
|
|
58
|
+
|
|
59
|
+
(subsubmenu as HTMLElement).style.display = "block";
|
|
60
|
+
(subsubmenu as HTMLElement).style.visibility = "hidden";
|
|
61
|
+
(subsubmenu as HTMLElement).style.left = "100%";
|
|
62
|
+
(subsubmenu as HTMLElement).style.transform = "";
|
|
63
|
+
|
|
64
|
+
const rect = subsubmenu.getBoundingClientRect();
|
|
65
|
+
|
|
66
|
+
if (rect.right > viewportWidth) {
|
|
67
|
+
(subsubmenu as HTMLElement).style.left = "-220%";
|
|
68
|
+
const leftRect = subsubmenu.getBoundingClientRect();
|
|
69
|
+
|
|
70
|
+
if (leftRect.left < 0) {
|
|
71
|
+
(subsubmenu as HTMLElement).style.left = "100%";
|
|
72
|
+
const overflow = rect.right - viewportWidth + 30;
|
|
73
|
+
(subsubmenu as HTMLElement).style.transform = `translateX(-${overflow}px)`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
(subsubmenu as HTMLElement).style.display = originalDisplay;
|
|
77
|
+
(subsubmenu as HTMLElement).style.visibility = originalVisibility;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const showSubsubmenu = () => {
|
|
81
|
+
adjustPosition();
|
|
82
|
+
|
|
83
|
+
(subsubmenu as HTMLElement).style.opacity = "1";
|
|
84
|
+
(subsubmenu as HTMLElement).style.pointerEvents = "all";
|
|
85
|
+
|
|
86
|
+
const isPositionedLeft = (subsubmenu as HTMLElement).style.left === "-100%";
|
|
87
|
+
const transformDirection = isPositionedLeft ? "-1rem" : "2.5rem";
|
|
88
|
+
|
|
89
|
+
const currentTransform = (subsubmenu as HTMLElement).style.transform;
|
|
90
|
+
if (currentTransform && currentTransform.includes("translateX")) {
|
|
91
|
+
const existingTranslate = currentTransform.match(
|
|
92
|
+
/translateX\(([^)]+)\)/
|
|
93
|
+
);
|
|
94
|
+
if (existingTranslate) {
|
|
95
|
+
const existingValue = existingTranslate[1];
|
|
96
|
+
(subsubmenu as HTMLElement).style.transform = `translateX(calc(${existingValue} + ${transformDirection}))`;
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
(subsubmenu as HTMLElement).style.transform =
|
|
100
|
+
`${currentTransform} translateX(${transformDirection})`.trim();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const hideSubsubmenu = () => {
|
|
105
|
+
(subsubmenu as HTMLElement).style.opacity = "";
|
|
106
|
+
(subsubmenu as HTMLElement).style.pointerEvents = "";
|
|
107
|
+
(subsubmenu as HTMLElement).style.transform = "";
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
parentItem.addEventListener("mouseenter", showSubsubmenu);
|
|
111
|
+
parentItem.addEventListener("mouseleave", hideSubsubmenu);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
46
116
|
window.addEventListener('resize', adjustSubsubmenuPositionOnResize);
|
|
47
|
-
document.addEventListener('DOMContentLoaded',
|
|
117
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
118
|
+
adjustSubsubmenuPositionOnResize();
|
|
119
|
+
handleSubsubmenuInteraction();
|
|
120
|
+
});
|
|
48
121
|
</script>
|
|
49
122
|
|
|
50
123
|
<nav class="header__menu" id="header-menu">
|
package/src/hamburger.ts
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
const d = document;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Adds a global click event listener to handle hamburger menu button and menu link interactions.
|
|
6
|
+
*
|
|
7
|
+
* When the hamburger button or any of its child elements are clicked, toggles the "is-active" class
|
|
8
|
+
* on both the hamburger button and the associated panel. When a menu link is clicked, removes the
|
|
9
|
+
* "is-active" class from both the hamburger button and the panel.
|
|
10
|
+
*
|
|
11
|
+
* @param hamburgerBtn - A CSS selector string for the hamburger button element.
|
|
12
|
+
* @param panel - A CSS selector string for the panel element to show/hide.
|
|
13
|
+
* @param menuLink - A CSS selector string for the menu link elements that close the menu when clicked.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export function HamburgerButton(hamburgerBtn: string, panel: string, menuLink: string): void {
|
|
18
|
+
d.addEventListener("click", (e: Event) => {
|
|
19
|
+
const target = e.target as Element;
|
|
20
|
+
|
|
21
|
+
if (
|
|
22
|
+
target?.matches?.(hamburgerBtn) ||
|
|
23
|
+
target?.matches?.(`${hamburgerBtn} *`)
|
|
24
|
+
) {
|
|
25
|
+
d.querySelector(hamburgerBtn)?.classList.toggle("is-active");
|
|
26
|
+
d.querySelector(panel)?.classList.toggle("is-active");
|
|
27
|
+
}
|
|
28
|
+
if (target?.matches?.(menuLink)) {
|
|
29
|
+
d.querySelector(hamburgerBtn)?.classList.remove("is-active");
|
|
30
|
+
d.querySelector(panel)?.classList.remove("is-active");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Class-based approach for the default implementation
|
|
36
|
+
export class HamburgerButtonController {
|
|
2
37
|
private hamburgerBtn: HTMLElement | null;
|
|
3
38
|
private mobileMenu: HTMLElement | null;
|
|
4
39
|
|
|
@@ -11,9 +46,26 @@ export class HamburgerButton {
|
|
|
11
46
|
private init(): void {
|
|
12
47
|
if (this.hamburgerBtn && this.mobileMenu) {
|
|
13
48
|
this.hamburgerBtn.addEventListener("click", () => this.toggleMenu());
|
|
49
|
+
// Add event listener for closing menu when clicking on menu links
|
|
50
|
+
this.addMenuLinkListeners();
|
|
14
51
|
}
|
|
15
52
|
}
|
|
16
53
|
|
|
54
|
+
private addMenuLinkListeners(): void {
|
|
55
|
+
// Listen for clicks on the document to handle menu link clicks
|
|
56
|
+
document.addEventListener("click", (e: Event) => {
|
|
57
|
+
const target = e.target as Element;
|
|
58
|
+
|
|
59
|
+
// Check if the clicked element is a menu link (following original logic pattern)
|
|
60
|
+
if (target?.matches?.(".mobile-menu__link") ||
|
|
61
|
+
target?.matches?.(".menu__link.mobile-menu__link") ||
|
|
62
|
+
target?.matches?.(".mobile-menu__link *") ||
|
|
63
|
+
target?.matches?.(".menu__link.mobile-menu__link *")) {
|
|
64
|
+
this.closeMenu();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
17
69
|
private toggleMenu(): void {
|
|
18
70
|
if (this.hamburgerBtn && this.mobileMenu) {
|
|
19
71
|
this.hamburgerBtn.classList.toggle("is-active");
|
|
@@ -36,9 +88,10 @@ export class HamburgerButton {
|
|
|
36
88
|
}
|
|
37
89
|
}
|
|
38
90
|
|
|
39
|
-
// Auto-initialize when DOM is ready
|
|
91
|
+
// Auto-initialize when DOM is ready (using only the class-based approach)
|
|
40
92
|
if (typeof document !== 'undefined') {
|
|
41
93
|
document.addEventListener('DOMContentLoaded', () => {
|
|
42
|
-
|
|
94
|
+
// Initialize only the class-based controller to avoid conflicts
|
|
95
|
+
new HamburgerButtonController();
|
|
43
96
|
});
|
|
44
97
|
}
|
package/src/index.ts
CHANGED