@arclux/arc-ui 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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/package.json +186 -0
  4. package/src/content/accordion-item.js +27 -0
  5. package/src/content/accordion.js +151 -0
  6. package/src/content/animated-number.js +160 -0
  7. package/src/content/aspect-ratio.js +78 -0
  8. package/src/content/avatar-group.js +101 -0
  9. package/src/content/avatar.js +92 -0
  10. package/src/content/badge.js +98 -0
  11. package/src/content/callout.js +141 -0
  12. package/src/content/card.js +75 -0
  13. package/src/content/carousel.js +300 -0
  14. package/src/content/code-block.js +152 -0
  15. package/src/content/collapsible.js +142 -0
  16. package/src/content/color-swatch.js +86 -0
  17. package/src/content/column.js +28 -0
  18. package/src/content/data-table.js +332 -0
  19. package/src/content/divider.js +153 -0
  20. package/src/content/empty-state.js +78 -0
  21. package/src/content/feature-card.js +142 -0
  22. package/src/content/highlight.js +63 -0
  23. package/src/content/icon-library.js +30 -0
  24. package/src/content/icon-registry.js +39 -0
  25. package/src/content/icon.js +95 -0
  26. package/src/content/index.js +44 -0
  27. package/src/content/infinite-scroll.js +144 -0
  28. package/src/content/kbd.js +40 -0
  29. package/src/content/markdown.js +294 -0
  30. package/src/content/marquee.js +166 -0
  31. package/src/content/meter.js +167 -0
  32. package/src/content/scroll-area.js +107 -0
  33. package/src/content/skeleton.js +85 -0
  34. package/src/content/spinner.js +77 -0
  35. package/src/content/stack.js +68 -0
  36. package/src/content/stat.js +72 -0
  37. package/src/content/step.js +22 -0
  38. package/src/content/stepper.js +202 -0
  39. package/src/content/table.js +134 -0
  40. package/src/content/tag.js +156 -0
  41. package/src/content/text.js +111 -0
  42. package/src/content/timeline-item.js +29 -0
  43. package/src/content/timeline.js +170 -0
  44. package/src/content/truncate.js +161 -0
  45. package/src/content/value-card.js +94 -0
  46. package/src/feedback/alert.js +187 -0
  47. package/src/feedback/command-item.js +28 -0
  48. package/src/feedback/command-palette.js +346 -0
  49. package/src/feedback/context-menu.js +299 -0
  50. package/src/feedback/dialog.js +298 -0
  51. package/src/feedback/dropdown-menu.js +259 -0
  52. package/src/feedback/hover-card.js +186 -0
  53. package/src/feedback/index.js +17 -0
  54. package/src/feedback/modal.js +226 -0
  55. package/src/feedback/notification-panel.js +196 -0
  56. package/src/feedback/popover.js +184 -0
  57. package/src/feedback/progress.js +169 -0
  58. package/src/feedback/sheet.js +249 -0
  59. package/src/feedback/toast.js +207 -0
  60. package/src/feedback/tooltip.js +189 -0
  61. package/src/icons/lucide.d.ts +1915 -0
  62. package/src/icons/lucide.js +1915 -0
  63. package/src/icons/phosphor.d.ts +1517 -0
  64. package/src/icons/phosphor.js +1517 -0
  65. package/src/icons/types.d.ts +8 -0
  66. package/src/index.js +9 -0
  67. package/src/input/button.js +127 -0
  68. package/src/input/calendar.js +340 -0
  69. package/src/input/checkbox.js +159 -0
  70. package/src/input/chip.js +120 -0
  71. package/src/input/color-picker.js +461 -0
  72. package/src/input/combobox.js +295 -0
  73. package/src/input/copy-button.js +144 -0
  74. package/src/input/date-picker.js +534 -0
  75. package/src/input/file-upload.js +333 -0
  76. package/src/input/form.js +179 -0
  77. package/src/input/icon-button.js +179 -0
  78. package/src/input/index.js +31 -0
  79. package/src/input/input.js +158 -0
  80. package/src/input/multi-select.js +392 -0
  81. package/src/input/number-input.js +239 -0
  82. package/src/input/otp-input.js +221 -0
  83. package/src/input/pin-input.js +294 -0
  84. package/src/input/radio-group.js +177 -0
  85. package/src/input/radio.js +28 -0
  86. package/src/input/rating.js +195 -0
  87. package/src/input/search.js +371 -0
  88. package/src/input/segmented-control.js +174 -0
  89. package/src/input/select.js +267 -0
  90. package/src/input/slider.js +217 -0
  91. package/src/input/sortable-list.js +345 -0
  92. package/src/input/suggestion.js +26 -0
  93. package/src/input/textarea.js +203 -0
  94. package/src/input/theme-toggle.js +196 -0
  95. package/src/input/toggle.js +166 -0
  96. package/src/layout/app-shell.js +266 -0
  97. package/src/layout/auth-shell.js +153 -0
  98. package/src/layout/container.js +37 -0
  99. package/src/layout/dashboard-grid.js +62 -0
  100. package/src/layout/index.js +15 -0
  101. package/src/layout/page-header.js +100 -0
  102. package/src/layout/page-layout.js +112 -0
  103. package/src/layout/resizable.js +221 -0
  104. package/src/layout/section.js +54 -0
  105. package/src/layout/settings-layout.js +91 -0
  106. package/src/layout/split-pane.js +172 -0
  107. package/src/layout/status-bar.js +84 -0
  108. package/src/layout/toolbar.js +92 -0
  109. package/src/navigation/breadcrumb-item.js +26 -0
  110. package/src/navigation/breadcrumb.js +129 -0
  111. package/src/navigation/drawer.js +183 -0
  112. package/src/navigation/footer.js +99 -0
  113. package/src/navigation/index.js +22 -0
  114. package/src/navigation/link.js +120 -0
  115. package/src/navigation/nav-item.js +46 -0
  116. package/src/navigation/navigation-menu.js +687 -0
  117. package/src/navigation/pagination.js +186 -0
  118. package/src/navigation/scroll-spy.js +198 -0
  119. package/src/navigation/scroll-to-top.js +163 -0
  120. package/src/navigation/sidebar-link.js +28 -0
  121. package/src/navigation/sidebar-section.js +45 -0
  122. package/src/navigation/sidebar.js +336 -0
  123. package/src/navigation/spy-link.js +26 -0
  124. package/src/navigation/tab.js +26 -0
  125. package/src/navigation/tabs.js +202 -0
  126. package/src/navigation/top-bar.js +263 -0
  127. package/src/navigation/tree-item.js +38 -0
  128. package/src/navigation/tree-view.js +255 -0
  129. package/src/shared/index.js +6 -0
  130. package/src/shared/menu-divider.js +9 -0
  131. package/src/shared/menu-item.js +30 -0
  132. package/src/shared/option.js +31 -0
  133. package/src/shared-styles.js +81 -0
  134. package/src/tokens.js +445 -0
  135. package/types/index.d.ts +973 -0
