@sofidevo/astro-dynamic-header 1.0.2 → 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 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
- import Header from '@sofidevo/astro-dynamic-header/Header.astro';
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.astro';
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.2",
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": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
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-package build",
29
- "dev": "astro-package dev",
27
+ "build": "astro build",
28
+ "dev": "astro dev",
30
29
  "check": "astro check",
31
30
  "check:watch": "astro check --watch"
32
31
  },
package/src/Header.astro CHANGED
@@ -77,7 +77,7 @@ const {
77
77
  </a>
78
78
  <NavMenu menuItems={menuItems} />
79
79
  <HamburgerButton />
80
- <MobileNav menuItems={menuItems} />
80
+ <MobileNav menuItems={menuItems} type={headerType} />
81
81
  </header>
82
82
  </div>
83
83
 
@@ -147,4 +147,9 @@ const {
147
147
  margin-right: 1rem;
148
148
  object-fit: contain;
149
149
  }
150
+
151
+ /* Adjust transform for fullscreen header */
152
+ .header--fullscreen .mobile-header__menu.is-active {
153
+ transform: translate(0%);
154
+ }
150
155
  </style>
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  export interface Props {
3
+ type?: "floating" | "fullscreen";
3
4
  menuItems?: Array<{
4
5
  link: string;
5
6
  text: string;
@@ -9,6 +10,7 @@ export interface Props {
9
10
  submenu?: Array<{
10
11
  link: string;
11
12
  text: string;
13
+
12
14
  }>;
13
15
  }>;
14
16
  }>;
@@ -17,6 +19,8 @@ export interface Props {
17
19
  accentColor?: string;
18
20
  }
19
21
 
22
+ const {type} = Astro.props;
23
+
20
24
  const {
21
25
  menuItems = [],
22
26
  showHomeLink = true,
@@ -27,7 +31,7 @@ const {
27
31
  const pagePathname = Astro.url.pathname;
28
32
  ---
29
33
 
30
- <nav class="mobile-header__menu" id="mobile-header-menu" style={`--mobile-accent-color: ${accentColor};`}>
34
+ <nav class={`mobile-header__menu mobile-header__menu--${type}`} id="mobile-header-menu" style={`--mobile-accent-color: ${accentColor};`}>
31
35
  <ol class="mobile-menu">
32
36
  {showHomeLink && pagePathname !== "/" && (
33
37
  <li class="header__item mobile-header__item">
@@ -190,6 +194,11 @@ const pagePathname = Astro.url.pathname;
190
194
  transform: translateX(-7%);
191
195
  }
192
196
 
197
+ /* Adjust transform for fullscreen header */
198
+ .mobile-header__menu--fullscreen.is-active {
199
+ transform: translate(0%);
200
+ }
201
+
193
202
  .menu__summary {
194
203
  width: 100%;
195
204
  font-size: 1.7rem;
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', adjustSubsubmenuPositionOnResize);
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
- export class HamburgerButton {
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
- new HamburgerButton();
94
+ // Initialize only the class-based controller to avoid conflicts
95
+ new HamburgerButtonController();
43
96
  });
44
97
  }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // TypeScript utilities
2
- export { HamburgerButton as HamburgerButtonController } from './hamburger';
2
+ export { HamburgerButton, HamburgerButtonController } from './hamburger.ts';
3
3
 
4
4
  // Types
5
5
  export interface MenuItemType {