@rimelight/ui 0.0.47 → 0.0.48

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 (197) hide show
  1. package/dist/index.d.mts +4 -5
  2. package/dist/preset.mjs +73 -1
  3. package/dist/resolver.d.mts +3 -4
  4. package/dist/resolver.mjs +2 -4
  5. package/dist/shortcuts.d.mts +10 -11
  6. package/dist/stores.d.mts +3 -4
  7. package/package.json +8 -6
  8. package/src/components/3d-hover/RLS3dHover.tsx +21 -0
  9. package/src/components/accordion/RLSAccordion.tsx +112 -0
  10. package/src/components/app/RLSApp.tsx +21 -0
  11. package/src/components/aspect-ratio/RLSAspectRatio.tsx +30 -0
  12. package/src/components/audio/RLSAudio.tsx +27 -0
  13. package/src/components/avatar/RLSAvatar.tsx +142 -0
  14. package/src/components/avatar/avatar.ts +22 -21
  15. package/src/components/avatar-group/RLSAvatarGroup.tsx +163 -0
  16. package/src/components/badge/RLSBadge.tsx +1 -1
  17. package/src/components/badge/RLVBadge.vue +5 -3
  18. package/src/components/banner/RLSBanner.tsx +151 -0
  19. package/src/components/breadcrumb/RLSBreadcrumb.tsx +194 -0
  20. package/src/components/browser-mockup/RLSBrowserMockup.tsx +21 -0
  21. package/src/components/button/RLSButton.tsx +49 -40
  22. package/src/components/button/RLVButton.vue +44 -32
  23. package/src/components/calendar/RLSCalendar.tsx +26 -0
  24. package/src/components/callout/RLSCallout.tsx +151 -0
  25. package/src/components/card/RLSCard.tsx +4 -0
  26. package/src/components/card/RLVCard.vue +4 -0
  27. package/src/components/card/card.ts +10 -0
  28. package/src/components/carousel/RLACarousel.astro +14 -1
  29. package/src/components/carousel/RLSCarousel.tsx +260 -0
  30. package/src/components/chart/RLSChart.tsx +299 -0
  31. package/src/components/chat/RLSChat.tsx +27 -0
  32. package/src/components/chat-message/RLSChatMessage.tsx +27 -0
  33. package/src/components/chat-messages/RLSChatMessages.tsx +27 -0
  34. package/src/components/chat-palette/RLSChatPalette.tsx +27 -0
  35. package/src/components/chat-prompt/RLSChatPrompt.tsx +27 -0
  36. package/src/components/chat-prompt-submit/RLSChatPromptSubmit.tsx +27 -0
  37. package/src/components/chat-reasoning/RLSChatReasoning.tsx +27 -0
  38. package/src/components/chat-shimmer/RLSChatShimmer.tsx +27 -0
  39. package/src/components/chat-tool/RLSChatTool.tsx +27 -0
  40. package/src/components/checkbox/RLACheckbox.astro +1 -0
  41. package/src/components/checkbox/RLSCheckbox.tsx +1 -1
  42. package/src/components/checkbox/checkbox.theme.ts +2 -2
  43. package/src/components/chip/RLSChip.tsx +65 -0
  44. package/src/components/collapsible/RLSCollapsible.tsx +89 -0
  45. package/src/components/color-area/RLSColorArea.tsx +554 -0
  46. package/src/components/color-field/RLSColorField.tsx +236 -0
  47. package/src/components/color-picker/RLSColorPicker.tsx +26 -0
  48. package/src/components/color-slider/RLSColorSlider.tsx +373 -0
  49. package/src/components/command-palette/RLSCommandPalette.tsx +26 -0
  50. package/src/components/compare/RLSCompare.tsx +26 -0
  51. package/src/components/confirm/RLSConfirm.tsx +173 -0
  52. package/src/components/container/RLSContainer.tsx +24 -0
  53. package/src/components/context-menu/RLSContextMenu.tsx +26 -0
  54. package/src/components/copy-markdown/RLSCopyMarkdown.tsx +57 -0
  55. package/src/components/dashboard-group/RLSDashboardGroup.tsx +29 -0
  56. package/src/components/dashboard-navbar/RLSDashboardNavbar.tsx +116 -0
  57. package/src/components/dashboard-panel/RLSDashboardPanel.tsx +84 -0
  58. package/src/components/dashboard-resize-handle/RLSDashboardResizeHandle.tsx +35 -0
  59. package/src/components/dashboard-search/RLADashboardSearch.astro +1 -1
  60. package/src/components/dashboard-search/RLSDashboardSearch.tsx +125 -0
  61. package/src/components/dashboard-search-button/RLSDashboardSearchButton.tsx +82 -0
  62. package/src/components/dashboard-sidebar/RLSDashboardSidebar.tsx +184 -0
  63. package/src/components/dashboard-sidebar-collapse/RLSDashboardSidebarCollapse.tsx +46 -0
  64. package/src/components/dashboard-sidebar-toggle/RLSDashboardSidebarToggle.tsx +35 -0
  65. package/src/components/dashboard-toolbar/RLSDashboardToolbar.tsx +49 -0
  66. package/src/components/date/RLSDate.tsx +34 -0
  67. package/src/components/dock/RLSDock.tsx +26 -0
  68. package/src/components/drawer/RLSDrawer.tsx +177 -0
  69. package/src/components/dropdown-menu/RLADropdownMenu.astro +250 -241
  70. package/src/components/dropdown-menu/RLSDropdownMenu.tsx +183 -86
  71. package/src/components/editor/RLSEditor.tsx +26 -0
  72. package/src/components/empty/RLSEmpty.tsx +132 -0
  73. package/src/components/error/RLSError.tsx +26 -0
  74. package/src/components/field-group/RLSFieldGroup.tsx +46 -0
  75. package/src/components/fieldset/RLSFieldset.tsx +26 -0
  76. package/src/components/file-upload/RLSFileUpload.tsx +163 -0
  77. package/src/components/floating-action/RLSFloatingAction.tsx +26 -0
  78. package/src/components/footer/RLSFooter.tsx +56 -0
  79. package/src/components/form/RLSForm.tsx +34 -0
  80. package/src/components/gallery/RLSGallery.tsx +21 -0
  81. package/src/components/gamepad/RLSGamepad.tsx +269 -0
  82. package/src/components/grid/RLSGrid.tsx +29 -0
  83. package/src/components/head/RLAHead.astro +9 -0
  84. package/src/components/head/RLSHead.tsx +42 -0
  85. package/src/components/head/UIHead.astro +3 -0
  86. package/src/components/header/RLSHeader.tsx +87 -0
  87. package/src/components/hover-card/RLSHoverCard.tsx +72 -0
  88. package/src/components/icon/RLSIcon.tsx +3 -1
  89. package/src/components/image/RLAImage.astro +447 -138
  90. package/src/components/image/RLSImage.tsx +545 -0
  91. package/src/components/image/image.theme.ts +19 -3
  92. package/src/components/image/image.ts +31 -1
  93. package/src/components/input/RLAInput.astro +62 -51
  94. package/src/components/input/RLSInput.tsx +1 -1
  95. package/src/components/input-date/RLSInputDate.tsx +92 -0
  96. package/src/components/input-menu/RLSInputMenu.tsx +199 -0
  97. package/src/components/input-number/RLSInputNumber.tsx +184 -0
  98. package/src/components/input-pin/RLSInputPin.tsx +185 -0
  99. package/src/components/input-rating/RLSInputRating.tsx +198 -0
  100. package/src/components/input-rating/input-rating.ts +4 -0
  101. package/src/components/input-tags/RLSInputTags.tsx +94 -0
  102. package/src/components/input-time/RLSInputTime.tsx +92 -0
  103. package/src/components/kbd/RLSKbd.tsx +37 -0
  104. package/src/components/keyboard/RLSKeyboard.tsx +476 -0
  105. package/src/components/knob/RLSKnob.tsx +27 -0
  106. package/src/components/label/RLSLabel.tsx +27 -0
  107. package/src/components/layout-grid/RLSLayoutGrid.tsx +402 -0
  108. package/src/components/layout-grid/layout-grid.ts +25 -1
  109. package/src/components/link/RLSLink.tsx +2 -1
  110. package/src/components/link/link.ts +21 -12
  111. package/src/components/link-group/RLSLinkGroup.tsx +76 -0
  112. package/src/components/listbox/RLSListbox.tsx +27 -0
  113. package/src/components/locale-selector/RLALocaleSelector.astro +2 -1
  114. package/src/components/locale-selector/RLSLocaleSelector.tsx +178 -0
  115. package/src/components/logo/RLALogo.astro +69 -4
  116. package/src/components/logo/RLSLogo.tsx +129 -0
  117. package/src/components/main/RLSMain.tsx +40 -0
  118. package/src/components/marquee/RLSMarquee.tsx +80 -0
  119. package/src/components/meter/RLSMeter.tsx +27 -0
  120. package/src/components/modal/RLAModal.astro +1 -1
  121. package/src/components/modal/RLSModal.tsx +109 -72
  122. package/src/components/navigation-menu/RLSNavigationMenu.tsx +228 -0
  123. package/src/components/navigation-menu/navigation-menu.ts +2 -2
  124. package/src/components/package-managers/RLSPackageManagers.tsx +204 -0
  125. package/src/components/page/RLSPage.tsx +21 -0
  126. package/src/components/page-aside/RLSPageAside.tsx +21 -0
  127. package/src/components/page-body/RLSPageBody.tsx +21 -0
  128. package/src/components/page-header/RLSPageHeader.tsx +21 -0
  129. package/src/components/page-section/RLSPageSection.tsx +201 -0
  130. package/src/components/pagination/RLSPagination.tsx +114 -0
  131. package/src/components/password/RLSPassword.tsx +27 -0
  132. package/src/components/phone-mockup/RLSPhoneMockup.tsx +27 -0
  133. package/src/components/placeholder/RLSPlaceholder.tsx +47 -0
  134. package/src/components/popover/RLSPopover.tsx +124 -0
  135. package/src/components/post/RLSPost.tsx +134 -0
  136. package/src/components/pricing-plan/RLSPricingPlan.tsx +26 -0
  137. package/src/components/pricing-table/RLSPricingTable.tsx +26 -0
  138. package/src/components/progress/RLSProgress.tsx +53 -0
  139. package/src/components/prose/RLSProse.tsx +26 -0
  140. package/src/components/qr-code/RLSQrCode.tsx +26 -0
  141. package/src/components/quote/RLAQuote.astro +10 -6
  142. package/src/components/quote/RLSQuote.tsx +37 -0
  143. package/src/components/quote/quote.theme.ts +5 -2
  144. package/src/components/radio-group/RLSRadioGroup.tsx +37 -0
  145. package/src/components/rating/RLSRating.tsx +27 -0
  146. package/src/components/scroll-area/RLSScrollArea.tsx +210 -0
  147. package/src/components/scroll-to-top/RLAScrollToTop.astro +1 -0
  148. package/src/components/scroll-to-top/RLSScrollToTop.tsx +145 -0
  149. package/src/components/search/RLSSearch.tsx +237 -0
  150. package/src/components/select/RLASelect.astro +67 -4
  151. package/src/components/select/RLSSelect.tsx +107 -39
  152. package/src/components/select/RLVSelect.vue +50 -1
  153. package/src/components/select/select.theme.ts +30 -10
  154. package/src/components/select/select.ts +20 -1
  155. package/src/components/separator/RLSSeparator.tsx +92 -0
  156. package/src/components/share/RLSShare.tsx +66 -0
  157. package/src/components/shortcuts/RLSShortcuts.tsx +393 -0
  158. package/src/components/sidebar/RLSSidebar.tsx +271 -0
  159. package/src/components/skeleton/RLSSkeleton.tsx +21 -0
  160. package/src/components/slideover/RLASlideover.astro +1 -1
  161. package/src/components/slideover/RLSSlideover.tsx +159 -0
  162. package/src/components/slider/RLSSlider.tsx +112 -0
  163. package/src/components/speed-dial/RLSSpeedDial.tsx +27 -0
  164. package/src/components/spinner/RLSSpinner.tsx +22 -0
  165. package/src/components/splitter/RLSSplitter.tsx +178 -0
  166. package/src/components/spoiler/RLSSpoiler.tsx +26 -0
  167. package/src/components/stepper/RLSStepper.tsx +205 -0
  168. package/src/components/steps/RLSSteps.tsx +108 -0
  169. package/src/components/sticky-surface/RLSStickySurface.tsx +63 -0
  170. package/src/components/surround/RLSSurround.tsx +57 -0
  171. package/src/components/switch/RLSSwitch.tsx +56 -0
  172. package/src/components/table/RLATable.astro +1044 -579
  173. package/src/components/table/RLSTable.tsx +893 -0
  174. package/src/components/table/table.theme.ts +47 -16
  175. package/src/components/table/table.ts +104 -66
  176. package/src/components/table-of-contents/RLSTableOfContents.tsx +196 -0
  177. package/src/components/tabs/RLSTabs.tsx +298 -0
  178. package/src/components/textarea/RLATextarea.astro +1 -0
  179. package/src/components/textarea/RLSTextarea.tsx +3 -1
  180. package/src/components/theme-selector/RLAThemeSelector.astro +85 -56
  181. package/src/components/theme-selector/RLSThemeSelector.tsx +131 -0
  182. package/src/components/theme-selector/theme-selector.theme.ts +5 -7
  183. package/src/components/theme-selector/theme-selector.ts +11 -1
  184. package/src/components/timeline/RLSTimeline.tsx +171 -0
  185. package/src/components/toast/RLSToast.tsx +179 -0
  186. package/src/components/toaster/RLSToaster.tsx +82 -0
  187. package/src/components/tooltip/RLSTooltip.tsx +48 -0
  188. package/src/components/tour/RLSTour.tsx +26 -0
  189. package/src/components/tree/RLSTree.tsx +26 -0
  190. package/src/components/user/RLSUser.tsx +26 -0
  191. package/src/components/video/RLAVideo.astro +4 -1
  192. package/src/components/video/RLSVideo.tsx +158 -0
  193. package/src/components/watermark/RLSWatermark.tsx +27 -0
  194. package/src/components/window-mockup/RLSWindowMockup.tsx +27 -0
  195. package/src/preset.ts +73 -1
  196. package/src/resolver.ts +6 -1
  197. package/src/virtual.d.ts +6 -0
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  createMemo,
3
+ createSignal,
4
+ createEffect,
3
5
  splitProps,