@@ -0,0 +1,687 @@
1
+ import { LitElement, html, css, render as litRender } from 'lit';
2
+ import { tokenStyles } from '../shared-styles.js';
3
+ import './nav-item.js';
4
+
5
+ export class ArcNavigationMenu extends LitElement {
6
+ static properties = {
7
+ _items: { state: true },
8
+ _openIndex: { state: true },
9
+ _mobileOpen: { state: true },
10
+ _mobileClosing: { state: true },
11
+ _mobileExpandedIndex: { state: true },
12
+ };
13
+
14
+ static styles = [
15
+ tokenStyles,
16
+ css`
17
+ :host {
18
+ display: block;
19
+ position: relative;
20
+ font-family: var(--font-body);
21
+ }
22
+
23
+ .nav {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: var(--space-xs);
27
+ }
28
+
29
+ .nav__item {
30
+ position: relative;
31
+ display: flex;
32
+ align-items: center;
33
+ }
34
+
35
+ .nav__trigger {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: var(--space-xs);
39
+ font-family: var(--font-accent);
40
+ font-size: var(--text-xs);
41
+ font-weight: 600;
42
+ letter-spacing: 2px;
43
+ text-transform: uppercase;
44
+ color: var(--text-muted);
45
+ background: transparent;
46
+ border: 1px solid var(--border-subtle);
47
+ border-radius: var(--radius-sm);
48
+ padding: var(--space-sm) calc(var(--space-sm) + var(--space-xs));
49
+ cursor: pointer;
50
+ text-decoration: none;
51
+ transition: color var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast), box-shadow var(--transition-fast);
52
+ white-space: nowrap;
53
+ }
54
+
55
+ .nav__trigger:hover,
56
+ .nav__trigger--open {
57
+ color: var(--text-primary);
58
+ background: var(--bg-hover);
59
+ border-color: var(--border-default);
60
+ }
61
+
62
+ .nav__trigger--active {
63
+ color: var(--accent-primary);
64
+ background: rgba(var(--accent-primary-rgb), 0.06);
65
+ border-color: rgba(var(--accent-primary-rgb), 0.3);
66
+ box-shadow: inset 0 0 8px rgba(var(--accent-primary-rgb), 0.06), 0 0 8px rgba(var(--accent-primary-rgb), 0.08);
67
+ }
68
+
69
+ .nav__trigger--active:hover {
70
+ border-color: rgba(var(--accent-primary-rgb), 0.5);
71
+ box-shadow: inset 0 0 8px rgba(var(--accent-primary-rgb), 0.08), 0 0 12px rgba(var(--accent-primary-rgb), 0.12);
72
+ }
73
+
74
+ .nav__trigger:focus-visible {
75
+ outline: none;
76
+ box-shadow: var(--focus-glow);
77
+ }
78
+
79
+ .nav__chevron {
80
+ display: inline-block;
81
+ width: 10px;
82
+ height: 10px;
83
+ transition: transform var(--transition-fast);
84
+ }
85
+
86
+ .nav__chevron--open {
87
+ transform: rotate(180deg);
88
+ }
89
+
90
+ .nav__dropdown {
91
+ position: absolute;
92
+ top: 100%;
93
+ left: 0;
94
+ min-width: 280px;
95
+ background: var(--bg-card);
96
+ border: 1px solid var(--border-default);
97
+ border-radius: var(--radius-md);
98
+ padding: var(--space-sm);
99
+ box-shadow: var(--shadow-overlay);
100
+ z-index: 1000;
101
+ opacity: 0;
102
+ transform: translateY(4px);
103
+ pointer-events: none;
104
+ transition: opacity var(--transition-fast), transform var(--transition-fast);
105
+ }
106
+
107
+ .nav__dropdown--open {
108
+ opacity: 1;
109
+ transform: translateY(0);
110
+ pointer-events: auto;
111
+ }
112
+
113
+ .nav__dropdown-item {
114
+ display: block;
115
+ width: 100%;
116
+ text-align: left;
117
+ background: none;
118
+ border: none;
119
+ padding: var(--touch-pad) var(--space-md);
120
+ min-height: var(--touch-min);
121
+ border-radius: var(--radius-sm);
122
+ cursor: pointer;
123
+ text-decoration: none;
124
+ color: var(--text-primary);
125
+ transition: background var(--transition-fast);
126
+ }
127
+
128
+ .nav__dropdown-item:hover {
129
+ background: rgba(var(--accent-primary-rgb), 0.08);
130
+ }
131
+
132
+ .nav__dropdown-item:focus-visible {
133
+ outline: none;
134
+ box-shadow: var(--focus-glow);
135
+ }
136
+
137
+ .nav__dropdown-label {
138
+ display: block;
139
+ font-size: var(--text-sm);
140
+ font-weight: 500;
141
+ color: var(--text-primary);
142
+ margin-bottom: 2px; /* cosmetic micro-spacing */
143
+ }
144
+
145
+ .nav__dropdown-desc {
146
+ display: block;
147
+ font-size: var(--text-sm);
148
+ color: var(--text-muted);
149
+ line-height: 1.4;
150
+ }
151
+
152
+ .nav__slot-host { display: none; }
153
+
154
+ /* ── Mobile panel ── */
155
+ @media (max-width: 900px) {
156
+ .nav { display: none; }
157
+ }
158
+
159
+ .mobile-backdrop {
160
+ display: none;
161
+ position: fixed;
162
+ inset: 0;
163
+ top: var(--nav-height);
164
+ background: var(--overlay-backdrop);
165
+ z-index: 98;
166
+ }
167
+
168
+ .mobile-backdrop--open {
169
+ display: block;
170
+ animation: mobile-fade-in var(--duration-enter) var(--ease-out-expo) both;
171
+ }
172
+
173
+ .mobile-backdrop--closing {
174
+ display: block;
175
+ animation: mobile-fade-out var(--duration-exit) ease-in both;
176
+ }
177
+
178
+ .mobile-panel {
179
+ display: none;
180
+ position: fixed;
181
+ top: var(--nav-height);
182
+ left: 0;
183
+ right: 0;
184
+ max-height: 0;
185
+ overflow: hidden;
186
+ overscroll-behavior: contain;
187
+ background: color-mix(in srgb, var(--bg-deep) 92%, transparent);
188
+ backdrop-filter: blur(12px) saturate(130%);
189
+ -webkit-backdrop-filter: blur(12px) saturate(130%);
190
+ border-bottom: 1px solid var(--border-subtle);
191
+ box-shadow: var(--shadow-lg);
192
+ z-index: 99;
193
+ }
194
+
195
+ .mobile-panel--open {
196
+ display: block;
197
+ overflow-x: hidden;
198
+ overflow-y: auto;
199
+ animation: mobile-slide-in var(--duration-enter) var(--ease-out-expo) both;
200
+ }
201
+
202
+ .mobile-panel--closing {
203
+ display: block;
204
+ animation: mobile-slide-out var(--duration-exit) ease-in both;
205
+ }
206
+
207
+ .mobile-panel--open .mobile-item {
208
+ animation: mobile-item-in 400ms var(--ease-out-expo) both;
209
+ }
210
+
211
+ .mobile-panel--closing .mobile-item {
212
+ animation: mobile-item-out var(--duration-exit) ease-in both;
213
+ }
214
+
215
+ @keyframes mobile-fade-in {
216
+ from { opacity: 0; }
217
+ to { opacity: 1; }
218
+ }
219
+
220
+ @keyframes mobile-fade-out {
221
+ from { opacity: 1; }
222
+ to { opacity: 0; }
223
+ }
224
+
225
+ @keyframes mobile-slide-in {
226
+ from { max-height: 0; opacity: 0; }
227
+ to { max-height: calc(100dvh - var(--nav-height)); opacity: 1; }
228
+ }
229
+
230
+ @keyframes mobile-slide-out {
231
+ from { max-height: calc(100dvh - var(--nav-height)); opacity: 1; }
232
+ to { max-height: 0; opacity: 0; }
233
+ }
234
+
235
+ @keyframes mobile-item-in {
236
+ from { opacity: 0; transform: translateY(-8px); }
237
+ to { opacity: 1; transform: translateY(0); }
238
+ }
239
+
240
+ @keyframes mobile-item-out {
241
+ from { opacity: 1; transform: translateY(0); }
242
+ to { opacity: 0; transform: translateY(-8px); }
243
+ }
244
+
245
+ .mobile-glow {
246
+ height: 2px;
247
+ background: var(--glow-line-gradient);
248
+ }
249
+
250
+ .mobile-list {
251
+ list-style: none;
252
+ margin: 0;
253
+ padding: var(--space-sm) 0;
254
+ }
255
+
256
+ .mobile-item {
257
+ border-bottom: 1px solid var(--border-subtle);
258
+ }
259
+
260
+ .mobile-item:last-child {
261
+ border-bottom: none;
262
+ }
263
+
264
+ .mobile-trigger {
265
+ display: flex;
266
+ align-items: center;
267
+ justify-content: space-between;
268
+ width: 100%;
269
+ padding: var(--space-md) var(--space-lg);
270
+ min-height: var(--touch-min);
271
+ background: none;
272
+ border: none;
273
+ color: var(--text-primary);
274
+ font-family: var(--font-accent);
275
+ font-size: var(--section-title-size);
276
+ font-weight: var(--section-title-weight);
277
+ letter-spacing: var(--section-title-spacing);
278
+ text-transform: uppercase;
279
+ text-decoration: none;
280
+ cursor: pointer;
281
+ transition: background var(--transition-fast);
282
+ }
283
+
284
+ .mobile-trigger:hover {
285
+ background: var(--bg-hover);
286
+ }
287
+
288
+ .mobile-trigger--active {
289
+ color: var(--accent-primary);
290
+ background: rgba(var(--accent-primary-rgb), 0.06);
291
+ }
292
+
293
+ .mobile-trigger--active:hover {
294
+ background: rgba(var(--accent-primary-rgb), 0.1);
295
+ }
296
+
297
+ .mobile-chevron {
298
+ width: 12px;
299
+ height: 12px;
300
+ transition: transform var(--transition-fast);
301
+ flex-shrink: 0;
302
+ }
303
+
304
+ .mobile-chevron--open {
305
+ transform: rotate(180deg);
306
+ }
307
+
308
+ .mobile-children {
309
+ max-height: 0;
310
+ overflow: hidden;
311
+ transition: max-height var(--transition-slow);
312
+ }
313
+
314
+ .mobile-children--open {
315
+ max-height: 500px;
316
+ }
317
+
318
+ .mobile-child {
319
+ display: block;
320
+ padding: var(--space-sm) var(--space-lg) var(--space-sm) var(--space-xl);
321
+ min-height: var(--touch-min);
322
+ text-decoration: none;
323
+ color: var(--text-primary);
324
+ font-weight: 400;
325
+ font-size: var(--body-size);
326
+ transition: background var(--transition-fast);
327
+ cursor: pointer;
328
+ }
329
+
330
+ .mobile-child:hover {
331
+ background: rgba(var(--accent-primary-rgb), 0.08);
332
+ }
333
+
334
+ .mobile-child-label {
335
+ display: block;
336
+ }
337
+
338
+ .mobile-child-desc {
339
+ display: block;
340
+ font-size: var(--text-sm);
341
+ color: var(--text-muted);
342
+ line-height: 1.4;
343
+ margin-top: 2px; /* cosmetic micro-spacing */
344
+ }
345
+
346
+ @media (prefers-reduced-motion: reduce) {
347
+ :host *,
348
+ :host *::before,
349
+ :host *::after {
350
+ animation-duration: 0.01ms !important;
351
+ animation-iteration-count: 1 !important;
352
+ transition-duration: 0.01ms !important;
353
+ }
354
+ }
355
+ `,
356
+ ];
357
+
358
+ constructor() {
359
+ super();
360
+ this._items = [];
361
+ this._openIndex = -1;
362
+ this._mobileOpen = false;
363
+ this._mobileClosing = false;
364
+ this._mobileExpandedIndex = -1;
365
+ this._closeTimeout = null;
366
+ this._onKeyDown = this._onKeyDown.bind(this);
367
+ this._onMobileToggle = this._onMobileToggle.bind(this);
368
+ this._onResize = this._onResize.bind(this);
369
+ this._onPanelAnimEnd = this._onPanelAnimEnd.bind(this);
370
+ }
371
+
372
+ connectedCallback() {
373
+ super.connectedCallback();
374
+ this.addEventListener('keydown', this._onKeyDown);
375
+ document.addEventListener('arc-mobile-menu-toggle', this._onMobileToggle);
376
+ window.addEventListener('resize', this._onResize);
377
+ this._createPortal();
378
+ }
379
+
380
+ disconnectedCallback() {
381
+ super.disconnectedCallback();
382
+ this.removeEventListener('keydown', this._onKeyDown);
383
+ document.removeEventListener('arc-mobile-menu-toggle', this._onMobileToggle);
384
+ window.removeEventListener('resize', this._onResize);
385
+ clearTimeout(this._closeTimeout);
386
+ this._unlockScroll();
387
+ this._destroyPortal();
388
+ }
389
+
390
+ _createPortal() {
391
+ this._portal = document.createElement('div');
392
+ this._portal.setAttribute('data-arc-nav-portal', '');
393
+ this._portalRoot = this._portal.attachShadow({ mode: 'open' });
394
+ document.body.appendChild(this._portal);
395
+ }
396
+
397
+ _destroyPortal() {
398
+ if (this._portal?.parentNode) {
399
+ this._portal.parentNode.removeChild(this._portal);
400
+ }
401
+ this._portal = null;
402
+ this._portalRoot = null;
403
+ }
404
+
405
+ _onSlotChange(e) {
406
+ this._items = e.target.assignedElements({ flatten: true })
407
+ .filter(el => el.tagName === 'ARC-NAV-ITEM');
408
+ }
409
+
410
+ _onKeyDown(e) {
411
+ if (e.key === 'Escape') {
412
+ if (this._mobileOpen) {
413
+ this._closeMobile();
414
+ return;
415
+ }
416
+ if (this._openIndex >= 0) {
417
+ this._close();
418
+ const triggers = this.shadowRoot.querySelectorAll('.nav__trigger');
419
+ triggers[this._openIndex]?.focus();
420
+ }
421
+ }
422
+ }
423
+
424
+ /* ── Desktop dropdown ── */
425
+
426
+ _open(index) {
427
+ clearTimeout(this._closeTimeout);
428
+ this._openIndex = index;
429
+ }
430
+
431
+ _close() {
432
+ this._openIndex = -1;
433
+ }
434
+
435
+ _scheduleClose() {
436
+ clearTimeout(this._closeTimeout);
437
+ this._closeTimeout = setTimeout(() => this._close(), 150);
438
+ }
439
+
440
+ _cancelClose() {
441
+ clearTimeout(this._closeTimeout);
442
+ }
443
+
444
+ _navigate(e, href, item) {
445
+ this._close();
446
+ if (this._mobileOpen) this._closeMobile();
447
+ const nav = new CustomEvent('arc-navigate', {
448
+ detail: { href, item: { label: item.label, href: item.href, description: item.description } },
449
+ bubbles: true,
450
+ composed: true,
451
+ cancelable: true,
452
+ });
453
+ this.dispatchEvent(nav);
454
+ if (nav.defaultPrevented) {
455
+ e.preventDefault();
456
+ }
457
+ }
458
+
459
+ _handleTriggerClick(e, item, index) {
460
+ if (item.hasChildren) {
461
+ if (this._openIndex === index) {
462
+ this._close();
463
+ } else {
464
+ this._open(index);
465
+ }
466
+ } else if (item.href) {
467
+ this._navigate(e, item.href, item);
468
+ }
469
+ }
470
+
471
+ /* ── Mobile panel ── */
472
+
473
+ _onMobileToggle(e) {
474
+ const shouldOpen = e.detail?.value ?? !this._mobileOpen;
475
+ if (shouldOpen) {
476
+ this._openMobile();
477
+ } else {
478
+ this._closeMobile();
479
+ }
480
+ }
481
+
482
+ _onResize() {
483
+ if (window.innerWidth > 900 && this._mobileOpen) {
484
+ this._closeMobile();
485
+ }
486
+ }
487
+
488
+ _openMobile() {
489
+ if (this._mobileOpen) return;
490
+ this._mobileOpen = true;
491
+ this._lockScroll();
492
+ }
493
+
494
+ _closeMobile() {
495
+ if (!this._mobileOpen || this._mobileClosing) return;
496
+ this._mobileClosing = true;
497
+ this._mobileExpandedIndex = -1;
498
+ document.dispatchEvent(new CustomEvent('arc-mobile-menu-toggle', {
499
+ detail: { value: false },
500
+ }));
501
+ }
502
+
503
+ _onPanelAnimEnd(e) {
504
+ if (e.animationName === 'mobile-slide-out') {
505
+ this._mobileOpen = false;
506
+ this._mobileClosing = false;
507
+ this._unlockScroll();
508
+ }
509
+ }
510
+
511
+ _lockScroll() {
512
+ document.body.style.overflow = 'hidden';
513
+ }
514
+
515
+ _unlockScroll() {
516
+ document.body.style.overflow = '';
517
+ }
518
+
519
+ _onBackdropClick() {
520
+ this._closeMobile();
521
+ }
522
+
523
+ updated() {
524
+ super.updated();
525
+ this._renderPortal();
526
+ }
527
+
528
+ _renderPortal() {
529
+ if (!this._portalRoot) return;
530
+ if (!this._portalStyled) {
531
+ const sheet = new CSSStyleSheet();
532
+ const cssTexts = (this.constructor.elementStyles || [])
533
+ .map(s => s.cssText ?? s.toString());
534
+ sheet.replaceSync(cssTexts.join('\n'));
535
+ this._portalRoot.adoptedStyleSheets = [sheet];
536
+ this._portalStyled = true;
537
+ }
538
+ litRender(this._renderMobileOverlay(), this._portalRoot);
539
+ }
540
+
541
+ _toggleMobileDropdown(index) {
542
+ this._mobileExpandedIndex = this._mobileExpandedIndex === index ? -1 : index;
543
+ }
544
+
545
+ _handleMobileTriggerClick(e, item, index) {
546
+ if (item.hasChildren) {
547
+ this._toggleMobileDropdown(index);
548
+ } else if (item.href) {
549
+ this._navigate(e, item.href, item);
550
+ }
551
+ }
552
+
553
+ render() {
554
+ return html`
555
+ <div class="nav__slot-host">
556
+ <slot @slotchange=${this._onSlotChange}></slot>
557
+ </div>
558
+ <nav class="nav" part="nav" aria-label="Navigation menu">
559
+ ${this._items.map((item, i) => {
560
+ const hasChildren = item.hasChildren;
561
+ const isOpen = this._openIndex === i;
562
+
563
+ return html`
564
+ <div
565
+ class="nav__item"
566
+ @mouseenter=${hasChildren ? () => this._open(i) : null}
567
+ @mouseleave=${hasChildren ? () => this._scheduleClose() : null}
568
+ part="item"
569
+ >
570
+ ${hasChildren ? html`
571
+ <button
572
+ class="nav__trigger nav__trigger--has-children ${isOpen ? 'nav__trigger--open' : ''} ${item.active ? 'nav__trigger--active' : ''}"
573
+ @click=${(e) => this._handleTriggerClick(e, item, i)}
574
+ aria-expanded=${String(isOpen)}
575
+ aria-haspopup="true"
576
+ part="trigger"
577
+ >
578
+ ${item.label}
579
+ <svg class="nav__chevron ${isOpen ? 'nav__chevron--open' : ''}" viewBox="0 0 10 10" fill="none" aria-hidden="true">
580
+ <path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
581
+ </svg>
582
+ </button>
583
+ ` : html`
584
+ <a
585
+ class="nav__trigger ${item.active ? 'nav__trigger--active' : ''}"
586
+ href=${item.href}
587
+ @click=${(e) => this._handleTriggerClick(e, item, i)}
588
+ part="trigger"
589
+ >
590
+ ${item.label}
591
+ </a>
592
+ `}
593
+
594
+ ${hasChildren ? html`
595
+ <div
596
+ class="nav__dropdown ${isOpen ? 'nav__dropdown--open' : ''}"
597
+ @mouseenter=${() => this._cancelClose()}
598
+ @mouseleave=${() => this._scheduleClose()}
599
+ role="menu"
600
+ part="dropdown"
601
+ >
602
+ ${item.children.map(child => html`
603
+ <a
604
+ class="nav__dropdown-item"
605
+ href=${child.href}
606
+ role="menuitem"
607
+ @click=${(e) => this._navigate(e, child.href, child)}
608
+ part="dropdown-item"
609
+ >
610
+ <span class="nav__dropdown-label">${child.label}</span>
611
+ ${child.description ? html`
612
+ <span class="nav__dropdown-desc">${child.description}</span>
613
+ ` : ''}
614
+ </a>
615
+ `)}
616
+ </div>
617
+ ` : ''}
618
+ </div>
619
+ `;
620
+ })}
621
+ </nav>
622
+
623
+ `;
624
+ }
625
+
626
+ _renderMobileOverlay() {
627
+ return html`
628
+ <div
629
+ class="mobile-backdrop ${this._mobileOpen && !this._mobileClosing ? 'mobile-backdrop--open' : ''} ${this._mobileClosing ? 'mobile-backdrop--closing' : ''}"
630
+ @click=${this._onBackdropClick}
631
+ ></div>
632
+ <div
633
+ class="mobile-panel ${this._mobileOpen && !this._mobileClosing ? 'mobile-panel--open' : ''} ${this._mobileClosing ? 'mobile-panel--closing' : ''}"
634
+ @animationend=${this._onPanelAnimEnd}
635
+ >
636
+ <div class="mobile-glow"></div>
637
+ <ul class="mobile-list">
638
+ ${this._items.map((item, i) => {
639
+ const hasChildren = item.hasChildren;
640
+ const isExpanded = this._mobileExpandedIndex === i;
641
+
642
+ return html`
643
+ <li class="mobile-item" style="animation-delay: ${i * 60}ms">
644
+ ${hasChildren ? html`
645
+ <button
646
+ class="mobile-trigger ${item.active ? 'mobile-trigger--active' : ''}"
647
+ @click=${() => this._toggleMobileDropdown(i)}
648
+ aria-expanded=${String(isExpanded)}
649
+ >
650
+ ${item.label}
651
+ <svg class="mobile-chevron ${isExpanded ? 'mobile-chevron--open' : ''}" viewBox="0 0 10 10" fill="none" aria-hidden="true">
652
+ <path d="M2 3.5L5 6.5L8 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
653
+ </svg>
654
+ </button>
655
+ <div class="mobile-children ${isExpanded ? 'mobile-children--open' : ''}">
656
+ ${item.children.map(child => html`
657
+ <a
658
+ class="mobile-child"
659
+ href=${child.href}
660
+ @click=${(e) => this._navigate(e, child.href, child)}
661
+ >
662
+ <span class="mobile-child-label">${child.label}</span>
663
+ ${child.description ? html`
664
+ <span class="mobile-child-desc">${child.description}</span>
665
+ ` : ''}
666
+ </a>
667
+ `)}
668
+ </div>
669
+ ` : html`
670
+ <a
671
+ class="mobile-trigger ${item.active ? 'mobile-trigger--active' : ''}"
672
+ href=${item.href}
673
+ @click=${(e) => this._handleMobileTriggerClick(e, item, i)}
674
+ >
675
+ ${item.label}
676
+ </a>
677
+ `}
678
+ </li>
679
+ `;
680
+ })}
681
+ </ul>
682
+ </div>
683
+ `;
684
+ }
685
+ }
686
+
687
+ customElements.define('arc-navigation-menu', ArcNavigationMenu);