starlight-mega-menu 1.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.
@@ -0,0 +1,671 @@
1
+ /* ===================================================================
2
+ starlight-mega-menu styles
3
+ Namespace: .smm-* to avoid collisions with Starlight/consumer styles
4
+ =================================================================== */
5
+
6
+ /* ── Desktop: Radix NavigationMenu ──────────────────────────────── */
7
+
8
+ .smm-root {
9
+ position: relative;
10
+ display: flex;
11
+ justify-content: center;
12
+ z-index: 10;
13
+ }
14
+
15
+ .smm-list {
16
+ display: flex;
17
+ list-style: none;
18
+ margin: 0;
19
+ padding: 0;
20
+ gap: 0.125rem;
21
+ }
22
+
23
+ .smm-trigger,
24
+ .smm-link {
25
+ display: inline-flex;
26
+ align-items: center;
27
+ gap: 0.25rem;
28
+ padding: 0.5rem 0.75rem;
29
+ border: none;
30
+ border-radius: 0.375rem;
31
+ background: transparent;
32
+ color: var(--sl-color-gray-2);
33
+ font-family: var(--sl-font);
34
+ font-size: var(--sl-text-sm);
35
+ font-weight: 500;
36
+ line-height: 1;
37
+ text-decoration: none;
38
+ cursor: pointer;
39
+ transition: color 150ms ease, background-color 150ms ease;
40
+ white-space: nowrap;
41
+ user-select: none;
42
+ -webkit-user-select: none;
43
+ }
44
+
45
+ .smm-trigger:hover,
46
+ .smm-trigger:focus-visible,
47
+ .smm-trigger[data-state='open'],
48
+ .smm-link:hover,
49
+ .smm-link:focus-visible {
50
+ color: var(--sl-color-white);
51
+ background-color: var(--sl-color-gray-6);
52
+ }
53
+
54
+ .smm-trigger:focus-visible,
55
+ .smm-link:focus-visible {
56
+ outline: 2px solid var(--sl-color-accent);
57
+ outline-offset: 2px;
58
+ }
59
+
60
+ /* Chevron rotation */
61
+ .smm-chevron {
62
+ transition: transform 250ms ease;
63
+ }
64
+ .smm-trigger[data-state='open'] .smm-chevron {
65
+ transform: rotate(180deg);
66
+ }
67
+
68
+ /* ── Indicator (the arrow that slides between items) ────────────── */
69
+
70
+ .smm-indicator {
71
+ display: flex;
72
+ align-items: flex-end;
73
+ justify-content: center;
74
+ height: 0.625rem;
75
+ overflow: hidden;
76
+ z-index: 10;
77
+ transition: width 250ms ease, transform 250ms ease;
78
+ }
79
+
80
+ .smm-indicator[data-state='visible'] {
81
+ animation: smm-fadeIn 200ms ease;
82
+ }
83
+
84
+ .smm-indicator[data-state='hidden'] {
85
+ animation: smm-fadeOut 200ms ease;
86
+ }
87
+
88
+ .smm-arrow {
89
+ position: relative;
90
+ top: 70%;
91
+ width: 0.75rem;
92
+ height: 0.75rem;
93
+ background: var(--sl-color-gray-6);
94
+ border-top-left-radius: 2px;
95
+ transform: rotate(45deg);
96
+ }
97
+
98
+ /* ── Viewport (the floating panel container) ────────────────────── */
99
+
100
+ .smm-viewport-wrapper {
101
+ position: absolute;
102
+ top: 100%;
103
+ left: 50%;
104
+ transform: translateX(-50%);
105
+ perspective: 2000px;
106
+ padding-top: 0.625rem;
107
+ width: 100%;
108
+ display: flex;
109
+ justify-content: center;
110
+ }
111
+
112
+ .smm-viewport {
113
+ position: relative;
114
+ width: var(--radix-navigation-menu-viewport-width);
115
+ height: var(--radix-navigation-menu-viewport-height);
116
+ min-width: 320px;
117
+ max-width: min(90vw, 700px);
118
+ overflow: hidden;
119
+ border-radius: 0.75rem;
120
+ border: 1px solid var(--sl-color-gray-5);
121
+ background: var(--sl-color-black);
122
+ box-shadow:
123
+ 0 10px 38px -10px rgba(0, 0, 0, 0.35),
124
+ 0 10px 20px -15px rgba(0, 0, 0, 0.2);
125
+ transition: width 300ms ease, height 300ms ease;
126
+ }
127
+
128
+ .smm-viewport[data-state='open'] {
129
+ animation: smm-scaleIn 275ms ease-out;
130
+ }
131
+
132
+ .smm-viewport[data-state='closed'] {
133
+ animation: smm-scaleOut 250ms ease-in;
134
+ }
135
+
136
+ /* ── Content (individual panel — directional slide) ─────────────── */
137
+
138
+ .smm-content {
139
+ position: absolute;
140
+ top: 0;
141
+ left: 0;
142
+ width: 100%;
143
+ padding: 1rem 1.25rem;
144
+ }
145
+
146
+ .smm-content[data-motion='from-start'] {
147
+ animation: smm-slideFromStart 250ms ease;
148
+ }
149
+ .smm-content[data-motion='from-end'] {
150
+ animation: smm-slideFromEnd 250ms ease;
151
+ }
152
+ .smm-content[data-motion='to-start'] {
153
+ animation: smm-slideToStart 250ms ease;
154
+ }
155
+ .smm-content[data-motion='to-end'] {
156
+ animation: smm-slideToEnd 250ms ease;
157
+ }
158
+
159
+ /* ── Panel layout ───────────────────────────────────────────────── */
160
+
161
+ .smm-panel {
162
+ display: flex;
163
+ flex-direction: column;
164
+ gap: 1rem;
165
+ }
166
+
167
+ .smm-panel[data-layout='grid'] {
168
+ display: grid;
169
+ grid-template-columns: repeat(var(--smm-columns, 2), 1fr);
170
+ gap: 1rem;
171
+ }
172
+
173
+ .smm-panel[data-layout='grid'] .smm-panel-footer {
174
+ grid-column: 1 / -1;
175
+ }
176
+
177
+ /* ── Category ───────────────────────────────────────────────────── */
178
+
179
+ .smm-category-title {
180
+ font-family: var(--sl-font);
181
+ font-size: 0.6875rem;
182
+ font-weight: 600;
183
+ text-transform: uppercase;
184
+ letter-spacing: 0.06em;
185
+ color: var(--sl-color-gray-3);
186
+ margin: 0 0 0.5rem;
187
+ padding: 0;
188
+ }
189
+
190
+ .smm-category-list {
191
+ list-style: none;
192
+ margin: 0;
193
+ padding: 0;
194
+ display: flex;
195
+ flex-direction: column;
196
+ gap: 0.125rem;
197
+ }
198
+
199
+ /* ── Link item ──────────────────────────────────────────────────── */
200
+
201
+ .smm-menu-link {
202
+ display: flex;
203
+ align-items: flex-start;
204
+ gap: 0.75rem;
205
+ padding: 0.5rem 0.625rem;
206
+ border-radius: 0.375rem;
207
+ text-decoration: none;
208
+ color: var(--sl-color-gray-2);
209
+ transition: background-color 150ms ease, color 150ms ease;
210
+ }
211
+
212
+ .smm-menu-link:hover,
213
+ .smm-menu-link:focus-visible {
214
+ background-color: var(--sl-color-gray-6);
215
+ color: var(--sl-color-white);
216
+ }
217
+
218
+ .smm-menu-link:focus-visible {
219
+ outline: 2px solid var(--sl-color-accent);
220
+ outline-offset: -2px;
221
+ }
222
+
223
+ .smm-link-icon {
224
+ flex-shrink: 0;
225
+ width: 1.25rem;
226
+ height: 1.25rem;
227
+ margin-top: 0.125rem;
228
+ color: var(--sl-color-gray-3);
229
+ }
230
+
231
+ .smm-menu-link:hover .smm-link-icon {
232
+ color: var(--sl-color-accent);
233
+ }
234
+
235
+ .smm-link-icon svg {
236
+ width: 100%;
237
+ height: 100%;
238
+ }
239
+
240
+ .smm-link-text {
241
+ display: flex;
242
+ flex-direction: column;
243
+ gap: 0.125rem;
244
+ }
245
+
246
+ .smm-link-label {
247
+ font-family: var(--sl-font);
248
+ font-size: var(--sl-text-sm);
249
+ font-weight: 500;
250
+ line-height: 1.4;
251
+ color: inherit;
252
+ }
253
+
254
+ .smm-link-description {
255
+ font-family: var(--sl-font);
256
+ font-size: 0.75rem;
257
+ line-height: 1.4;
258
+ color: var(--sl-color-gray-3);
259
+ }
260
+
261
+ /* ── Panel footer ───────────────────────────────────────────────── */
262
+
263
+ .smm-panel-footer {
264
+ border-top: 1px solid var(--sl-color-gray-5);
265
+ padding-top: 0.75rem;
266
+ margin-top: 0.25rem;
267
+ }
268
+
269
+ .smm-footer-link {
270
+ display: inline-flex;
271
+ align-items: center;
272
+ gap: 0.375rem;
273
+ padding: 0.375rem 0.625rem;
274
+ border-radius: 0.375rem;
275
+ text-decoration: none;
276
+ font-family: var(--sl-font);
277
+ font-size: var(--sl-text-sm);
278
+ font-weight: 500;
279
+ color: var(--sl-color-accent);
280
+ transition: background-color 150ms ease;
281
+ }
282
+
283
+ .smm-footer-link:hover,
284
+ .smm-footer-link:focus-visible {
285
+ background-color: var(--sl-color-gray-6);
286
+ }
287
+
288
+ .smm-footer-link:focus-visible {
289
+ outline: 2px solid var(--sl-color-accent);
290
+ outline-offset: -2px;
291
+ }
292
+
293
+ .smm-footer-description {
294
+ font-size: 0.75rem;
295
+ color: var(--sl-color-gray-3);
296
+ margin-left: 0.25rem;
297
+ }
298
+
299
+ .smm-footer-arrow {
300
+ flex-shrink: 0;
301
+ transition: transform 150ms ease;
302
+ }
303
+
304
+ .smm-footer-link:hover .smm-footer-arrow {
305
+ transform: translateX(2px);
306
+ }
307
+
308
+ /* ── Mobile: Hamburger + overlay ────────────────────────────────── */
309
+
310
+ .smm-mobile-button {
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: center;
314
+ padding: 0.375rem;
315
+ border: none;
316
+ border-radius: 0.375rem;
317
+ background: transparent;
318
+ color: var(--sl-color-gray-2);
319
+ cursor: pointer;
320
+ transition: color 150ms ease, background-color 150ms ease;
321
+ }
322
+
323
+ .smm-mobile-button:hover,
324
+ .smm-mobile-button:focus-visible {
325
+ color: var(--sl-color-white);
326
+ background-color: var(--sl-color-gray-6);
327
+ }
328
+
329
+ .smm-mobile-button:focus-visible {
330
+ outline: 2px solid var(--sl-color-accent);
331
+ outline-offset: 2px;
332
+ }
333
+
334
+ .smm-mobile-backdrop {
335
+ position: fixed;
336
+ inset: 0;
337
+ background: rgba(0, 0, 0, 0.5);
338
+ z-index: 100;
339
+ animation: smm-fadeIn 200ms ease;
340
+ }
341
+
342
+ .smm-mobile-overlay {
343
+ position: fixed;
344
+ top: 0;
345
+ right: 0;
346
+ bottom: 0;
347
+ width: min(85vw, 380px);
348
+ background: var(--sl-color-black);
349
+ border-left: 1px solid var(--sl-color-gray-5);
350
+ z-index: 101;
351
+ display: flex;
352
+ flex-direction: column;
353
+ animation: smm-slideInRight 250ms ease-out;
354
+ overflow-y: auto;
355
+ overscroll-behavior: contain;
356
+ }
357
+
358
+ .smm-mobile-header {
359
+ display: flex;
360
+ align-items: center;
361
+ justify-content: space-between;
362
+ padding: 1rem 1.25rem;
363
+ border-bottom: 1px solid var(--sl-color-gray-5);
364
+ flex-shrink: 0;
365
+ }
366
+
367
+ .smm-mobile-title {
368
+ font-family: var(--sl-font);
369
+ font-size: var(--sl-text-base, 1rem);
370
+ font-weight: 600;
371
+ color: var(--sl-color-white);
372
+ }
373
+
374
+ .smm-mobile-close {
375
+ display: flex;
376
+ align-items: center;
377
+ justify-content: center;
378
+ padding: 0.375rem;
379
+ border: none;
380
+ border-radius: 0.375rem;
381
+ background: transparent;
382
+ color: var(--sl-color-gray-2);
383
+ cursor: pointer;
384
+ transition: color 150ms ease, background-color 150ms ease;
385
+ }
386
+
387
+ .smm-mobile-close:hover,
388
+ .smm-mobile-close:focus-visible {
389
+ color: var(--sl-color-white);
390
+ background-color: var(--sl-color-gray-6);
391
+ }
392
+
393
+ .smm-mobile-close:focus-visible {
394
+ outline: 2px solid var(--sl-color-accent);
395
+ outline-offset: -2px;
396
+ }
397
+
398
+ .smm-mobile-nav {
399
+ padding: 0.75rem;
400
+ display: flex;
401
+ flex-direction: column;
402
+ gap: 0.25rem;
403
+ }
404
+
405
+ /* ── Mobile: Accordion sections ─────────────────────────────────── */
406
+
407
+ .smm-mobile-section {
408
+ border-radius: 0.375rem;
409
+ }
410
+
411
+ .smm-mobile-summary {
412
+ display: flex;
413
+ align-items: center;
414
+ justify-content: space-between;
415
+ padding: 0.625rem 0.75rem;
416
+ border-radius: 0.375rem;
417
+ font-family: var(--sl-font);
418
+ font-size: var(--sl-text-sm);
419
+ font-weight: 500;
420
+ color: var(--sl-color-gray-2);
421
+ cursor: pointer;
422
+ list-style: none;
423
+ transition: color 150ms ease, background-color 150ms ease;
424
+ }
425
+
426
+ .smm-mobile-summary::-webkit-details-marker {
427
+ display: none;
428
+ }
429
+
430
+ .smm-mobile-summary:hover {
431
+ color: var(--sl-color-white);
432
+ background-color: var(--sl-color-gray-6);
433
+ }
434
+
435
+ .smm-mobile-chevron {
436
+ transition: transform 200ms ease;
437
+ }
438
+
439
+ .smm-mobile-section[open] > .smm-mobile-summary .smm-mobile-chevron {
440
+ transform: rotate(180deg);
441
+ }
442
+
443
+ .smm-mobile-panel {
444
+ padding: 0.25rem 0 0.5rem 0.75rem;
445
+ }
446
+
447
+ .smm-mobile-category {
448
+ margin-bottom: 0.75rem;
449
+ }
450
+
451
+ .smm-mobile-category:last-child {
452
+ margin-bottom: 0;
453
+ }
454
+
455
+ .smm-mobile-category-title {
456
+ font-family: var(--sl-font);
457
+ font-size: 0.6875rem;
458
+ font-weight: 600;
459
+ text-transform: uppercase;
460
+ letter-spacing: 0.06em;
461
+ color: var(--sl-color-gray-3);
462
+ margin: 0 0 0.375rem 0.75rem;
463
+ padding: 0;
464
+ }
465
+
466
+ .smm-mobile-category-list {
467
+ list-style: none;
468
+ margin: 0;
469
+ padding: 0;
470
+ }
471
+
472
+ .smm-mobile-link {
473
+ display: flex;
474
+ align-items: flex-start;
475
+ gap: 0.625rem;
476
+ padding: 0.5rem 0.75rem;
477
+ border-radius: 0.375rem;
478
+ text-decoration: none;
479
+ color: var(--sl-color-gray-2);
480
+ transition: background-color 150ms ease, color 150ms ease;
481
+ }
482
+
483
+ .smm-mobile-link:hover,
484
+ .smm-mobile-link:focus-visible {
485
+ background-color: var(--sl-color-gray-6);
486
+ color: var(--sl-color-white);
487
+ }
488
+
489
+ .smm-mobile-link-icon {
490
+ flex-shrink: 0;
491
+ width: 1.125rem;
492
+ height: 1.125rem;
493
+ margin-top: 0.125rem;
494
+ color: var(--sl-color-gray-3);
495
+ }
496
+
497
+ .smm-mobile-link-icon svg {
498
+ width: 100%;
499
+ height: 100%;
500
+ }
501
+
502
+ .smm-mobile-link-text {
503
+ display: flex;
504
+ flex-direction: column;
505
+ gap: 0.0625rem;
506
+ }
507
+
508
+ .smm-mobile-link-label {
509
+ font-family: var(--sl-font);
510
+ font-size: var(--sl-text-sm);
511
+ font-weight: 500;
512
+ line-height: 1.4;
513
+ }
514
+
515
+ .smm-mobile-link-desc {
516
+ font-family: var(--sl-font);
517
+ font-size: 0.75rem;
518
+ line-height: 1.4;
519
+ color: var(--sl-color-gray-3);
520
+ }
521
+
522
+ .smm-mobile-footer-link {
523
+ display: block;
524
+ padding: 0.5rem 0.75rem;
525
+ margin-top: 0.5rem;
526
+ border-top: 1px solid var(--sl-color-gray-5);
527
+ padding-top: 0.75rem;
528
+ font-family: var(--sl-font);
529
+ font-size: var(--sl-text-sm);
530
+ font-weight: 500;
531
+ color: var(--sl-color-accent);
532
+ text-decoration: none;
533
+ border-radius: 0.375rem;
534
+ transition: background-color 150ms ease;
535
+ }
536
+
537
+ .smm-mobile-footer-link:hover {
538
+ background-color: var(--sl-color-gray-6);
539
+ }
540
+
541
+ .smm-mobile-top-link {
542
+ display: block;
543
+ padding: 0.625rem 0.75rem;
544
+ border-radius: 0.375rem;
545
+ font-family: var(--sl-font);
546
+ font-size: var(--sl-text-sm);
547
+ font-weight: 500;
548
+ color: var(--sl-color-gray-2);
549
+ text-decoration: none;
550
+ transition: color 150ms ease, background-color 150ms ease;
551
+ }
552
+
553
+ .smm-mobile-top-link:hover,
554
+ .smm-mobile-top-link:focus-visible {
555
+ color: var(--sl-color-white);
556
+ background-color: var(--sl-color-gray-6);
557
+ }
558
+
559
+ /* ── Animations ─────────────────────────────────────────────────── */
560
+
561
+ @keyframes smm-scaleIn {
562
+ from {
563
+ opacity: 0;
564
+ transform: rotateX(-8deg) scale(0.96);
565
+ }
566
+ to {
567
+ opacity: 1;
568
+ transform: rotateX(0deg) scale(1);
569
+ }
570
+ }
571
+
572
+ @keyframes smm-scaleOut {
573
+ from {
574
+ opacity: 1;
575
+ transform: rotateX(0deg) scale(1);
576
+ }
577
+ to {
578
+ opacity: 0;
579
+ transform: rotateX(-8deg) scale(0.96);
580
+ }
581
+ }
582
+
583
+ @keyframes smm-slideFromStart {
584
+ from {
585
+ opacity: 0;
586
+ transform: translateX(-100%);
587
+ }
588
+ to {
589
+ opacity: 1;
590
+ transform: translateX(0);
591
+ }
592
+ }
593
+
594
+ @keyframes smm-slideFromEnd {
595
+ from {
596
+ opacity: 0;
597
+ transform: translateX(100%);
598
+ }
599
+ to {
600
+ opacity: 1;
601
+ transform: translateX(0);
602
+ }
603
+ }
604
+
605
+ @keyframes smm-slideToStart {
606
+ from {
607
+ opacity: 1;
608
+ transform: translateX(0);
609
+ }
610
+ to {
611
+ opacity: 0;
612
+ transform: translateX(-100%);
613
+ }
614
+ }
615
+
616
+ @keyframes smm-slideToEnd {
617
+ from {
618
+ opacity: 1;
619
+ transform: translateX(0);
620
+ }
621
+ to {
622
+ opacity: 0;
623
+ transform: translateX(100%);
624
+ }
625
+ }
626
+
627
+ @keyframes smm-fadeIn {
628
+ from { opacity: 0; }
629
+ to { opacity: 1; }
630
+ }
631
+
632
+ @keyframes smm-fadeOut {
633
+ from { opacity: 1; }
634
+ to { opacity: 0; }
635
+ }
636
+
637
+ @keyframes smm-slideInRight {
638
+ from {
639
+ transform: translateX(100%);
640
+ }
641
+ to {
642
+ transform: translateX(0);
643
+ }
644
+ }
645
+
646
+ /* ── Reduced motion ─────────────────────────────────────────────── */
647
+
648
+ @media (prefers-reduced-motion: reduce) {
649
+ .smm-viewport,
650
+ .smm-content,
651
+ .smm-indicator,
652
+ .smm-chevron,
653
+ .smm-mobile-chevron,
654
+ .smm-mobile-overlay,
655
+ .smm-mobile-backdrop,
656
+ .smm-footer-arrow {
657
+ animation-duration: 0s !important;
658
+ transition-duration: 0s !important;
659
+ }
660
+ }
661
+
662
+ /* ── Print ──────────────────────────────────────────────────────── */
663
+
664
+ @media print {
665
+ .smm-root,
666
+ .smm-mobile-button,
667
+ .smm-mobile-backdrop,
668
+ .smm-mobile-overlay {
669
+ display: none !important;
670
+ }
671
+ }
package/env.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ declare module 'virtual:starlight-mega-menu/config' {
2
+ const config: import('./types').MegaMenuConfig;
3
+ export default config;
4
+ }
package/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { StarlightPlugin } from '@astrojs/starlight/types';
2
+ import react from '@astrojs/react';
3
+
4
+ import type { MegaMenuConfig } from './types';
5
+ import { vitePluginMegaMenuConfig } from './libs/vite';
6
+
7
+ export type {
8
+ MegaMenuConfig,
9
+ MegaMenuItem,
10
+ MegaMenuPanel,
11
+ MegaMenuCategory,
12
+ MegaMenuLink,
13
+ MegaMenuFooter,
14
+ } from './types';
15
+
16
+ export default function starlightMegaMenu(config: MegaMenuConfig): StarlightPlugin {
17
+ return {
18
+ name: 'starlight-mega-menu',
19
+ hooks: {
20
+ 'config:setup'({ config: starlightConfig, updateConfig, addIntegration, astroConfig, logger }) {
21
+ // 1. Conditionally add @astrojs/react if not already loaded
22
+ const isReactLoaded = astroConfig.integrations.find(
23
+ ({ name }) => name === '@astrojs/react'
24
+ );
25
+ if (!isReactLoaded) {
26
+ addIntegration(react());
27
+ }
28
+
29
+ // 2. Add Vite virtual module via Astro integration
30
+ addIntegration({
31
+ name: 'starlight-mega-menu-integration',
32
+ hooks: {
33
+ 'astro:config:setup': ({ updateConfig: updateAstroConfig }) => {
34
+ updateAstroConfig({
35
+ vite: {
36
+ plugins: [vitePluginMegaMenuConfig(config)],
37
+ },
38
+ });
39
+ },
40
+ },
41
+ });
42
+
43
+ // 3. Override Header component + inject CSS
44
+ updateConfig({
45
+ customCss: [
46
+ ...(starlightConfig.customCss ?? []),
47
+ 'starlight-mega-menu/components/mega-menu.css',
48
+ ],
49
+ components: {
50
+ ...starlightConfig.components,
51
+ Header: 'starlight-mega-menu/components/Header.astro',
52
+ },
53
+ });
54
+
55
+ logger.info('Mega-menu plugin loaded');
56
+ },
57
+ },
58
+ };
59
+ }