4
6
  Show,
5
7
  onMount,
6
8
  onCleanup,
7
- createEffect,
8
9
  type Component,
9
10
  type JSX
10
11
  } from "solid-js"
@@ -15,8 +16,11 @@ import RLSButton from "../button/RLSButton"
15
16
  export interface RLSModalProps extends ModalProps {
16
17
  onClose?: () => void
17
18
  children?: JSX.Element
19
+ trigger?: JSX.Element
20
+ triggerSlot?: JSX.Element
18
21
  titleSlot?: JSX.Element
19
22
  descriptionSlot?: JSX.Element
23
+ footer?: JSX.Element
20
24
  footerSlot?: JSX.Element
21
25
  }
22
26
 
@@ -26,6 +30,7 @@ const RLSModal: Component<RLSModalProps> = (allProps) => {
26
30
  "onClose",
27
31
  "title",
28
32
  "description",
33
+ "triggerLabel",
29
34
  "size",
30
35
  "closeOnClickOutside",
31
36
  "closeOnEscape",
@@ -34,32 +39,50 @@ const RLSModal: Component<RLSModalProps> = (allProps) => {
34
39
  "ui",
35
40
  "class",
36
41
  "children",
42
+ "trigger",
43
+ "triggerSlot",
37
44
  "titleSlot",
38
45
  "descriptionSlot",
46
+ "footer",
39
47
  "footerSlot"
40
48
  ])
