@rimelight/ui 0.0.34 → 0.0.35

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 (185) hide show
  1. package/README.md +1 -0
  2. package/package.json +19 -24
  3. package/src/components/astro/RLAAccordion.astro +94 -18
  4. package/src/components/astro/RLAAvatar.astro +135 -68
  5. package/src/components/astro/RLABadge.astro +4 -2
  6. package/src/components/astro/RLABanner.astro +138 -23
  7. package/src/components/astro/RLABreadcrumb.astro +13 -0
  8. package/src/components/astro/RLAButton.astro +8 -4
  9. package/src/components/astro/RLACallout.astro +112 -0
  10. package/src/components/astro/RLACard.astro +6 -5
  11. package/src/components/astro/RLACarousel.astro +104 -75
  12. package/src/components/astro/RLAChart.astro +4 -1
  13. package/src/components/astro/RLAChip.astro +11 -5
  14. package/src/components/astro/RLACollapsible.astro +4 -1
  15. package/src/components/astro/RLAColorArea.astro +432 -0
  16. package/src/components/astro/RLAColorField.astro +300 -0
  17. package/src/components/astro/RLAColorSlider.astro +413 -0
  18. package/src/components/astro/RLAConfirm.astro +13 -11
  19. package/src/components/astro/RLACopyMarkdown.astro +78 -0
  20. package/src/components/astro/RLADate.astro +30 -0
  21. package/src/components/astro/RLADrawer.astro +50 -3
  22. package/src/components/astro/RLADropdownMenu.astro +78 -51
  23. package/src/components/astro/RLAFieldGroup.astro +4 -1
  24. package/src/components/astro/RLAFileUpload.astro +114 -100
  25. package/src/components/astro/RLAFooter.astro +2 -1
  26. package/src/components/astro/RLAFormField.astro +10 -6
  27. package/src/components/astro/RLAGrid.astro +31 -39
  28. package/src/components/astro/RLAHead.astro +139 -39
  29. package/src/components/astro/RLAHeader.astro +2 -1
  30. package/src/components/astro/RLAImage.astro +274 -251
  31. package/src/components/astro/RLAInput.astro +14 -9
  32. package/src/components/astro/RLAInputMenu.astro +119 -83
  33. package/src/components/astro/RLAKbd.astro +5 -5
  34. package/src/components/astro/RLALayoutGrid.astro +395 -0
  35. package/src/components/astro/RLALink.astro +23 -1
  36. package/src/components/astro/RLALinkGroup.astro +99 -99
  37. package/src/components/astro/RLALocaleSelector.astro +29 -35
  38. package/src/components/astro/RLALogo.astro +117 -90
  39. package/src/components/astro/RLAModal.astro +48 -18
  40. package/src/components/astro/RLANavigationMenu.astro +381 -326
  41. package/src/components/astro/{RLADocsPackageManagers.astro → RLAPackageManagers.astro} +81 -87
  42. package/src/components/astro/RLAPageSection.astro +4 -2
  43. package/src/components/astro/RLAPopover.astro +4 -3
  44. package/src/components/astro/RLAPost.astro +11 -2
  45. package/src/components/astro/RLAScrollArea.astro +4 -1
  46. package/src/components/astro/RLAScrollToTop.astro +200 -172
  47. package/src/components/astro/RLASearch.astro +282 -0
  48. package/src/components/astro/RLASelect.astro +78 -55
  49. package/src/components/astro/RLASeparator.astro +11 -6
  50. package/src/components/astro/RLAShare.astro +60 -0
  51. package/src/components/astro/RLAShortcuts.astro +429 -0
  52. package/src/components/astro/RLASidebar.astro +158 -212
  53. package/src/components/astro/RLASlideover.astro +47 -17
  54. package/src/components/astro/RLASplitter.astro +194 -141
  55. package/src/components/astro/RLASteps.astro +118 -71
  56. package/src/components/astro/RLASurround.astro +39 -0
  57. package/src/components/astro/RLATable.astro +4 -1
  58. package/src/components/astro/RLATableOfContents.astro +109 -23
  59. package/src/components/astro/RLATabs.astro +147 -133
  60. package/src/components/astro/RLATextarea.astro +4 -1
  61. package/src/components/astro/RLAThemeSelector.astro +105 -76
  62. package/src/components/astro/RLAToast.astro +35 -21
  63. package/src/components/vue/RLVBanner.vue +230 -0
  64. package/src/components/vue/RLVButton.vue +3 -2
  65. package/src/components/vue/RLVCheckbox.vue +4 -2
  66. package/src/components/vue/RLVFooter.vue +3 -2
  67. package/src/components/vue/RLVHeader.vue +3 -2
  68. package/src/components/vue/RLVIcon.vue +4 -2
  69. package/src/components/vue/RLVInput.vue +5 -4
  70. package/src/components/vue/RLVLogo.vue +25 -3
  71. package/src/components/vue/RLVPlaceholder.vue +3 -2
  72. package/src/components/vue/RLVPost.vue +8 -2
  73. package/src/components/vue/RLVProgress.vue +60 -0
  74. package/src/components/vue/RLVScrollToTop.vue +65 -52
  75. package/src/components/vue/RLVSection.vue +14 -13
  76. package/src/components/vue/RLVToast.vue +17 -4
  77. package/src/composables/index.ts +1 -0
  78. package/src/composables/useHeaderStore.ts +1 -0
  79. package/src/composables/useShortcuts.ts +11 -0
  80. package/src/config/security.ts +43 -36
  81. package/src/config/site.config.ts +1 -0
  82. package/src/docs/components/ApiChangelog.astro +147 -0
  83. package/src/{plugins/starlightDocsApi → docs}/components/ApiEmits.astro +4 -4
  84. package/src/{plugins/starlightDocsApi → docs}/components/ApiProps.astro +4 -4
  85. package/src/{plugins/starlightDocsApi → docs}/components/ApiSlots.astro +4 -4
  86. package/src/docs/components/ApiTheme.astro +100 -0
  87. package/src/docs/extractAll.ts +94 -0
  88. package/src/docs/extractors/extractAstro.ts +69 -0
  89. package/src/docs/extractors/extractTheme.ts +56 -0
  90. package/src/{plugins/starlightDocsApi/extract-vue.ts → docs/extractors/extractVue.ts} +164 -15
  91. package/src/{plugins/starlightDocsApi → docs}/utils.ts +1 -2
  92. package/src/env.d.ts +0 -12
  93. package/src/integrations/ui.ts +35 -19
  94. package/src/middleware/security.ts +75 -19
  95. package/src/presets/index.ts +118 -293
  96. package/src/styles/index.css +0 -2
  97. package/src/themes/accordion.theme.ts +29 -3
  98. package/src/themes/aspect-ratio.theme.ts +1 -1
  99. package/src/themes/avatar-group.theme.ts +1 -1
  100. package/src/themes/avatar.theme.ts +2 -2
  101. package/src/themes/badge.theme.ts +2 -3
  102. package/src/themes/banner.theme.ts +40 -4
  103. package/src/themes/breadcrumb.theme.ts +1 -1
  104. package/src/themes/button.theme.ts +21 -22
  105. package/src/themes/{alert.theme.ts → callout.theme.ts} +12 -39
  106. package/src/themes/card.theme.ts +17 -9
  107. package/src/themes/carousel.theme.ts +1 -1
  108. package/src/themes/checkbox.theme.ts +1 -2
  109. package/src/themes/chip.theme.ts +2 -3
  110. package/src/themes/color-area.theme.ts +33 -0
  111. package/src/themes/color-field.theme.ts +32 -0
  112. package/src/themes/color-slider.theme.ts +34 -0
  113. package/src/themes/confirm.theme.ts +1 -1
  114. package/src/themes/container.theme.ts +1 -1
  115. package/src/themes/drawer.theme.ts +1 -1
  116. package/src/themes/footer.theme.ts +2 -2
  117. package/src/themes/grid.theme.ts +38 -0
  118. package/src/themes/header.theme.ts +4 -3
  119. package/src/themes/hover-card.theme.ts +1 -1
  120. package/src/themes/image.theme.ts +11 -1
  121. package/src/themes/input.theme.ts +7 -1
  122. package/src/themes/kbd.theme.ts +2 -3
  123. package/src/themes/label.theme.ts +1 -1
  124. package/src/themes/layout-grid.theme.ts +16 -0
  125. package/src/themes/link-group.theme.ts +0 -4
  126. package/src/themes/marquee.theme.ts +1 -1
  127. package/src/themes/modal.theme.ts +1 -1
  128. package/src/themes/navigation-menu.theme.ts +2 -2
  129. package/src/themes/page-section.theme.ts +4 -2
  130. package/src/themes/pagination.theme.ts +1 -1
  131. package/src/themes/pin-input.theme.ts +1 -1
  132. package/src/themes/popover.theme.ts +1 -1
  133. package/src/themes/post.theme.ts +23 -3
  134. package/src/themes/progress.theme.ts +1 -2
  135. package/src/themes/scroll-to-top.theme.ts +2 -3
  136. package/src/themes/search.theme.ts +64 -0
  137. package/src/themes/separator.theme.ts +1 -1
  138. package/src/themes/slideover.theme.ts +1 -1
  139. package/src/themes/slider.theme.ts +1 -2
  140. package/src/themes/spinner.theme.ts +1 -2
  141. package/src/themes/{megamenu.theme.ts → steps.theme.ts} +3 -3
  142. package/src/themes/sticky-surface.theme.ts +1 -1
  143. package/src/themes/switch.theme.ts +1 -2
  144. package/src/themes/table-of-contents.theme.ts +21 -2
  145. package/src/themes/timeline.theme.ts +7 -2
  146. package/src/themes/toast.theme.ts +1 -1
  147. package/src/types/components/accordion.ts +35 -2
  148. package/src/types/components/avatar.ts +16 -0
  149. package/src/types/components/banner.ts +42 -0
  150. package/src/types/components/button.ts +3 -4
  151. package/src/types/components/{alert.ts → callout.ts} +6 -6
  152. package/src/types/components/color-area.ts +19 -0
  153. package/src/types/components/color-field.ts +21 -0
  154. package/src/types/components/color-slider.ts +20 -0
  155. package/src/types/components/grid.ts +23 -0
  156. package/src/types/components/input.ts +4 -0
  157. package/src/types/components/layout-grid.ts +145 -0
  158. package/src/types/components/link-group.ts +4 -2
  159. package/src/types/components/link.ts +15 -0
  160. package/src/types/components/logo.ts +3 -3
  161. package/src/types/components/post.ts +12 -0
  162. package/src/types/components/search.ts +29 -0
  163. package/src/types/components/sidebar.ts +2 -6
  164. package/src/types/components/{megamenu.ts → steps.ts} +7 -1
  165. package/src/types/components/table-of-contents.ts +22 -0
  166. package/src/types/components/toast.ts +4 -0
  167. package/src/types/docs.ts +40 -0
  168. package/src/types/index.ts +151 -1
  169. package/src/types/theme.ts +6 -6
  170. package/src/utils/color.ts +165 -0
  171. package/src/utils/docs.ts +260 -110
  172. package/src/utils/headerStack.ts +24 -4
  173. package/src/utils/index.ts +2 -0
  174. package/src/utils/shortcuts.ts +322 -0
  175. package/src/components/TableOfContents.astro +0 -107
  176. package/src/components/astro/RLAAlert.astro +0 -93
  177. package/src/components/astro/RLAChangelog.astro +0 -23
  178. package/src/components/astro/RLAMegamenu.astro +0 -23
  179. package/src/plugins/index.ts +0 -2
  180. package/src/plugins/starlightDocsApi/components/ApiTheme.astro +0 -27
  181. package/src/plugins/starlightDocsApi/index.ts +0 -272
  182. package/src/styles/prism.css +0 -42
  183. package/src/themes/changelog.theme.ts +0 -13
  184. package/src/types/components/index.ts +0 -134
  185. /package/src/{plugins/starlightDocsApi → docs}/types.ts +0 -0
