athlefi-ui 0.1.7 → 0.1.9

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.
@@ -0,0 +1,407 @@
1
+ ---
2
+ import Logo from "../atoms/Logo.astro";
3
+ import NavMenu from "../molecules/NavMenu.astro";
4
+
5
+ export interface Props {
6
+ class?: string;
7
+ }
8
+
9
+ const { class: className = "" } = Astro.props;
10
+ ---
11
+
12
+ <header
13
+ class={`header ${className}`}
14
+ role="banner"
15
+ >
16
+ <div class="header__container">
17
+ <!-- Distribución con grid para centrado perfecto -->
18
+ <div class="header__grid">
19
+ <!-- Logo -->
20
+ <div class="header__logo">
21
+ <Logo />
22
+ </div>
23
+
24
+ <!-- Navegación Desktop -->
25
+ <div class="header__nav-desktop">
26
+ <NavMenu class="header__nav-center" />
27
+ </div>
28
+
29
+ <!-- Botones Auth Desktop - Slot -->
30
+ <div class="header__auth-desktop">
31
+ <slot name="auth-buttons" />
32
+ </div>
33
+
34
+ <!-- Botón Hamburguesa Mobile -->
35
+ <div class="header__mobile-toggle">
36
+ <button
37
+ id="mobile-menu-button"
38
+ type="button"
39
+ class="header__hamburger"
40
+ aria-controls="mobile-menu"
41
+ aria-expanded="false"
42
+ aria-label="Abrir menú de navegación"
43
+ >
44
+ <span class="sr-only">Abrir menú principal</span>
45
+ <!-- Hamburger Icon -->
46
+ <svg
47
+ id="hamburger-icon"
48
+ class="header__icon header__icon--visible"
49
+ xmlns="http://www.w3.org/2000/svg"
50
+ fill="none"
51
+ viewBox="0 0 24 24"
52
+ stroke="currentColor"
53
+ aria-hidden="true"
54
+ >
55
+ <path
56
+ stroke-linecap="round"
57
+ stroke-linejoin="round"
58
+ stroke-width="2"
59
+ d="M4 6h16M4 12h16M4 18h16"></path>
60
+ </svg>
61
+ <!-- Close Icon -->
62
+ <svg
63
+ id="close-icon"
64
+ class="header__icon header__icon--hidden"
65
+ xmlns="http://www.w3.org/2000/svg"
66
+ fill="none"
67
+ viewBox="0 0 24 24"
68
+ stroke="currentColor"
69
+ aria-hidden="true"
70
+ >
71
+ <path
72
+ stroke-linecap="round"
73
+ stroke-linejoin="round"
74
+ stroke-width="2"
75
+ d="M6 18L18 6M6 6l12 12"></path>
76
+ </svg>
77
+ </button>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- Mobile Menu -->
83
+ <div
84
+ id="mobile-menu"
85
+ class="header__mobile-menu header__mobile-menu--hidden"
86
+ role="navigation"
87
+ aria-label="Menú de navegación móvil"
88
+ >
89
+ <div class="header__mobile-content">
90
+ <!-- Mobile Navigation -->
91
+ <NavMenu isMobile={true} />
92
+
93
+ <!-- Mobile Auth Buttons - Slot -->
94
+ <div class="header__mobile-auth">
95
+ <slot name="auth-buttons-mobile">
96
+ <slot name="auth-buttons" />
97
+ </slot>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </header>
102
+
103
+ <style>
104
+ .header {
105
+ background-color: var(--color-primary-90);
106
+ border-bottom: 1px solid var(--color-gray-medium);
107
+ }
108
+
109
+ .header__container {
110
+ max-width: 1280px;
111
+ margin: 0 auto;
112
+ padding: 0 1rem;
113
+ }
114
+
115
+ @media (min-width: 640px) {
116
+ .header__container {
117
+ padding: 0 1.5rem;
118
+ }
119
+ }
120
+
121
+ @media (min-width: 1024px) {
122
+ .header__container {
123
+ padding: 0 2rem;
124
+ }
125
+ }
126
+
127
+ /* Grid Layout */
128
+ .header__grid {
129
+ display: grid;
130
+ grid-template-columns: repeat(4, 1fr);
131
+ align-items: center;
132
+ gap: 1rem;
133
+ padding: 1rem 0;
134
+ }
135
+
136
+ @media (min-width: 1024px) {
137
+ .header__grid {
138
+ grid-template-columns: repeat(16, 1fr);
139
+ }
140
+ }
141
+
142
+ /* Logo */
143
+ .header__logo {
144
+ grid-column: span 2;
145
+ }
146
+
147
+ @media (min-width: 1024px) {
148
+ .header__logo {
149
+ grid-column: 1 / span 3;
150
+ }
151
+ }
152
+
153
+ /* Desktop Navigation */
154
+ .header__nav-desktop {
155
+ display: none;
156
+ }
157
+
158
+ @media (min-width: 1024px) {
159
+ .header__nav-desktop {
160
+ display: block;
161
+ grid-column: 5 / span 8;
162
+ margin-left: 5rem;
163
+ }
164
+ }
165
+
166
+ .header__nav-center {
167
+ display: flex;
168
+ justify-content: center;
169
+ }
170
+
171
+ /* Desktop Auth Buttons */
172
+ .header__auth-desktop {
173
+ display: none;
174
+ }
175
+
176
+ @media (min-width: 1024px) {
177
+ .header__auth-desktop {
178
+ display: block;
179
+ grid-column: 14 / span 3;
180
+ }
181
+ }
182
+
183
+ /* Mobile Toggle */
184
+ .header__mobile-toggle {
185
+ grid-column: span 2;
186
+ display: flex;
187
+ justify-content: flex-end;
188
+ }
189
+
190
+ @media (min-width: 1024px) {
191
+ .header__mobile-toggle {
192
+ display: none;
193
+ }
194
+ }
195
+
196
+ /* Hamburger Button */
197
+ .header__hamburger {
198
+ display: inline-flex;
199
+ align-items: center;
200
+ justify-content: center;
201
+ padding: 0.5rem;
202
+ color: var(--color-white);
203
+ background-color: transparent;
204
+ border: none;
205
+ border-radius: 6px;
206
+ cursor: pointer;
207
+ transition: background-color 0.2s ease, color 0.2s ease;
208
+ }
209
+
210
+ .header__hamburger:hover {
211
+ background-color: rgb(31, 41, 55);
212
+ color: var(--color-primary-60);
213
+ }
214
+
215
+ .header__hamburger:focus-visible {
216
+ outline: 2px solid var(--color-primary-60);
217
+ outline-offset: 2px;
218
+ }
219
+
220
+ /* Icons */
221
+ .header__icon {
222
+ width: 24px;
223
+ height: 24px;
224
+ }
225
+
226
+ .header__icon--visible {
227
+ display: block;
228
+ }
229
+
230
+ .header__icon--hidden {
231
+ display: none;
232
+ }
233
+
234
+ /* Screen reader only */
235
+ .sr-only {
236
+ position: absolute;
237
+ width: 1px;
238
+ height: 1px;
239
+ padding: 0;
240
+ margin: -1px;
241
+ overflow: hidden;
242
+ clip: rect(0, 0, 0, 0);
243
+ white-space: nowrap;
244
+ border-width: 0;
245
+ }
246
+
247
+ /* Mobile Menu */
248
+ .header__mobile-menu {
249
+ border-top: 1px solid var(--color-primary-60);
250
+ }
251
+
252
+ @media (min-width: 1024px) {
253
+ .header__mobile-menu {
254
+ display: none !important;
255
+ }
256
+ }
257
+
258
+ .header__mobile-menu--hidden {
259
+ display: none;
260
+ }
261
+
262
+ .header__mobile-menu--visible {
263
+ display: block;
264
+ }
265
+
266
+ .header__mobile-content {
267
+ max-width: 1280px;
268
+ margin: 0 auto;
269
+ padding: 1rem;
270
+ display: flex;
271
+ flex-direction: column;
272
+ gap: 1rem;
273
+ }
274
+
275
+ @media (min-width: 640px) {
276
+ .header__mobile-content {
277
+ padding: 1rem 1.5rem;
278
+ }
279
+ }
280
+
281
+ /* Mobile Auth */
282
+ .header__mobile-auth {
283
+ border-top: 1px solid var(--color-primary-60);
284
+ padding-top: 1rem;
285
+ }
286
+ </style>
287
+
288
+ <script>
289
+ // Mobile menu toggle functionality
290
+ document.addEventListener("DOMContentLoaded", function () {
291
+ // Get DOM elements
292
+ const mobileMenuButton = document.getElementById("mobile-menu-button");
293
+ const mobileMenu = document.getElementById("mobile-menu");
294
+ const hamburgerIcon = document.getElementById("hamburger-icon");
295
+ const closeIcon = document.getElementById("close-icon");
296
+
297
+ // Early return if any required element is missing
298
+ if (!mobileMenuButton || !mobileMenu || !hamburgerIcon || !closeIcon) {
299
+ console.warn("Mobile menu: Required DOM elements not found");
300
+ return;
301
+ }
302
+
303
+ let isMenuOpen = false;
304
+
305
+ /**
306
+ * Toggles the mobile menu visibility and updates UI states
307
+ */
308
+ function toggleMenu(): void {
309
+ try {
310
+ isMenuOpen = !isMenuOpen;
311
+
312
+ // Toggle menu visibility
313
+ if (isMenuOpen) {
314
+ mobileMenu!.classList.remove("header__mobile-menu--hidden");
315
+ mobileMenu!.classList.add("header__mobile-menu--visible");
316
+ } else {
317
+ mobileMenu!.classList.remove("header__mobile-menu--visible");
318
+ mobileMenu!.classList.add("header__mobile-menu--hidden");
319
+ }
320
+
321
+ // Toggle icons visibility
322
+ if (isMenuOpen) {
323
+ hamburgerIcon!.classList.remove("header__icon--visible");
324
+ hamburgerIcon!.classList.add("header__icon--hidden");
325
+ closeIcon!.classList.remove("header__icon--hidden");
326
+ closeIcon!.classList.add("header__icon--visible");
327
+ } else {
328
+ hamburgerIcon!.classList.remove("header__icon--hidden");
329
+ hamburgerIcon!.classList.add("header__icon--visible");
330
+ closeIcon!.classList.remove("header__icon--visible");
331
+ closeIcon!.classList.add("header__icon--hidden");
332
+ }
333
+
334
+ // Update ARIA attributes for accessibility
335
+ mobileMenuButton!.setAttribute("aria-expanded", isMenuOpen.toString());
336
+ mobileMenuButton!.setAttribute(
337
+ "aria-label",
338
+ isMenuOpen ? "Cerrar menú de navegación" : "Abrir menú de navegación",
339
+ );
340
+
341
+ // Manage body scroll and focus
342
+ if (isMenuOpen) {
343
+ document.body.style.overflow = "hidden";
344
+ // Focus first focusable element in mobile menu for keyboard navigation
345
+ const firstFocusableElement = mobileMenu!.querySelector(
346
+ 'a, button, input, [tabindex]:not([tabindex="-1"])',
347
+ ) as HTMLElement;
348
+ firstFocusableElement?.focus();
349
+ } else {
350
+ document.body.style.overflow = "";
351
+ // Return focus to menu button when closing
352
+ mobileMenuButton!.focus();
353
+ }
354
+ } catch (error) {
355
+ console.error("Error toggling mobile menu:", error);
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Closes the menu if it's currently open
361
+ */
362
+ function closeMenu(): void {
363
+ if (isMenuOpen) {
364
+ toggleMenu();
365
+ }
366
+ }
367
+
368
+ // Event Listeners
369
+
370
+ // Toggle menu on button click
371
+ mobileMenuButton.addEventListener("click", toggleMenu);
372
+
373
+ // Close menu when clicking outside
374
+ document.addEventListener("click", function (event: Event) {
375
+ const target = event.target as Node;
376
+ if (
377
+ isMenuOpen &&
378
+ !mobileMenu!.contains(target) &&
379
+ !mobileMenuButton!.contains(target)
380
+ ) {
381
+ closeMenu();
382
+ }
383
+ });
384
+
385
+ // Close menu on escape key
386
+ document.addEventListener("keydown", function (event: KeyboardEvent) {
387
+ if (event.key === "Escape" && isMenuOpen) {
388
+ closeMenu();
389
+ }
390
+ });
391
+
392
+ // Close menu when window is resized to desktop size
393
+ let resizeTimeout: number;
394
+ window.addEventListener("resize", function () {
395
+ clearTimeout(resizeTimeout);
396
+ resizeTimeout = window.setTimeout(() => {
397
+ if (window.innerWidth >= 1024 && isMenuOpen) {
398
+ closeMenu();
399
+ }
400
+ }, 150);
401
+ });
402
+
403
+ // Close menu on page navigation (for SPAs)
404
+ window.addEventListener("beforeunload", closeMenu);
405
+ });
406
+ </script>
407
+
package/src/index.ts CHANGED
@@ -6,20 +6,32 @@
6
6
  // ============================================
7
7
  export { default as Button } from "./components/atoms/Button.astro";
8
8
  export { default as ContactIcon } from "./components/atoms/ContactIcon.astro";
9
+ export { default as FooterLink } from "./components/atoms/FooterLink.astro";
10
+ export { default as FormInput } from "./components/atoms/FormInput.astro";
11
+ export { default as IconCard } from "./components/atoms/IconCard.astro";
12
+ export { default as Logo } from "./components/atoms/Logo.astro";
13
+ export { default as NavLink } from "./components/atoms/NavLink.astro";
14
+ export { default as SocialIcon } from "./components/atoms/SocialIcon.astro";
15
+ export { default as TabButton } from "./components/atoms/TabButton.astro";
16
+ export { default as TextMono } from "./components/atoms/TextMono.astro";
17
+ export { default as TimelineStep } from "./components/atoms/TimelineStep.astro";
9
18
  // export { default as Input } from "./components/atoms/Input.astro";
10
19
  // export { default as Label } from "./components/atoms/Label.astro";
11
20
 
12
21
  // ============================================
13
22
  // MOLÉCULAS
14
23
  // ============================================
24
+ export { default as LinkGroup } from "./components/molecules/LinkGroup.astro";
25
+ export { default as NavMenu } from "./components/molecules/NavMenu.astro";
26
+ export { default as SocialGroup } from "./components/molecules/SocialGroup.astro";
15
27
  // export { default as SearchBar } from "./components/molecules/SearchBar.astro";
16
28
  // export { default as Card } from "./components/molecules/Card.astro";
17
29
 
18
30
  // ============================================
19
31
  // ORGANISMOS
20
32
  // ============================================
21
- // export { default as Header } from "./components/organisms/Header.astro";
22
- // export { default as Footer } from "./components/organisms/Footer.astro";
33
+ export { default as Header } from "./components/organisms/Header.astro";
34
+ export { default as Footer } from "./components/organisms/Footer.astro";
23
35
 
24
36
  // ============================================
25
37
  // TEMPLATES