41
49
 
50
+ const [internalOpen, setInternalOpen] = createSignal(props.open ?? false)
51
+ const isControlled = () => props.open !== undefined
52
+ const isOpen = () => (isControlled() ? !!props.open : internalOpen())
53
+
54
+ createEffect(() => {
55
+ if (props.open !== undefined) {
56
+ setInternalOpen(props.open)
57
+ }
58
+ })
59
+
42
60
  const closeOnClickOutside = () => props.closeOnClickOutside !== false
43
61
  const closeOnEscape = () => props.closeOnEscape !== false
44
62
  const unmountOnHide = () => props.unmountOnHide !== false
45
63
 
46
64
  const handleClose = () => {
65
+ if (!isControlled()) setInternalOpen(false)
47
66
  props.onClose?.()
48
67
  }
49
68
 
69
+ const handleOpen = () => {
70
+ if (!isControlled()) setInternalOpen(true)
71
+ }
72
+
73
+ let dialogRef: HTMLDialogElement | undefined
74
+
50
75
  onMount(() => {
51
76
  const handleCancel = (e: Event) => {
52
- // Native Escape fires 'cancel' on the dialog element
53
77
  if (e.target !== dialogRef) return
54
78
  if (!closeOnEscape()) {
55
79
  e.preventDefault()
56
- } else {
57
- e.preventDefault()
80
+ return
58
81
  }
82
+ e.preventDefault()
59
83
  handleClose()
60
84
  }
61
85
  const handleClick = (e: MouseEvent) => {
62
- // Clicking the ::backdrop means e.target IS the dialog element
63
86
  if (closeOnClickOutside() && e.target === dialogRef) {
64
87
  handleClose()
65
88
  }
@@ -80,81 +103,95 @@ const RLSModal: Component<RLSModalProps> = (allProps) => {
80
103
  })
81
104
  )
82
105
 
83
- let dialogRef: HTMLDialogElement | undefined
84
-
85
106
  createEffect(() => {
86
107
  if (!dialogRef) return
87
- if (props.open) {
88
- if (!dialogRef.open) dialogRef.showModal()
108
+ if (isOpen()) {
109
+ if (!dialogRef.open && typeof dialogRef.showModal === "function") {
110
+ dialogRef.showModal()
111
+ if (props.lockScroll !== false) {
112
+ document.body.style.overflow = "hidden"
113
+ }
114
+ }
89
115
  } else {
90
- if (dialogRef.open) dialogRef.close()
116
+ if (dialogRef.open && typeof dialogRef.close === "function") {
117
+ dialogRef.close()
118
+ if (props.lockScroll !== false) {
119
+ document.body.style.overflow = ""
120
+ }
121
+ }
91
122
  }
92
123
  })
93
124
 
125
+ const triggerContent = () => props.trigger ?? props.triggerSlot
126
+ const footerContent = () => props.footer ?? props.footerSlot
127
+
94
128
  return (
95
- <Show when={props.open || !unmountOnHide()}>
96
- <dialog
97
- ref={(el) => (dialogRef = el)}
98
- class={`w-[calc(100%-1.5rem)] sm:w-full rounded-xl border border-default bg-default p-5 sm:p-6 shadow-xl outline-none flex flex-col max-h-[calc(100dvh-1.5rem)] sm:max-h-[90vh] overflow-y-auto text-highlighted backdrop:bg-black/60 backdrop:backdrop-blur-sm ${
99
- props.open ? "" : "hidden"
100
- } ${resolvedClasses().content || ""} ${props.class || ""}`}
101
- data-state={props.open ? "open" : "closed"}
102
- data-slot="content"
103
- {...rest}
104
- >
105
- <RLSButton
129
+ <div class={resolvedClasses().root} data-slot="root">
130
+ {triggerContent() ? (
131
+ <span onClick={handleOpen}>{triggerContent()}</span>
132
+ ) : props.triggerLabel ? (
133
+ <button
106
134
  type="button"
107
- variant="ghost"
108
- color="neutral"
109
- size="sm"
110
- square
111
- leadingIcon="close"
112
- aria-label="Close modal"
113
- data-slot="close-button"
114
- class="absolute right-4 top-4 text-muted hover:text-highlighted"
115
- onClick={handleClose}
116
- />
117
-
118
- {props.title || props.description || props.titleSlot || props.descriptionSlot ? (
119
- <div class={resolvedClasses().header || "mb-4"} data-slot="header">
120
- {props.titleSlot ? (
121
- props.titleSlot
122
- ) : props.title ? (
123
- <h3
124
- class={resolvedClasses().title || "text-lg font-semibold text-highlighted"}
125
- data-slot="title"
126
- >
127
- {props.title}
128
- </h3>
129
- ) : null}
130
-
131
- {props.descriptionSlot ? (
132
- props.descriptionSlot
133
- ) : props.description ? (
134
- <p
135
- class={resolvedClasses().description || "text-sm text-muted"}
136
- data-slot="description"
137
- >
138
- {props.description}
139
- </p>
140
- ) : null}
141
- </div>
142
- ) : null}
143
-
144
- {props.children}
145
-
146
- {props.footerSlot ? (
147
- <div
148
- class={
149
- resolvedClasses().footer || "flex justify-end gap-2 mt-6 pt-4 border-t border-default"
150
- }
151
- data-slot="footer"
152
- >
153
- {props.footerSlot}
154
- </div>
155
- ) : null}
156
- </dialog>
157
- </Show>
135
+ class={resolvedClasses().trigger}
136
+ data-slot="trigger"
137
+ onClick={handleOpen}
138
+ >
139
+ {props.triggerLabel}
140
+ </button>
141
+ ) : null}
142
+
143
+ <Show when={isOpen() || !unmountOnHide()}>
144
+ <dialog
145
+ ref={(el) => (dialogRef = el)}
146
+ class={resolvedClasses().content}
147
+ aria-modal="true"
148
+ data-state={isOpen() ? "open" : "closed"}
149
+ data-slot="content"
150
+ {...rest}
151
+ >
152
+ <RLSButton
153
+ type="button"
154
+ variant="ghost"
155
+ color="neutral"
156
+ size="sm"
157
+ square
158
+ leadingIcon="close"
159
+ aria-label="Close modal"
160
+ data-slot="close-button"
161
+ class={resolvedClasses().closeButton}
162
+ onClick={handleClose}
163
+ />
164
+
165
+ {props.title || props.description || props.titleSlot || props.descriptionSlot ? (
166
+ <div class={resolvedClasses().header} data-slot="header">
167
+ {props.titleSlot ? (
168
+ props.titleSlot
169
+ ) : props.title ? (
170
+ <h3 class={resolvedClasses().title} data-slot="title">
171
+ {props.title}
172
+ </h3>
173
+ ) : null}
174
+
175
+ {props.descriptionSlot ? (
176
+ props.descriptionSlot
177
+ ) : props.description ? (
178
+ <p class={resolvedClasses().description} data-slot="description">
179
+ {props.description}
180
+ </p>
181
+ ) : null}
182
+ </div>
183
+ ) : null}
184
+
185
+ {props.children}
186
+
187
+ {footerContent() ? (
188
+ <div class={resolvedClasses().footer} data-slot="footer">
189
+ {footerContent()}
190
+ </div>
191
+ ) : null}
192
+ </dialog>
193
+ </Show>
194
+ </div>
158
195
  )
159
196
  }
160
197
 
@@ -0,0 +1,228 @@
1
+ import {
2
+ createMemo,
3
+ createSignal,
4
+ splitProps,
5
+ For,
6
+ onCleanup,
7
+ type Component,
8
+ type JSX
9
+ } from "solid-js"
10
+ import type {
11
+ NavigationMenuItem,
12
+ NavigationMenuChildItem,
13
+ NavigationMenuProps
14
+ } from "./navigation-menu"
15
+ import { navigationMenuTheme } from "./navigation-menu.theme"
16
+ import RLSIcon from "../icon/RLSIcon"
17
+
18
+ export interface RLSNavigationMenuProps extends NavigationMenuProps {
19
+ children?: JSX.Element
20
+ }
21
+
22
+ const RLSNavigationMenu: Component<RLSNavigationMenuProps> = (allProps) => {
23
+ const [props, rest] = splitProps(allProps, [
24
+ "items",
25
+ "color",
26
+ "orientation",
27
+ "theme",
28
+ "ui",
29
+ "class",
30
+ "children"
31
+ ])
32
+
33
+ const [openIndex, setOpenIndex] = createSignal<number | null>(null)
34
+
35
+ const items = createMemo(() => props.items ?? [])
36
+
37
+ const orientation = () => props.orientation ?? "horizontal"
38
+
39
+ const resolvedClasses = createMemo(() =>
40
+ navigationMenuTheme({
41
+ color: props.color ?? "primary",
42
+ orientation: orientation(),
43
+ ui: props.ui,
44
+ class: props.class
45
+ })
46
+ )
47
+
48
+ const currentPath = () => {
49
+ if (typeof window === "undefined") return ""
50
+ return (
51
+ window.location.pathname
52
+ .replace(/^\/[a-z]{2}(?:-[a-zA-Z]{2})?(?=\/|$)/, "")
53
+ .replace(/\/+$/, "") || "/"
54
+ )
55
+ }
56
+
57
+ const isItemActive = (item: NavigationMenuItem) => {
58
+ if (item["active"] !== undefined) return !!item["active"]
59
+ const rawPath = (item["href"] ?? (item["to"] as string | undefined) ?? "#") as string
60
+ const normalizedPath =
61
+ rawPath.replace(/^\/[a-z]{2}(?:-[a-zA-Z]{2})?(?=\/|$)/, "").replace(/\/+$/, "") || "/"
62
+ const path = currentPath()
63
+ if (normalizedPath !== "" && normalizedPath !== "/") return path.startsWith(normalizedPath)
64
+ return path === "/"
65
+ }
66
+
67
+ let closeTimer: ReturnType<typeof setTimeout> | undefined
68
+
69
+ const openItem = (index: number) => {
70
+ if (closeTimer) clearTimeout(closeTimer)
71
+ setOpenIndex(index)
72
+ }
73
+
74
+ const closeAll = () => {
75
+ if (closeTimer) clearTimeout(closeTimer)
76
+ setOpenIndex(null)
77
+ }
78
+
79
+ const scheduleClose = () => {
80
+ if (closeTimer) clearTimeout(closeTimer)
81
+ closeTimer = setTimeout(() => {
82
+ setOpenIndex(null)
83
+ }, 150)
84
+ }
85
+
86
+ onCleanup(() => {
87
+ if (closeTimer) clearTimeout(closeTimer)
88
+ })
89
+
90
+ const itemHref = (item: NavigationMenuItem) =>
91
+ typeof item["href"] === "string"
92
+ ? item["href"]
93
+ : typeof item["to"] === "string"
94
+ ? item["to"]
95
+ : "#"
96
+
97
+ const childHref = (child: NavigationMenuChildItem) =>
98
+ typeof child["href"] === "string"
99
+ ? child["href"]
100
+ : typeof child["to"] === "string"
101
+ ? child["to"]
102
+ : "#"
103
+
104
+ return (
105
+ <nav
106
+ class={resolvedClasses().root}
107
+ data-slot="root"
108
+ data-theme={props.theme}
109
+ data-nav-menu
110
+ data-orientation={orientation()}
111
+ {...rest}
112
+ >
113
+ <ul class={resolvedClasses().list} data-slot="list" role="list">
114
+ <For each={items()}>
115
+ {(item, i) => {
116
+ const hasChildren = () => !!(item.children && item.children.length > 0)
117
+ const isActive = () => isItemActive(item)
118
+ const isOpen = () => openIndex() === i()
119
+
120
+ return (
121
+ <li
122
+ class={resolvedClasses().item}
123
+ data-slot="item"
124
+ data-nav-item
125
+ data-index={i()}
126
+ onMouseEnter={() => {
127
+ if (orientation() === "horizontal" && hasChildren()) openItem(i())
128
+ }}
129
+ onMouseLeave={() => {
130
+ if (orientation() === "horizontal" && hasChildren()) scheduleClose()
131
+ }}
132
+ >
133
+ {hasChildren() ? (
134
+ <button
135
+ type="button"
136
+ class={resolvedClasses().trigger}
137
+ data-slot="trigger"
138
+ data-nav-trigger
139
+ aria-expanded={isOpen() ? "true" : "false"}
140
+ aria-haspopup="true"
141
+ data-state={isOpen() ? "open" : "closed"}
142
+ onClick={(e) => {
143
+ e.stopPropagation()
144
+ if (isOpen()) {
145
+ closeAll()
146
+ } else {
147
+ openItem(i())
148
+ }
149
+ }}
150
+ >
151
+ {item.icon && <span class={`${item.icon} mr-1.5 size-4`} aria-hidden="true" />}
152
+ {item.label}
153
+ <RLSIcon
154
+ name="chevronDown"
155
+ class={`ml-1 size-3.5 transition-transform duration-200 ${isOpen() ? "rotate-180" : ""}`}
156
+ aria-hidden="true"
157
+ data-chevron
158
+ />
159
+ </button>
160
+ ) : (
161
+ <a
162
+ href={itemHref(item)}
163
+ class={resolvedClasses().link}
164
+ data-slot="link"
165
+ aria-current={isActive() ? "page" : undefined}
166
+ >
167
+ {item.icon && <span class={`${item.icon} mr-1.5 size-4`} aria-hidden="true" />}
168
+ {item.label}
169
+ </a>
170
+ )}
171
+
172
+ {hasChildren() ? (
173
+ <div
174
+ class={`${resolvedClasses().content} ${isOpen() ? "" : "hidden"}`}
175
+ data-slot="content"
176
+ data-nav-content
177
+ data-item-index={i()}
178
+ role="region"
179
+ >
180
+ <ul role="list" class="py-1">
181
+ <For each={item.children}>
182
+ {(child) => (
183
+ <li>
184
+ <a
185
+ href={childHref(child)}
186
+ class="flex items-start gap-3 rounded-sm px-3 py-2 text-sm hover:bg-accented hover:text-highlighted transition-colors"
187
+ >
188
+ {child["icon"] && (
189
+ <span
190
+ class={`${child["icon"]} mt-0.5 size-4 shrink-0 text-muted`}
191
+ aria-hidden="true"
192
+ />
193
+ )}
194
+ <div>
195
+ <div class="font-medium leading-none">{child["label"]}</div>
196
+ {child["description"] && (
197
+ <p class="mt-1 text-xs text-muted line-clamp-2">
198
+ {child["description"]}
199
+ </p>
200
+ )}
201
+ </div>
202
+ </a>
203
+ </li>
204
+ )}
205
+ </For>
206
+ </ul>
207
+ </div>
208
+ ) : null}
209
+ </li>
210
+ )
211
+ }}
212
+ </For>
213
+ </ul>
214
+
215
+ {props.children}
216
+
217
+ <div
218
+ class={`${resolvedClasses().viewportWrapper} hidden`}
219
+ data-slot="viewport-wrapper"
220
+ data-nav-viewport-wrapper
221
+ >
222
+ <div class={resolvedClasses().viewport} data-slot="viewport" data-nav-viewport />
223
+ </div>
224
+ </nav>
225
+ )
226
+ }
227
+
228
+ export default RLSNavigationMenu
@@ -26,7 +26,7 @@ export interface NavigationMenuItem extends Omit<LinkProps, "type" | "raw" | "cu
26
26
  * Additional CSS classes to apply to the root element.
27
27
  */
28
28
  class?: any
29
- [key: string]: unknown
29
+ [key: string]: any
30
30
  }