@@ -1,326 +1,381 @@
1
- ---
2
- import { scv } from "css-variants"
3
- import { useUi, resolveClasses } from "../../utils"
4
- import type { NavigationMenuProps } from "../../types"
5
- import navigationMenuThemeDefault, { createNavigationMenuTheme } from "../../themes/navigation-menu.theme"
6
-
7
- const navigationMenuTheme = navigationMenuThemeDefault
8
- const navigationMenu = scv(navigationMenuTheme)
9
-
10
- const {
11
- items = [],
12
- color = "primary",
13
- theme,
14
- orientation = "horizontal",
15
- ui: uiProp,
16
- class: className,
17
- ...rest
18
- } = Astro.props as NavigationMenuProps
19
-
20
- const classes = resolveClasses(navigationMenu, { theme, orientation }, useUi("navigationMenu", uiProp), className)
21
- ---
22
-
23
- <nav class={classes.root} data-slot="root" data-nav-menu data-orientation={orientation} {...rest}>
24
- <ul class={classes.list} data-slot="list" role="list">
25
- {items.map((item, i) => {
26
- const hasChildren = item.children && item.children.length > 0
27
-
28
- const itemPath = (item.href ?? item.to ?? "#") as string
29
- const normalizedPath = itemPath.replace(/^\/[a-z]{2}(?:-[a-zA-Z]{2})?(?=\/|$)/, '').replace(/\/+$/, '') || '/'
30
- const currentPath = Astro.url.pathname.replace(/^\/[a-z]{2}(?:-[a-zA-Z]{2})?(?=\/|$)/, '').replace(/\/+$/, '') || '/'
31
-
32
- const isActive = item.active ?? (
33
- normalizedPath !== '' && normalizedPath !== '/'
34
- ? currentPath.startsWith(normalizedPath)
35
- : currentPath === '/'
36
- )
37
-
38
- return (
39
- <li class={classes.item} data-slot="item" data-nav-item data-index={i}>
40
- {hasChildren ? (
41
- <button
42
- type="button"
43
- class={classes.link}
44
- data-slot="trigger"
45
- data-nav-trigger
46
- aria-expanded="false"
47
- aria-haspopup="true"
48
- >
49
- {item.icon && <span class={item.icon + " mr-1.5 size-4"} />}
50
- {item.label}
51
- <span class="i-lucide-chevron-down ml-1 size-3.5 transition-transform duration-200" aria-hidden="true" />
52
- </button>
53
- ) : (
54
- <a
55
- href={item.href ?? item.to ?? "#"}
56
- class={classes.link}
57
- data-slot="link"
58
- aria-current={isActive ? "page" : undefined}
59
- >
60
- {item.icon && <span class={item.icon + " mr-1.5 size-4"} />}
61
- {item.label}
62
- </a>
63
- )}
64
-
65
- {hasChildren && (
66
- <div
67
- class={classes.content + " hidden"}
68
- data-slot="content"
69
- data-nav-content
70
- data-item-index={i}
71
- role="region"
72
- >
73
- {item.slot && Astro.slots.has(`${item.slot}-content`) ? (
74
- <slot name={`${item.slot}-content`} />
75
- ) : (
76
- <ul role="list" class="py-1">
77
- {item.children!.map(child => (
78
- <li>
79
- <a
80
- href={child.href ?? child.to ?? "#"}
81
- class="flex items-start gap-3 rounded-sm px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
82
- >
83
- {child.icon && <span class={child.icon + " mt-0.5 size-4 shrink-0 text-muted-foreground"} />}
84
- <div>
85
- <div class="font-medium leading-none">{child.label}</div>
86
- {child.description && (
87
- <p class="mt-1 text-xs text-muted-foreground line-clamp-2">{child.description}</p>
88
- )}
89
- </div>
90
- </a>
91
- </li>
92
- ))}
93
- </ul>
94
- )}
95
- </div>
96
- )}
97
- </li>
98
- )
99
- })}
100
- </ul>
101
-
102
- <slot />
103
-
104
- <div class={classes.viewportWrapper + " hidden"} data-slot="viewport-wrapper" data-nav-viewport-wrapper>
105
- <div class={classes.viewport} data-slot="viewport" data-nav-viewport />
106
- </div>
107
- </nav>
108
-
109
- <script>
110
- document.querySelectorAll("[data-nav-menu]").forEach(menu => {
111
- const orientation = menu.getAttribute("data-orientation") || "horizontal";
112
- const items = menu.querySelectorAll("[data-nav-item]");
113
- const viewportWrapper = orientation === "horizontal" ? menu.querySelector("[data-nav-viewport-wrapper]") : null as HTMLElement | null;
114
- const viewport = orientation === "horizontal" ? menu.querySelector("[data-nav-viewport]") : null;
115
-
116
- // Teleport viewport wrapper to body to escape parent transforms (e.g. translate-x on header)
117
- if (viewportWrapper && viewportWrapper.parentNode !== document.body) {
118
- document.body.appendChild(viewportWrapper);
119
- }
120
-
121
- let activeItem = null as HTMLElement | null;
122
- let hoverTimeout = null as any;
123
-
124
- const updatePosition = () => {
125
- if (viewportWrapper && activeItem) {
126
- const header = menu.closest("header") || document.querySelector("header");
127
- if (header) {
128
- const headerRect = header.getBoundingClientRect();
129
- viewportWrapper.style.top = `${headerRect.bottom}px`;
130
- }
131
- }
132
- };
133
-
134
- window.addEventListener("scroll", updatePosition, { passive: true });
135
- window.addEventListener("resize", updatePosition, { passive: true });
136
-
137
- const open = (item: HTMLElement, trigger: HTMLElement, content: HTMLElement) => {
138
- clearTimeout(hoverTimeout);
139
-
140
- // If there's another active item, close it first (horizontal viewport mode only)
141
- if (orientation === "horizontal" && activeItem && activeItem !== item) {
142
- close(activeItem);
143
- }
144
-
145
- if (orientation === "horizontal") {
146
- activeItem = item;
147
- updatePosition();
148
- }
149
-
150
- if (viewport) {
151
- viewport.innerHTML = "";
152
- viewport.appendChild(content);
153
- content.classList.remove("hidden");
154
- if (viewportWrapper) {
155
- viewportWrapper.classList.remove("hidden");
156
- viewportWrapper.setAttribute("data-state", "open");
157
- }
158
- } else {
159
- content.classList.remove("hidden");
160
- }
161
-
162
- trigger.setAttribute("aria-expanded", "true");
163
- trigger.setAttribute("data-state", "open");
164
- const icon = trigger.querySelector(".i-lucide-chevron-down");
165
- if (icon) (icon as HTMLElement).style.transform = "rotate(180deg)";
166
- };
167
-
168
- const close = (item: HTMLElement) => {
169
- const trigger = item.querySelector("[data-nav-trigger]") as HTMLElement | null;
170
- const itemIndex = item.getAttribute("data-index");
171
-
172
- // Find content - either inside viewport or inside the item
173
- let content = item.querySelector("[data-nav-content]") as HTMLElement | null;
174
- if (!content && viewport) {
175
- content = viewport.querySelector(`[data-nav-content][data-item-index="${itemIndex}"]`) as HTMLElement | null;
176
- }
177
-
178
- if (content) {
179
- content.classList.add("hidden");
180
- // Move back to item
181
- if (viewport && viewport.contains(content)) {
182
- item.appendChild(content);
183
- }
184
- }
185
-
186
- if (trigger) {
187
- trigger.setAttribute("aria-expanded", "false");
188
- trigger.setAttribute("data-state", "closed");
189
- const icon = trigger.querySelector(".i-lucide-chevron-down");
190
- if (icon) (icon as HTMLElement).style.transform = "";
191
- }
192
-
193
- if (orientation === "horizontal" && activeItem === item) {
194
- activeItem = null;
195
- }
196
-
197
- // If no item is active, hide viewport (horizontal viewport mode only)
198
- if (orientation === "horizontal" && !activeItem && viewportWrapper) {
199
- viewportWrapper.classList.add("hidden");
200
- viewportWrapper.setAttribute("data-state", "closed");
201
- if (viewport) viewport.innerHTML = "";
202
- }
203
- };
204
-
205
- const closeAll = () => {
206
- items.forEach(item => {
207
- close(item as HTMLElement);
208
- });
209
- };
210
-
211
- items.forEach(itemNode => {
212
- const item = itemNode as HTMLElement;
213
- const trigger = item.querySelector("[data-nav-trigger]") as HTMLElement | null;
214
- const content = item.querySelector("[data-nav-content]") as HTMLElement | null;
215
-
216
- if (!trigger || !content) return;
217
-
218
- // Click behavior (works everywhere, including mobile)
219
- trigger.addEventListener("click", (e) => {
220
- e.stopPropagation();
221
- const isOpen = trigger.getAttribute("aria-expanded") === "true";
222
- if (isOpen) {
223
- close(item);
224
- } else {
225
- open(item, trigger, content);
226
- }
227
- });
228
-
229
- // Hover behavior (horizontal desktop only)
230
- if (orientation === "horizontal") {
231
- item.addEventListener("mouseenter", () => {
232
- clearTimeout(hoverTimeout);
233
- open(item, trigger, content);
234
- });
235
-
236
- item.addEventListener("mouseleave", () => {
237
- hoverTimeout = setTimeout(() => {
238
- if (activeItem === item) {
239
- close(item);
240
- }
241
- }, 150);
242
- });
243
- }
244
- });
245
-
246
- // Handle viewport wrapper hover so menu stays open when hovering the mega menu itself
247
- if (viewportWrapper) {
248
- viewportWrapper.addEventListener("mouseenter", () => {
249
- clearTimeout(hoverTimeout);
250
- });
251
-
252
- viewportWrapper.addEventListener("mouseleave", () => {
253
- hoverTimeout = setTimeout(() => {
254
- if (activeItem) {
255
- close(activeItem);
256
- }
257
- }, 150);
258
- });
259
- }
260
-
261
- // Close on clicking outside
262
- document.addEventListener("click", (e) => {
263
- const target = e.target as HTMLElement;
264
- if (!menu.contains(target) && (!viewportWrapper || !viewportWrapper.contains(target))) {
265
- closeAll();
266
- }
267
- });
268
- });
269
- </script>
270
-
271
- <style is:global>
272
- /* Reset margins, borders, rounding, and rings on the main viewport container */
273
- [data-nav-viewport] {
274
- margin-top: 0 !important;
275
- border: none !important;
276
- border-width: 0px !important;
277
- border-style: none !important;
278
- border-color: transparent !important;
279
- border-radius: 0 !important;
280
- outline: none !important;
281
- outline-width: 0px !important;
282
- outline-color: transparent !important;
283
-
284
- /* Wiping out any UnoCSS/Tailwind rings on the viewport container */
285
- --un-ring-width: 0px !important;
286
- --tw-ring-width: 0px !important;
287
- --un-ring-offset-width: 0px !important;
288
- --tw-ring-offset-width: 0px !important;
289
- --un-ring-color: transparent !important;
290
- --tw-ring-color: transparent !important;
291
- --un-ring-shadow: none !important;
292
- --tw-ring-shadow: none !important;
293
- --un-ring-offset-shadow: none !important;
294
- --tw-ring-offset-shadow: none !important;
295
-
296
- /* Premium card shadow */
297
- box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.35), 0 8px 10px -6px rgb(0 0 0 / 0.35) !important; /* Premium shadow-xl */
298
- }
299
-
300
- /* Reset layout and strip standard dropdown styling (borders/shadows/padding) from teleported content nodes */
301
- [data-nav-viewport] [data-nav-content] {
302
- position: static !important;
303
- width: 100% !important;
304
- margin: 0 !important;
305
- transform: none !important;
306
- border: none !important;
307
- border-width: 0px !important;
308
- box-shadow: none !important;
309
- padding: 0 !important;
310
- background: transparent !important;
311
-
312
- /* Wiping out any UnoCSS/Tailwind rings on content */
313
- --un-ring-width: 0px !important;
314
- --tw-ring-width: 0px !important;
315
- --un-ring-offset-width: 0px !important;
316
- --tw-ring-offset-width: 0px !important;
317
- --un-ring-color: transparent !important;
318
- --tw-ring-color: transparent !important;
319
- --un-ring-shadow: none !important;
320
- --tw-ring-shadow: none !important;
321
- --un-ring-offset-shadow: none !important;
322
- --tw-ring-offset-shadow: none !important;
323
- --un-shadow: none !important;
324
- --tw-shadow: none !important;
325
- }
326
- </style>
1
+ ---
2
+ import { scv } from "css-variants"
3
+ import { useUi, resolveClasses } from "../../utils"
4
+ import type { NavigationMenuProps } from "../../types"
5
+ import navigationMenuThemeDefault, { createNavigationMenuTheme } from "../../themes/navigation-menu.theme"
6
+
7
+ import { getUIConfig } from "virtual:rimelight-ui"
8
+
9
+ const uiConfig = getUIConfig()
10
+ const navigationMenuTheme = uiConfig.colors ? createNavigationMenuTheme() : navigationMenuThemeDefault
11
+ const navigationMenu = scv(navigationMenuTheme)
12
+
13
+ const {
14
+ items = [],
15
+ color = "primary",
16
+ theme,
17
+ orientation = "horizontal",
18
+ ui: uiProp,
19
+ class: className,
20
+ ...rest
21
+ } = Astro.props as NavigationMenuProps & { theme?: string }
22
+
23
+ const classes = resolveClasses(navigationMenu, { theme: theme as "dark", orientation }, useUi("navigationMenu", uiProp), className)
24
+
25
+ const slotContents: Record<string, string> = {}
26
+ await Promise.all(
27
+ items.map(async (item) => {
28
+ if (item.slot && Astro.slots.has(`${item.slot}-content`)) {
29
+ slotContents[`${item.slot}-content`] = await Astro.slots.render(`${item.slot}-content`)
30
+ }
31
+ })
32
+ )
33
+ ---
34
+
35
+ <rla-navigation-menu orientation={orientation} {...rest}>
36
+ <nav class={classes.root} data-slot="root" data-nav-menu data-orientation={orientation}>
37
+ <ul class={classes.list} data-slot="list" role="list">
38
+ {items.map((item, i) => {
39
+ const hasChildren = item.children && item.children.length > 0
40
+
41
+ const itemPath = (item.href ?? item.to ?? "#") as string
42
+ const normalizedPath = itemPath.replace(/^\/[a-z]{2}(?:-[a-zA-Z]{2})?(?=\/|$)/, '').replace(/\/+$/, '') || '/'
43
+ const currentPath = Astro.url.pathname.replace(/^\/[a-z]{2}(?:-[a-zA-Z]{2})?(?=\/|$)/, '').replace(/\/+$/, '') || '/'
44
+
45
+ const isActive = item.active ?? (
46
+ normalizedPath !== '' && normalizedPath !== '/'
47
+ ? currentPath.startsWith(normalizedPath)
48
+ : currentPath === '/'
49
+ )
50
+
51
+ return (
52
+ <li class={classes.item} data-slot="item" data-nav-item data-index={i}>
53
+ {hasChildren ? (
54
+ <button
55
+ type="button"
56
+ class={classes.link}
57
+ data-slot="trigger"
58
+ data-nav-trigger
59
+ aria-expanded="false"
60
+ aria-haspopup="true"
61
+ >
62
+ {item.icon && <span class={item.icon + " mr-1.5 size-4"} />}
63
+ {item.label}
64
+ <span class="i-lucide-chevron-down ml-1 size-3.5 transition-transform duration-200" aria-hidden="true" />
65
+ </button>
66
+ ) : (
67
+ <a
68
+ href={typeof item.href === "string" ? item.href : (typeof item.to === "string" ? item.to : "#")}
69
+ class={classes.link}
70
+ data-slot="link"
71
+ aria-current={isActive ? "page" : undefined}
72
+ >
73
+ {item.icon && <span class={item.icon + " mr-1.5 size-4"} />}
74
+ {item.label}
75
+ </a>
76
+ )}
77
+
78
+ {hasChildren && (
79
+ <div
80
+ class={classes.content + " hidden"}
81
+ data-slot="content"
82
+ data-nav-content
83
+ data-item-index={i}
84
+ role="region"
85
+ >
86
+ {item.slot && Astro.slots.has(`${item.slot}-content`) ? (
87
+ <Fragment set:html={slotContents[`${item.slot}-content`]} />
88
+ ) : (
89
+ <ul role="list" class="py-1">
90
+ {item.children!.map(child => (
91
+ <li>
92
+ <a
93
+ href={typeof child.href === "string" ? child.href : (typeof child.to === "string" ? child.to : "#")}
94
+ class="flex items-start gap-3 rounded-sm px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground transition-colors"
95
+ >
96
+ {child.icon && <span class={child.icon + " mt-0.5 size-4 shrink-0 text-muted-foreground"} />}
97
+ <div>
98
+ <div class="font-medium leading-none">{child.label}</div>
99
+ {child.description && (
100
+ <p class="mt-1 text-xs text-muted-foreground line-clamp-2">{child.description}</p>
101
+ )}
102
+ </div>
103
+ </a>
104
+ </li>
105
+ ))}
106
+ </ul>
107
+ )}
108
+ </div>
109
+ )}
110
+ </li>
111
+ )
112
+ })}
113
+ </ul>
114
+
115
+ <slot />
116
+
117
+ <div class={classes.viewportWrapper + " hidden"} data-slot="viewport-wrapper" data-nav-viewport-wrapper>
118
+ <div class={classes.viewport} data-slot="viewport" data-nav-viewport />
119
+ </div>
120
+ </nav>
121
+ </rla-navigation-menu>
122
+
123
+ <script>
124
+ class RLANavigationMenu extends HTMLElement {
125
+ private navCleanup: (() => void) | null = null;
126
+
127
+ connectedCallback() {
128
+ this.style.display = 'contents';
129
+ const menu = this.querySelector("[data-nav-menu]") as HTMLElement | null;
130
+ if (!menu) return;
131
+
132
+ const orientation = menu.getAttribute("data-orientation") || "horizontal";
133
+ const items = menu.querySelectorAll("[data-nav-item]");
134
+ const viewportWrapper = (orientation === "horizontal" ? menu.querySelector("[data-nav-viewport-wrapper]") : null) as HTMLElement | null;
135
+ const viewport = orientation === "horizontal" ? menu.querySelector("[data-nav-viewport]") : null;
136
+
137
+ // Teleport viewport wrapper to body to escape parent transforms (e.g. translate-x on header)
138
+ if (viewportWrapper && viewportWrapper.parentNode !== document.body) {
139
+ document.body.appendChild(viewportWrapper);
140
+ }
141
+
142
+ let activeItem = null as HTMLElement | null;
143
+ let hoverTimeout = null as any;
144
+
145
+ const updatePosition = () => {
146
+ if (viewportWrapper && activeItem) {
147
+ const header = menu.closest("header") || document.querySelector("header");
148
+ if (header) {
149
+ const headerRect = header.getBoundingClientRect();
150
+ viewportWrapper.style.top = `${headerRect.bottom}px`;
151
+ }
152
+ }
153
+ };
154
+
155
+ window.addEventListener("scroll", updatePosition, { passive: true });
156
+ window.addEventListener("resize", updatePosition, { passive: true });
157
+
158
+ const open = (item: HTMLElement, trigger: HTMLElement, content: HTMLElement) => {
159
+ clearTimeout(hoverTimeout);
160
+
161
+ if (orientation === "horizontal" && activeItem && activeItem !== item) {
162
+ close(activeItem);
163
+ }
164
+
165
+ if (orientation === "horizontal") {
166
+ activeItem = item;
167
+ updatePosition();
168
+ }
169
+
170
+ if (viewport) {
171
+ viewport.innerHTML = "";
172
+ viewport.appendChild(content);
173
+ content.classList.remove("hidden");
174
+ if (viewportWrapper) {
175
+ viewportWrapper.classList.remove("hidden");
176
+ viewportWrapper.setAttribute("data-state", "open");
177
+ }
178
+ } else {
179
+ content.classList.remove("hidden");
180
+ }
181
+
182
+ trigger.setAttribute("aria-expanded", "true");
183
+ trigger.setAttribute("data-state", "open");
184
+ const icon = trigger.querySelector(".i-lucide-chevron-down");
185
+ if (icon) (icon as HTMLElement).style.transform = "rotate(180deg)";
186
+ };
187
+
188
+ const close = (item: HTMLElement) => {
189
+ const trigger = item.querySelector("[data-nav-trigger]") as HTMLElement | null;
190
+ const itemIndex = item.getAttribute("data-index");
191
+
192
+ let content = item.querySelector("[data-nav-content]") as HTMLElement | null;
193
+ if (!content && viewport) {
194
+ content = viewport.querySelector(`[data-nav-content][data-item-index="${itemIndex}"]`) as HTMLElement | null;
195
+ }
196
+
197
+ if (content) {
198
+ content.classList.add("hidden");
199
+ if (viewport && viewport.contains(content)) {
200
+ item.appendChild(content);
201
+ }
202
+ }
203
+
204
+ if (trigger) {
205
+ trigger.setAttribute("aria-expanded", "false");
206
+ trigger.setAttribute("data-state", "closed");
207
+ const icon = trigger.querySelector(".i-lucide-chevron-down");
208
+ if (icon) (icon as HTMLElement).style.transform = "";
209
+ }
210
+
211
+ if (orientation === "horizontal" && activeItem === item) {
212
+ activeItem = null;
213
+ }
214
+
215
+ if (orientation === "horizontal" && !activeItem && viewportWrapper) {
216
+ viewportWrapper.classList.add("hidden");
217
+ viewportWrapper.setAttribute("data-state", "closed");
218
+ if (viewport) viewport.innerHTML = "";
219
+ }
220
+ };
221
+
222
+ const closeAll = () => {
223
+ items.forEach(item => {
224
+ close(item as HTMLElement);
225
+ });
226
+ };
227
+
228
+ const clickListeners: (() => void)[] = [];
229
+ const enterListeners: (() => void)[] = [];
230
+ const leaveListeners: (() => void)[] = [];
231
+
232
+ items.forEach(itemNode => {
233
+ const item = itemNode as HTMLElement;
234
+ const trigger = item.querySelector("[data-nav-trigger]") as HTMLElement | null;
235
+ const content = item.querySelector("[data-nav-content]") as HTMLElement | null;
236
+
237
+ if (!trigger || !content) return;
238
+
239
+ const onClick = (e: MouseEvent) => {
240
+ e.stopPropagation();
241
+ const isOpen = trigger.getAttribute("aria-expanded") === "true";
242
+ if (isOpen) {
243
+ close(item);
244
+ } else {
245
+ open(item, trigger, content);
246
+ }
247
+ };
248
+ trigger.addEventListener("click", onClick);
249
+ clickListeners.push(() => trigger.removeEventListener("click", onClick));
250
+
251
+ if (orientation === "horizontal") {
252
+ const onMouseEnter = () => {
253
+ clearTimeout(hoverTimeout);
254
+ open(item, trigger, content);
255
+ };
256
+ const onMouseLeave = () => {
257
+ hoverTimeout = setTimeout(() => {
258
+ if (activeItem === item) {
259
+ close(item);
260
+ }
261
+ }, 150);
262
+ };
263
+ item.addEventListener("mouseenter", onMouseEnter);
264
+ item.addEventListener("mouseleave", onMouseLeave);
265
+ enterListeners.push(() => item.removeEventListener("mouseenter", onMouseEnter));
266
+ leaveListeners.push(() => item.removeEventListener("mouseleave", onMouseLeave));
267
+ }
268
+ });
269
+
270
+ let viewportEnter: (() => void) | null = null;
271
+ let viewportLeave: (() => void) | null = null;
272
+
273
+ if (viewportWrapper) {
274
+ const onVpEnter = () => {
275
+ clearTimeout(hoverTimeout);
276
+ };
277
+ const onVpLeave = () => {
278
+ hoverTimeout = setTimeout(() => {
279
+ if (activeItem) {
280
+ close(activeItem);
281
+ }
282
+ }, 150);
283
+ };
284
+ viewportWrapper.addEventListener("mouseenter", onVpEnter);
285
+ viewportWrapper.addEventListener("mouseleave", onVpLeave);
286
+ viewportEnter = () => viewportWrapper.removeEventListener("mouseenter", onVpEnter);
287
+ viewportLeave = () => viewportWrapper.removeEventListener("mouseleave", onVpLeave);
288
+ }
289
+
290
+ const onDocumentClick = (e: MouseEvent) => {
291
+ const target = e.target as HTMLElement;
292
+ if (!menu.contains(target) && (!viewportWrapper || !viewportWrapper.contains(target))) {
293
+ closeAll();
294
+ }
295
+ };
296
+ document.addEventListener("click", onDocumentClick);
297
+
298
+ this.navCleanup = () => {
299
+ window.removeEventListener("scroll", updatePosition);
300
+ window.removeEventListener("resize", updatePosition);
301
+ document.removeEventListener("click", onDocumentClick);
302
+ clickListeners.forEach(fn => fn());
303
+ enterListeners.forEach(fn => fn());
304
+ leaveListeners.forEach(fn => fn());
305
+ if (viewportEnter) viewportEnter();
306
+ if (viewportLeave) viewportLeave();
307
+ if (viewportWrapper && viewportWrapper.parentNode === document.body) {
308
+ viewportWrapper.remove();
309
+ }
310
+ };
311
+ }
312
+
313
+ disconnectedCallback() {
314
+ if (this.navCleanup) {
315
+ this.navCleanup();
316
+ this.navCleanup = null;
317
+ }
318
+ }
319
+ }
320
+
321
+ if (!customElements.get('rla-navigation-menu')) {
322
+ customElements.define('rla-navigation-menu', RLANavigationMenu);
323
+ }
324
+ </script>
325
+
326
+ <style is:global>
327
+ /* Reset margins, borders, rounding, and rings on the main viewport container */
328
+ [data-nav-viewport] {
329
+ margin-top: 0 !important;
330
+ border: none !important;
331
+ border-width: 0px !important;
332
+ border-style: none !important;
333
+ border-color: transparent !important;
334
+ border-radius: 0 !important;
335
+ outline: none !important;
336
+ outline-width: 0px !important;
337
+ outline-color: transparent !important;
338
+
339
+ /* Wiping out any UnoCSS/Tailwind rings on the viewport container */
340
+ --un-ring-width: 0px !important;
341
+ --tw-ring-width: 0px !important;
342
+ --un-ring-offset-width: 0px !important;
343
+ --tw-ring-offset-width: 0px !important;
344
+ --un-ring-color: transparent !important;
345
+ --tw-ring-color: transparent !important;
346
+ --un-ring-shadow: none !important;
347
+ --tw-ring-shadow: none !important;
348
+ --un-ring-offset-shadow: none !important;
349
+ --tw-ring-offset-shadow: none !important;
350
+
351
+ /* Premium card shadow */
352
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.35), 0 8px 10px -6px rgb(0 0 0 / 0.35) !important; /* Premium shadow-xl */
353
+ }
354
+
355
+ /* Reset layout and strip standard dropdown styling (borders/shadows/padding) from teleported content nodes */
356
+ [data-nav-viewport] [data-nav-content] {
357
+ position: static !important;
358
+ width: 100% !important;
359
+ margin: 0 !important;
360
+ transform: none !important;
361
+ border: none !important;
362
+ border-width: 0px !important;
363
+ box-shadow: none !important;
364
+ padding: 0 !important;
365
+ background: transparent !important;
366
+
367
+ /* Wiping out any UnoCSS/Tailwind rings on content */
368
+ --un-ring-width: 0px !important;
369
+ --tw-ring-width: 0px !important;
370
+ --un-ring-offset-width: 0px !important;
371
+ --tw-ring-offset-width: 0px !important;
372
+ --un-ring-color: transparent !important;
373
+ --tw-ring-color: transparent !important;
374
+ --un-ring-shadow: none !important;
375
+ --tw-ring-shadow: none !important;
376
+ --un-ring-offset-shadow: none !important;
377
+ --tw-ring-offset-shadow: none !important;
378
+ --un-shadow: none !important;
379
+ --tw-shadow: none !important;
380
+ }
381
+ </style>