31
31
 
32
32
  export interface NavigationMenuChildItem extends Omit<NavigationMenuItem, "type" | "ui"> {
@@ -56,5 +56,5 @@ export interface NavigationMenuProps {
56
56
  * Additional CSS classes to apply to the root element.
57
57
  */
58
58
  class?: any
59
- [key: string]: unknown
59
+ [key: string]: any
60
60
  }
@@ -0,0 +1,204 @@
1
+ import { createMemo, createSignal, For, Show, splitProps, type Component } from "solid-js"
2
+ import RLSButton from "../button/RLSButton"
3
+
4
+ type Manager = "npm" | "pnpm" | "yarn" | "deno" | "bun" | "vp"
5
+ type CommandType = "add" | "create" | "dlx" | "exec" | "install" | "remove" | "run"
6
+
7
+ export interface RLSPackageManagersProps {
8
+ pkg?: string
9
+ type?: CommandType
10
+ dev?: boolean
11
+ args?: string
12
+ comment?: string
13
+ prefix?: string
14
+ managers?: Manager[]
15
+ }
16
+
17
+ const commands: Record<Manager, Partial<Record<CommandType, string>> & { dev: string }> = {
18
+ npm: {
19
+ add: "npm i",
20
+ create: "npm create",
21
+ dlx: "npx",
22
+ exec: "npx",
23
+ install: "npm install",
24
+ run: "npm run",
25
+ remove: "npm uninstall",
26
+ dev: "-D"
27
+ },
28
+ yarn: {
29
+ add: "yarn add",
30
+ create: "yarn create",
31
+ dlx: "yarn dlx",
32
+ exec: "yarn",
33
+ install: "yarn install",
34
+ run: "yarn run",
35
+ remove: "yarn remove",
36
+ dev: "-D"
37
+ },
38
+ pnpm: {
39
+ add: "pnpm add",
40
+ create: "pnpm create",
41
+ dlx: "pnpx",
42
+ exec: "pnpm",
43
+ install: "pnpm install",
44
+ run: "pnpm run",
45
+ remove: "pnpm remove",
46
+ dev: "-D"
47
+ },
48
+ deno: {
49
+ add: "deno add",
50
+ create: "deno run -A",
51
+ dlx: "deno run",
52
+ exec: "deno run",
53
+ install: "deno install",
54
+ run: "deno task",
55
+ remove: "deno remove",
56
+ dev: "--dev"
57
+ },
58
+ bun: {
59
+ add: "bun add",
60
+ create: "bun create",
61
+ install: "bun install",
62
+ run: "bun run",
63
+ remove: "bun remove",
64
+ dev: "-d"
65
+ },
66
+ vp: {
67
+ add: "vp add",
68
+ create: "vp create",
69
+ dlx: "vp dlx",
70
+ exec: "vp exec",
71
+ install: "vp install",
72
+ run: "vp run",
73
+ remove: "vp remove",
74
+ dev: "-D"
75
+ }
76
+ }
77
+
78
+ const RLSPackageManagers: Component<RLSPackageManagersProps> = (allProps) => {
79
+ const [props] = splitProps(allProps, [
80
+ "pkg",
81
+ "type",
82
+ "dev",
83
+ "args",
84
+ "comment",
85
+ "prefix",
86
+ "managers"
87
+ ])
88
+
89
+ const getCommand = (mgr: Manager, cmdType: CommandType): string | undefined => {
90
+ let cmd = commands[mgr][cmdType]
91
+ if (cmd === undefined) return undefined
92
+
93
+ if (props.prefix) cmd = `${props.prefix} ${cmd}`
94
+ if (props.comment) cmd = `# ${props.comment.replaceAll("{PKG}", mgr)}\n${cmd}`
95
+ if (props.dev && cmdType === "add") cmd += ` ${commands[mgr].dev}`
96
+
97
+ if (props.pkg) {
98
+ const processedPkg =
99
+ cmdType === "create" && mgr === "yarn"
100
+ ? props.pkg.replace(/@(?![^@]*\/)\S*$/, "")
101
+ : props.pkg
102
+ cmd += ` ${processedPkg}`
103
+ }
104
+
105
+ if (props.args) {
106
+ const needsDoubleDash = mgr === "npm" && !["dlx", "exec", "run"].includes(cmdType)
107
+ cmd += `${needsDoubleDash ? " --" : ""} ${props.args}`
108
+ }
109
+
110
+ return cmd
111
+ }
112
+
113
+ const tabs = createMemo(() =>
114
+ (props.managers ?? ["npm", "pnpm", "yarn", "deno", "bun", "vp"])
115
+ .map((mgr) => ({ mgr, cmd: getCommand(mgr, props.type ?? "add") }))
116
+ .filter((tab) => tab.cmd !== undefined)
117
+ )
118
+
119
+ const [activeIdx, setActiveIdx] = createSignal(0)
120
+ const [copied, setCopied] = createSignal<string | null>(null)
121
+
122
+ const copyCommand = async (mgr: string, cmd: string) => {
123
+ try {
124
+ await navigator.clipboard.writeText(cmd)
125
+ setCopied(mgr)
126
+ setTimeout(() => {
127
+ setCopied((current) => (current === mgr ? null : current))
128
+ }, 2000)
129
+ } catch {
130
+ setCopied(null)
131
+ }
132
+ }
133
+
134
+ return (
135
+ <div
136
+ class="w-full overflow-hidden rounded-xl border border-default bg-elevated/60"
137
+ data-slot="root"
138
+ >
139
+ <div
140
+ class="flex flex-wrap items-center gap-1 border-b border-default bg-elevated/80 px-2 pt-2"
141
+ role="tablist"
142
+ data-slot="list"
143
+ >
144
+ <For each={tabs()}>
145
+ {(tab, idx) => {
146
+ const active = idx() === activeIdx()
147
+ return (
148
+ <button
149
+ type="button"
150
+ role="tab"
151
+ class={`px-3 py-1.5 text-sm font-semibold rounded-t-lg transition-colors ${
152
+ active
153
+ ? "bg-default text-highlighted border border-default border-b-transparent"
154
+ : "text-muted hover:text-highlighted"
155
+ }`}
156
+ data-slot="trigger"
157
+ data-state={active ? "active" : "inactive"}
158
+ data-value={tab.mgr}
159
+ aria-selected={active ? "true" : "false"}
160
+ onClick={() => setActiveIdx(idx())}
161
+ >
162
+ {tab.mgr}
163
+ </button>
164
+ )
165
+ }}
166
+ </For>
167
+ </div>
168
+
169
+ <For each={tabs()}>
170
+ {(tab, idx) => (
171
+ <div
172
+ role="tabpanel"
173
+ data-value={tab.mgr}
174
+ data-slot="content"
175
+ hidden={idx() !== activeIdx() ? "until-found" : undefined}
176
+ >
177
+ <Show when={idx() === activeIdx()}>
178
+ <div class="relative">
179
+ <pre class="overflow-x-auto bg-[#0a0a0f] px-4 py-3 pr-16 text-sm font-mono leading-6 text-[#e4e4e7] whitespace-pre">
180
+ <code aria-hidden="true">{tab.cmd!}</code>
181
+ </pre>
182
+ <RLSButton
183
+ type="button"
184
+ variant="ghost"
185
+ color="neutral"
186
+ size="sm"
187
+ class="absolute right-2 top-2"
188
+ leadingIcon={copied() === tab.mgr ? "check" : "copy"}
189
+ aria-label="Copy command"
190
+ data-slot="copy-button"
191
+ onClick={() => copyCommand(tab.mgr, tab.cmd!)}
192
+ >
193
+ {copied() === tab.mgr ? "Copied" : "Copy"}
194
+ </RLSButton>
195
+ </div>
196
+ </Show>
197
+ </div>
198
+ )}
199
+ </For>
200
+ </div>
201
+ )
202
+ }
203
+
204
+ export default RLSPackageManagers