@rimelight/ui 0.0.46 → 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 (222) hide show
  1. package/dist/icons-DPnFQCts.d.mts +72 -0
  2. package/dist/icons.d.mts +1 -1
  3. package/dist/index.d.mts +5 -6
  4. package/dist/preset.d.mts +1 -1
  5. package/dist/preset.mjs +73 -1
  6. package/dist/resolver.d.mts +4 -5
  7. package/dist/resolver.mjs +2 -4
  8. package/dist/shortcuts.d.mts +10 -11
  9. package/dist/stores.d.mts +3 -4
  10. package/dist/{types-DqAgCNcm.d.mts → types-DWmKMcYC.d.mts} +1 -1
  11. package/dist/types.d.mts +1 -1
  12. package/package.json +8 -6
  13. package/src/components/3d-hover/RLS3dHover.tsx +21 -0
  14. package/src/components/accordion/RLSAccordion.tsx +112 -0
  15. package/src/components/app/RLSApp.tsx +21 -0
  16. package/src/components/aspect-ratio/RLSAspectRatio.tsx +30 -0
  17. package/src/components/audio/RLSAudio.tsx +27 -0
  18. package/src/components/avatar/RLSAvatar.tsx +142 -0
  19. package/src/components/avatar/avatar.ts +22 -21
  20. package/src/components/avatar-group/RLSAvatarGroup.tsx +163 -0
  21. package/src/components/badge/RLSBadge.tsx +1 -1
  22. package/src/components/badge/RLVBadge.vue +5 -3
  23. package/src/components/banner/RLABanner.astro +1 -1
  24. package/src/components/banner/RLSBanner.tsx +151 -0
  25. package/src/components/breadcrumb/RLSBreadcrumb.tsx +194 -0
  26. package/src/components/browser-mockup/RLSBrowserMockup.tsx +21 -0
  27. package/src/components/button/RLSButton.tsx +53 -38
  28. package/src/components/button/RLVButton.vue +44 -32
  29. package/src/components/calendar/RLSCalendar.tsx +26 -0
  30. package/src/components/callout/RLSCallout.tsx +151 -0
  31. package/src/components/card/RLSCard.tsx +4 -0
  32. package/src/components/card/RLVCard.vue +4 -0
  33. package/src/components/card/card.ts +10 -0
  34. package/src/components/carousel/RLACarousel.astro +14 -1
  35. package/src/components/carousel/RLSCarousel.tsx +260 -0
  36. package/src/components/chart/RLSChart.tsx +299 -0
  37. package/src/components/chat/RLSChat.tsx +27 -0
  38. package/src/components/chat-message/RLSChatMessage.tsx +27 -0
  39. package/src/components/chat-messages/RLSChatMessages.tsx +27 -0
  40. package/src/components/chat-palette/RLSChatPalette.tsx +27 -0
  41. package/src/components/chat-prompt/RLSChatPrompt.tsx +27 -0
  42. package/src/components/chat-prompt-submit/RLSChatPromptSubmit.tsx +27 -0
  43. package/src/components/chat-reasoning/RLSChatReasoning.tsx +27 -0
  44. package/src/components/chat-shimmer/RLSChatShimmer.tsx +27 -0
  45. package/src/components/chat-tool/RLSChatTool.tsx +27 -0
  46. package/src/components/checkbox/RLACheckbox.astro +1 -0
  47. package/src/components/checkbox/RLSCheckbox.tsx +1 -1
  48. package/src/components/checkbox/checkbox.theme.ts +2 -2
  49. package/src/components/chip/RLSChip.tsx +65 -0
  50. package/src/components/collapsible/RLSCollapsible.tsx +89 -0
  51. package/src/components/color-area/RLSColorArea.tsx +554 -0
  52. package/src/components/color-field/RLSColorField.tsx +236 -0
  53. package/src/components/color-picker/RLSColorPicker.tsx +26 -0
  54. package/src/components/color-slider/RLSColorSlider.tsx +373 -0
  55. package/src/components/command-palette/RLSCommandPalette.tsx +26 -0
  56. package/src/components/compare/RLSCompare.tsx +26 -0
  57. package/src/components/confirm/RLSConfirm.tsx +173 -0
  58. package/src/components/container/RLSContainer.tsx +24 -0
  59. package/src/components/context-menu/RLSContextMenu.tsx +26 -0
  60. package/src/components/copy-markdown/RLSCopyMarkdown.tsx +57 -0
  61. package/src/components/dashboard-group/RLSDashboardGroup.tsx +29 -0
  62. package/src/components/dashboard-navbar/RLSDashboardNavbar.tsx +116 -0
  63. package/src/components/dashboard-panel/RLSDashboardPanel.tsx +84 -0
  64. package/src/components/dashboard-resize-handle/RLSDashboardResizeHandle.tsx +35 -0
  65. package/src/components/dashboard-search/RLADashboardSearch.astro +1 -1
  66. package/src/components/dashboard-search/RLSDashboardSearch.tsx +125 -0
  67. package/src/components/dashboard-search-button/RLSDashboardSearchButton.tsx +82 -0
  68. package/src/components/dashboard-sidebar/RLADashboardSidebar.astro +4 -4
  69. package/src/components/dashboard-sidebar/RLSDashboardSidebar.tsx +184 -0
  70. package/src/components/dashboard-sidebar-collapse/RLSDashboardSidebarCollapse.tsx +46 -0
  71. package/src/components/dashboard-sidebar-toggle/RLSDashboardSidebarToggle.tsx +35 -0
  72. package/src/components/dashboard-toolbar/RLSDashboardToolbar.tsx +49 -0
  73. package/src/components/date/RLSDate.tsx +34 -0
  74. package/src/components/dock/RLSDock.tsx +26 -0
  75. package/src/components/drawer/RLSDrawer.tsx +177 -0
  76. package/src/components/dropdown-menu/RLADropdownMenu.astro +250 -241
  77. package/src/components/dropdown-menu/RLSDropdownMenu.tsx +183 -86
  78. package/src/components/editor/RLSEditor.tsx +26 -0
  79. package/src/components/empty/RLSEmpty.tsx +132 -0
  80. package/src/components/error/RLSError.tsx +26 -0
  81. package/src/components/field-group/RLSFieldGroup.tsx +46 -0
  82. package/src/components/fieldset/RLSFieldset.tsx +26 -0
  83. package/src/components/file-upload/RLAFileUpload.astro +1 -1
  84. package/src/components/file-upload/RLSFileUpload.tsx +163 -0
  85. package/src/components/floating-action/RLSFloatingAction.tsx +26 -0
  86. package/src/components/footer/RLSFooter.tsx +56 -0
  87. package/src/components/form/RLSForm.tsx +34 -0
  88. package/src/components/gallery/RLSGallery.tsx +21 -0
  89. package/src/components/gamepad/RLSGamepad.tsx +269 -0
  90. package/src/components/grid/RLSGrid.tsx +29 -0
  91. package/src/components/head/RLAHead.astro +9 -0
  92. package/src/components/head/RLSHead.tsx +42 -0
  93. package/src/components/head/UIHead.astro +3 -0
  94. package/src/components/header/HeaderLayer.astro +1 -1
  95. package/src/components/header/RLSHeader.tsx +87 -0
  96. package/src/components/hover-card/RLSHoverCard.tsx +72 -0
  97. package/src/components/icon/RLSIcon.tsx +3 -1
  98. package/src/components/image/RLAImage.astro +447 -138
  99. package/src/components/image/RLSImage.tsx +545 -0
  100. package/src/components/image/image.theme.ts +19 -3
  101. package/src/components/image/image.ts +31 -1
  102. package/src/components/input/RLAInput.astro +62 -51
  103. package/src/components/input/RLSInput.tsx +1 -1
  104. package/src/components/input-date/RLSInputDate.tsx +92 -0
  105. package/src/components/input-menu/RLAInputMenu.astro +3 -3
  106. package/src/components/input-menu/RLSInputMenu.tsx +199 -0
  107. package/src/components/input-number/RLSInputNumber.tsx +184 -0
  108. package/src/components/input-pin/RLSInputPin.tsx +185 -0
  109. package/src/components/input-rating/RLAInputRating.astro +8 -8
  110. package/src/components/input-rating/RLSInputRating.tsx +198 -0
  111. package/src/components/input-rating/input-rating.ts +4 -0
  112. package/src/components/input-tags/RLSInputTags.tsx +94 -0
  113. package/src/components/input-time/RLSInputTime.tsx +92 -0
  114. package/src/components/kbd/RLSKbd.tsx +37 -0
  115. package/src/components/keyboard/RLSKeyboard.tsx +476 -0
  116. package/src/components/knob/RLSKnob.tsx +27 -0
  117. package/src/components/label/RLSLabel.tsx +27 -0
  118. package/src/components/layout-grid/RLALayoutGrid.astro +29 -29
  119. package/src/components/layout-grid/RLSLayoutGrid.tsx +402 -0
  120. package/src/components/layout-grid/layout-grid.ts +25 -1
  121. package/src/components/link/RLALink.astro +5 -5
  122. package/src/components/link/RLSLink.tsx +6 -5
  123. package/src/components/link/link.ts +21 -12
  124. package/src/components/link-group/RLSLinkGroup.tsx +76 -0
  125. package/src/components/listbox/RLSListbox.tsx +27 -0
  126. package/src/components/locale-selector/RLALocaleSelector.astro +110 -102
  127. package/src/components/locale-selector/RLSLocaleSelector.tsx +178 -0
  128. package/src/components/locale-selector/locale-selector.ts +2 -7
  129. package/src/components/logo/RLALogo.astro +153 -102
  130. package/src/components/logo/RLSLogo.tsx +129 -0
  131. package/src/components/main/RLSMain.tsx +40 -0
  132. package/src/components/marquee/RLSMarquee.tsx +80 -0
  133. package/src/components/meter/RLSMeter.tsx +27 -0
  134. package/src/components/modal/RLAModal.astro +1 -1
  135. package/src/components/modal/RLSModal.tsx +109 -72
  136. package/src/components/navigation-menu/RLANavigationMenu.astro +6 -6
  137. package/src/components/navigation-menu/RLSNavigationMenu.tsx +228 -0
  138. package/src/components/navigation-menu/navigation-menu.ts +2 -2
  139. package/src/components/package-managers/RLSPackageManagers.tsx +204 -0
  140. package/src/components/page/RLSPage.tsx +21 -0
  141. package/src/components/page-aside/RLSPageAside.tsx +21 -0
  142. package/src/components/page-body/RLSPageBody.tsx +21 -0
  143. package/src/components/page-header/RLSPageHeader.tsx +21 -0
  144. package/src/components/page-section/RLSPageSection.tsx +201 -0
  145. package/src/components/pagination/RLSPagination.tsx +114 -0
  146. package/src/components/password/RLSPassword.tsx +27 -0
  147. package/src/components/phone-mockup/RLSPhoneMockup.tsx +27 -0
  148. package/src/components/placeholder/RLSPlaceholder.tsx +47 -0
  149. package/src/components/popover/RLAPopover.astro +2 -2
  150. package/src/components/popover/RLSPopover.tsx +124 -0
  151. package/src/components/post/RLSPost.tsx +134 -0
  152. package/src/components/pricing-plan/RLSPricingPlan.tsx +26 -0
  153. package/src/components/pricing-table/RLSPricingTable.tsx +26 -0
  154. package/src/components/progress/RLSProgress.tsx +53 -0
  155. package/src/components/prose/RLSProse.tsx +26 -0
  156. package/src/components/qr-code/RLSQrCode.tsx +26 -0
  157. package/src/components/quote/RLAQuote.astro +10 -6
  158. package/src/components/quote/RLSQuote.tsx +37 -0
  159. package/src/components/quote/quote.theme.ts +5 -2
  160. package/src/components/radio-group/RLSRadioGroup.tsx +37 -0
  161. package/src/components/rating/RLSRating.tsx +27 -0
  162. package/src/components/scroll-area/RLAScrollArea.astro +1 -1
  163. package/src/components/scroll-area/RLSScrollArea.tsx +210 -0
  164. package/src/components/scroll-to-top/RLAScrollToTop.astro +5 -4
  165. package/src/components/scroll-to-top/RLSScrollToTop.tsx +145 -0
  166. package/src/components/search/RLASearch.astro +6 -6
  167. package/src/components/search/RLSSearch.tsx +237 -0
  168. package/src/components/select/RLASelect.astro +67 -4
  169. package/src/components/select/RLSSelect.tsx +107 -39
  170. package/src/components/select/RLVSelect.vue +50 -1
  171. package/src/components/select/select.theme.ts +30 -10
  172. package/src/components/select/select.ts +20 -1
  173. package/src/components/separator/RLSSeparator.tsx +92 -0
  174. package/src/components/share/RLSShare.tsx +66 -0
  175. package/src/components/shortcuts/RLAShortcuts.astro +2 -2
  176. package/src/components/shortcuts/RLSShortcuts.tsx +393 -0
  177. package/src/components/sidebar/RLASidebar.astro +1 -1
  178. package/src/components/sidebar/RLSSidebar.tsx +271 -0
  179. package/src/components/skeleton/RLSSkeleton.tsx +21 -0
  180. package/src/components/slideover/RLASlideover.astro +1 -1
  181. package/src/components/slideover/RLSSlideover.tsx +159 -0
  182. package/src/components/slider/RLSSlider.tsx +112 -0
  183. package/src/components/speed-dial/RLSSpeedDial.tsx +27 -0
  184. package/src/components/spinner/RLSSpinner.tsx +22 -0
  185. package/src/components/splitter/RLASplitter.astro +1 -1
  186. package/src/components/splitter/RLSSplitter.tsx +178 -0
  187. package/src/components/spoiler/RLSSpoiler.tsx +26 -0
  188. package/src/components/stepper/RLAStepper.astro +9 -9
  189. package/src/components/stepper/RLSStepper.tsx +205 -0
  190. package/src/components/steps/RLSSteps.tsx +108 -0
  191. package/src/components/sticky-surface/RLSStickySurface.tsx +63 -0
  192. package/src/components/surround/RLSSurround.tsx +57 -0
  193. package/src/components/switch/RLSSwitch.tsx +56 -0
  194. package/src/components/table/RLATable.astro +1044 -579
  195. package/src/components/table/RLSTable.tsx +893 -0
  196. package/src/components/table/table.theme.ts +47 -16
  197. package/src/components/table/table.ts +104 -66
  198. package/src/components/table-of-contents/RLSTableOfContents.tsx +196 -0
  199. package/src/components/tabs/RLATabs.astro +8 -8
  200. package/src/components/tabs/RLSTabs.tsx +298 -0
  201. package/src/components/textarea/RLATextarea.astro +1 -0
  202. package/src/components/textarea/RLSTextarea.tsx +3 -1
  203. package/src/components/theme-selector/RLAThemeSelector.astro +85 -56
  204. package/src/components/theme-selector/RLSThemeSelector.tsx +131 -0
  205. package/src/components/theme-selector/theme-selector.theme.ts +5 -7
  206. package/src/components/theme-selector/theme-selector.ts +11 -1
  207. package/src/components/timeline/RLSTimeline.tsx +171 -0
  208. package/src/components/toast/RLAToast.astro +2 -2
  209. package/src/components/toast/RLSToast.tsx +179 -0
  210. package/src/components/toaster/RLSToaster.tsx +82 -0
  211. package/src/components/tooltip/RLSTooltip.tsx +48 -0
  212. package/src/components/tour/RLSTour.tsx +26 -0
  213. package/src/components/tree/RLSTree.tsx +26 -0
  214. package/src/components/user/RLSUser.tsx +26 -0
  215. package/src/components/video/RLAVideo.astro +4 -1
  216. package/src/components/video/RLSVideo.tsx +158 -0
  217. package/src/components/watermark/RLSWatermark.tsx +27 -0
  218. package/src/components/window-mockup/RLSWindowMockup.tsx +27 -0
  219. package/src/preset.ts +73 -1
  220. package/src/resolver.ts +6 -1
  221. package/src/virtual.d.ts +6 -0
  222. package/dist/icons--RzV1RTU.d.mts +0 -72
@@ -0,0 +1,210 @@
1
+ import {
2
+ createEffect,
3
+ createMemo,
4
+ createSignal,
5
+ onCleanup,
6
+ onMount,
7
+ Show,
8
+ splitProps,
9
+ type Component,
10
+ type JSX
11
+ } from "solid-js"
12
+ import type { ScrollAreaProps } from "./scroll-area"
13
+ import { scrollAreaTheme } from "./scroll-area.theme"
14
+
15
+ export interface RLSScrollAreaProps extends ScrollAreaProps {
16
+ children?: JSX.Element
17
+ }
18
+
19
+ const RLSScrollArea: Component<RLSScrollAreaProps> = (allProps) => {
20
+ const [props, rest] = splitProps(allProps, [
21
+ "orientation",
22
+ "scrollHideDelay",
23
+ "height",
24
+ "ui",
25
+ "class",
26
+ "children"
27
+ ])
28
+
29
+ const orientation = () => props.orientation ?? "vertical"
30
+ const hideDelay = () => props.scrollHideDelay ?? 600
31
+
32
+ let viewportRef: HTMLDivElement | null = null
33
+ let vBarRef: HTMLDivElement | null = null
34
+ let hBarRef: HTMLDivElement | null = null
35
+ let vThumbRef: HTMLDivElement | null = null
36
+ let hThumbRef: HTMLDivElement | null = null
37
+
38
+ const [barsVisible, setBarsVisible] = createSignal(false)
39
+ let hideTimer: ReturnType<typeof setTimeout> | undefined
40
+
41
+ const showBars = () => {
42
+ setBarsVisible(true)
43
+ }
44
+
45
+ const hideBars = () => {
46
+ if (hideTimer) clearTimeout(hideTimer)
47
+ hideTimer = setTimeout(() => {
48
+ setBarsVisible(false)
49
+ }, hideDelay())
50
+ }
51
+
52
+ const updateThumbs = () => {
53
+ const vp = viewportRef
54
+ if (!vp) return
55
+
56
+ if (vThumbRef && vBarRef) {
57
+ const ratio = vp.clientHeight / vp.scrollHeight
58
+ const thumbH = Math.max(ratio * vBarRef.clientHeight, 20)
59
+ const thumbTop = (vp.scrollTop / vp.scrollHeight) * vBarRef.clientHeight
60
+ vThumbRef.style.height = `${thumbH}px`
61
+ vThumbRef.style.transform = `translateY(${thumbTop}px)`
62
+ }
63
+
64
+ if (hThumbRef && hBarRef) {
65
+ const ratio = vp.clientWidth / vp.scrollWidth
66
+ const thumbW = Math.max(ratio * hBarRef.clientWidth, 20)
67
+ const thumbLeft = (vp.scrollLeft / vp.scrollWidth) * hBarRef.clientWidth
68
+ hThumbRef.style.width = `${thumbW}px`
69
+ hThumbRef.style.transform = `translateX(${thumbLeft}px)`
70
+ }
71
+ }
72
+
73
+ const handleScroll = () => {
74
+ if (hideTimer) clearTimeout(hideTimer)
75
+ showBars()
76
+ updateThumbs()
77
+ hideBars()
78
+ }
79
+
80
+ const setupDraggable = (
81
+ thumb: HTMLDivElement | null,
82
+ scrollProp: "scrollTop" | "scrollLeft",
83
+ clientProp: "clientY" | "clientX",
84
+ sizeProp: "scrollHeight" | "scrollWidth",
85
+ clientSizeProp: "clientHeight" | "clientWidth"
86
+ ) => {
87
+ if (!thumb) return () => {}
88
+ let startPos = 0
89
+ let startScroll = 0
90
+
91
+ const onMouseDown = (e: MouseEvent) => {
92
+ const vp = viewportRef
93
+ if (!vp) return
94
+ e.preventDefault()
95
+ startPos = e[clientProp]
96
+ startScroll = vp[scrollProp]
97
+
98
+ const onMouseMove = (ev: MouseEvent) => {
99
+ const vpCurrent = viewportRef
100
+ if (!vpCurrent) return
101
+ const delta = ev[clientProp] - startPos
102
+ const ratio = vpCurrent[sizeProp] / vpCurrent[clientSizeProp]
103
+ vpCurrent[scrollProp] = startScroll + delta * ratio
104
+ }
105
+
106
+ const onMouseUp = () => {
107
+ document.removeEventListener("mousemove", onMouseMove)
108
+ document.removeEventListener("mouseup", onMouseUp)
109
+ }
110
+
111
+ document.addEventListener("mousemove", onMouseMove)
112
+ document.addEventListener("mouseup", onMouseUp)
113
+ }
114
+
115
+ thumb.addEventListener("mousedown", onMouseDown)
116
+ return () => thumb.removeEventListener("mousedown", onMouseDown)
117
+ }
118
+
119
+ onMount(() => {
120
+ updateThumbs()
121
+ const cleanV = setupDraggable(vThumbRef, "scrollTop", "clientY", "scrollHeight", "clientHeight")
122
+ const cleanH = setupDraggable(hThumbRef, "scrollLeft", "clientX", "scrollWidth", "clientWidth")
123
+
124
+ onCleanup(() => {
125
+ if (hideTimer) clearTimeout(hideTimer)
126
+ cleanV()
127
+ cleanH()
128
+ })
129
+ })
130
+
131
+ createEffect(() => {
132
+ void props.children
133
+ requestAnimationFrame(() => updateThumbs())
134
+ })
135
+
136
+ const resolvedClasses = createMemo(() =>
137
+ scrollAreaTheme({
138
+ orientation: orientation(),
139
+ ui: props.ui,
140
+ class: props.class
141
+ })
142
+ )
143
+
144
+ const heightStyle = () =>
145
+ props.height
146
+ ? typeof props.height === "number"
147
+ ? `${props.height}px`
148
+ : props.height
149
+ : undefined
150
+
151
+ return (
152
+ <div
153
+ class={resolvedClasses().root}
154
+ data-slot="root"
155
+ data-scroll-area
156
+ data-orientation={orientation()}
157
+ data-hide-delay={hideDelay()}
158
+ style={{ height: heightStyle() }}
159
+ {...rest}
160
+ >
161
+ <div
162
+ ref={(el) => (viewportRef = el)}
163
+ class={resolvedClasses().viewport}
164
+ data-slot="viewport"
165
+ data-scroll-viewport
166
+ onScroll={handleScroll}
167
+ >
168
+ {props.children}
169
+ </div>
170
+
171
+ <Show when={orientation() === "vertical" || orientation() === "both"}>
172
+ <div
173
+ ref={(el) => (vBarRef = el)}
174
+ class={`${resolvedClasses().scrollbar} transition-opacity ${
175
+ barsVisible() ? "opacity-100" : "opacity-0"
176
+ }`}
177
+ data-slot="scrollbar"
178
+ data-scroll-bar="vertical"
179
+ aria-hidden="true"
180
+ >
181
+ <div
182
+ ref={(el) => (vThumbRef = el)}
183
+ class={resolvedClasses().thumb}
184
+ data-scroll-thumb="vertical"
185
+ />
186
+ </div>
187
+ </Show>
188
+
189
+ <Show when={orientation() === "horizontal" || orientation() === "both"}>
190
+ <div
191
+ ref={(el) => (hBarRef = el)}
192
+ class={`absolute bottom-0 left-0 right-0 h-2.5 flex-row flex p-0.5 bg-transparent transition-colors duration-150 ease-out hover:bg-muted/10 transition-opacity ${
193
+ barsVisible() ? "opacity-100" : "opacity-0"
194
+ }`}
195
+ data-slot="scrollbar"
196
+ data-scroll-bar="horizontal"
197
+ aria-hidden="true"
198
+ >
199
+ <div
200
+ ref={(el) => (hThumbRef = el)}
201
+ class={resolvedClasses().thumb}
202
+ data-scroll-thumb="horizontal"
203
+ />
204
+ </div>
205
+ </Show>
206
+ </div>
207
+ )
208
+ }
209
+
210
+ export default RLSScrollArea
@@ -21,6 +21,7 @@ const classes = scrollToTopTheme(Astro.props)
21
21
  <rla-scroll-to-top data-threshold={threshold} data-duration={duration} data-color={color} data-progress-width={progressWidth} data-theme={theme} {...rest}>
22
22
  <div
23
23
  class:list={["rla-scroll-to-top-inner", "fixed bottom-6 right-6 z-50", classes.root]}
24
+ data-slot="root"
24
25
  >
25
26
  <button
26
27
  type="button"
@@ -69,12 +70,12 @@ const classes = scrollToTopTheme(Astro.props)
69
70
  if (!button || !innerElement) return;
70
71
 
71
72
  const id = ++RLAScrollToTop.instanceCounter;
72
- const threshold = Number(this.dataset.threshold) || 200;
73
- const color = this.dataset.color || 'primary';
74
- const progressWidth = Number(this.dataset.progressWidth) || 6;
73
+ const threshold = Number(this.dataset["threshold"]) || 200;
74
+ const color = this.dataset["color"] || 'primary';
75
+ const progressWidth = Number(this.dataset["progressWidth"]) || 6;
75
76
  const gaugeBorderWidth = progressWidth * 0.5;
76
77
 
77
- innerElement.dataset.rlaInstance = String(id);
78
+ innerElement.dataset["rlaInstance"] = String(id);
78
79
 
79
80
  button.addEventListener('click', scrollToTop);
80
81
 
@@ -0,0 +1,145 @@
1
+ import { createMemo, createSignal, splitProps, onMount, onCleanup, type Component } from "solid-js"
2
+ import type { ScrollToTopProps } from "./scroll-to-top"
3
+ import { scrollToTopTheme } from "./scroll-to-top.theme"
4
+ import { trackScroll, scrollToTop } from "./scroll"
5
+
6
+ export interface RLSScrollToTopProps extends ScrollToTopProps {}
7
+
8
+ const SCROLL_TO_TOP_CSS = `
9
+ .rla-scroll-to-top-inner {
10
+ opacity: 0;
11
+ pointer-events: none;
12
+ transition: opacity 0.3s ease-in-out;
13
+ }
14
+ .rla-scroll-to-top-inner.js-visible {
15
+ opacity: 1;
16
+ pointer-events: auto;
17
+ }
18
+ .progress-circle-base {
19
+ position: relative;
20
+ transform: translateZ(0);
21
+ }
22
+ .gauge-track {
23
+ position: absolute;
24
+ inset: 0;
25
+ border-radius: 50%;
26
+ border-style: solid;
27
+ }
28
+ .gauge-progress {
29
+ position: absolute;
30
+ inset: 0;
31
+ border-radius: 50%;
32
+ background: conic-gradient(
33
+ from 0deg,
34
+ var(--gauge-color, var(--rl-primary-500)) 0deg,
35
+ var(--gauge-color, var(--rl-primary-500)) calc(var(--gauge-progress, 0) * 3.6deg),
36
+ transparent calc(var(--gauge-progress, 0) * 3.6deg)
37
+ );
38
+ -webkit-mask: radial-gradient(
39
+ circle closest-side,
40
+ transparent calc(100% - var(--gauge-stroke-width, 6px)),
41
+ #000 calc(100% - var(--gauge-stroke-width, 6px))
42
+ );
43
+ mask: radial-gradient(
44
+ circle closest-side,
45
+ transparent calc(100% - var(--gauge-stroke-width, 6px)),
46
+ #000 calc(100% - var(--gauge-stroke-width, 6px))
47
+ );
48
+ }
49
+ `
50
+
51
+ const RLSScrollToTop: Component<RLSScrollToTopProps> = (allProps) => {
52
+ const [props, rest] = splitProps(allProps, [
53
+ "color",
54
+ "progressWidth",
55
+ "duration",
56
+ "threshold",
57
+ "showProgress",
58
+ "theme",
59
+ "ui",
60
+ "class"
61
+ ])
62
+
63
+ const [isVisible, setIsVisible] = createSignal(false)
64
+ const [scrollPercentage, setScrollPercentage] = createSignal(0)
65
+
66
+ onMount(() => {
67
+ const unsubscribe = trackScroll({ threshold: props.threshold ?? 200 }, (state) => {
68
+ setIsVisible(state.isVisible)
69
+ setScrollPercentage(state.scrollPercentage)
70
+ })
71
+ onCleanup(unsubscribe)
72
+ })
73
+
74
+ const resolvedClasses = createMemo(() =>
75
+ scrollToTopTheme({
76
+ color: props.color ?? "primary",
77
+ ui: props.ui,
78
+ class: props.class
79
+ })
80
+ )
81
+
82
+ const color = () => props.color ?? "primary"
83
+ const progressWidth = () => props.progressWidth ?? 6
84
+ const gaugeBorderWidth = () => progressWidth() * 0.5
85
+
86
+ const progressStyle = () =>
87
+ ({
88
+ "--gauge-stroke-width": `${gaugeBorderWidth()}px`,
89
+ "--gauge-color": `var(--rl-${color()}-500)`,
90
+ "--gauge-progress": String(scrollPercentage())
91
+ }) as Record<string, string>
92
+
93
+ const trackStyle = () =>
94
+ ({
95
+ "border-width": `${gaugeBorderWidth()}px`,
96
+ "border-color": `var(--rl-${color()}-900)`,
97
+ "background-color": "#000"
98
+ }) as Record<string, string>
99
+
100
+ return (
101
+ <div
102
+ class={`rla-scroll-to-top-inner scroll-to-top-inner fixed bottom-6 right-6 z-50 ${resolvedClasses().root} ${
103
+ isVisible() ? "js-visible" : ""
104
+ }`}
105
+ data-slot="root"
106
+ data-theme={props.theme}
107
+ {...rest}
108
+ >
109
+ <button
110
+ type="button"
111
+ class={resolvedClasses().button}
112
+ aria-label="Scroll to top"
113
+ data-scroll-to-top-button
114
+ onClick={scrollToTop}
115
+ >
116
+ <div class={resolvedClasses().progressBase}>
117
+ <div class="gauge-track" style={trackStyle()} />
118
+ {props.showProgress ? (
119
+ <div class="gauge-progress" data-gauge-progress style={progressStyle()} />
120
+ ) : null}
121
+ <div class={resolvedClasses().iconContainer}>
122
+ <svg
123
+ xmlns="http://www.w3.org/2000/svg"
124
+ width="24"
125
+ height="24"
126
+ viewBox="0 0 24 24"
127
+ fill="none"
128
+ stroke="currentColor"
129
+ stroke-width="2"
130
+ stroke-linecap="round"
131
+ stroke-linejoin="round"
132
+ class={resolvedClasses().icon}
133
+ >
134
+ <path d="m5 12 7-7 7 7" />
135
+ <path d="M12 19V5" />
136
+ </svg>
137
+ </div>
138
+ </div>
139
+ </button>
140
+ <style>{SCROLL_TO_TOP_CSS}</style>
141
+ </div>
142
+ )
143
+ }
144
+
145
+ export default RLSScrollToTop
@@ -112,12 +112,12 @@ const emptyId = `search-empty-${uniqueId}`
112
112
 
113
113
  connectedCallback() {
114
114
  this.style.display = 'contents';
115
- const triggerId = this.dataset.triggerId;
116
- const modalId = this.dataset.modalId;
117
- const inputId = this.dataset.inputId;
118
- const resultsId = this.dataset.resultsId;
119
- const emptyId = this.dataset.emptyId;
120
- const endpoint = this.dataset.endpoint;
115
+ const triggerId = this.dataset["triggerId"];
116
+ const modalId = this.dataset["modalId"];
117
+ const inputId = this.dataset["inputId"];
118
+ const resultsId = this.dataset["resultsId"];
119
+ const emptyId = this.dataset["emptyId"];
120
+ const endpoint = this.dataset["endpoint"];
121
121
 
122
122
  const trigger = this.querySelector(`#${triggerId}`);
123
123
  const modal = this.querySelector(`#${modalId}`) as HTMLElement | null;
@@ -0,0 +1,237 @@
1
+ import {
2
+ createMemo,
3
+ createSignal,
4
+ For,
5
+ onCleanup,
6
+ onMount,
7
+ Show,
8
+ splitProps,
9
+ type Component,
10
+ type JSX
11
+ } from "solid-js"
12
+ import type { SearchProps } from "./search"
13
+ import { searchTheme } from "./search.theme"
14
+ import RLSIcon from "../icon/RLSIcon"
15
+ import RLSInput from "../input/RLSInput"
16
+ import RLSKbd from "../kbd/RLSKbd"
17
+ import RLSButton from "../button/RLSButton"
18
+ import { defineShortcuts } from "../../shortcuts"
19
+
20
+ export interface SearchResultItem {
21
+ title?: string
22
+ url?: string
23
+ sourceType?: string
24
+ snippet?: string
25
+ [key: string]: any
26
+ }
27
+
28
+ export interface RLSSearchProps extends SearchProps {
29
+ children?: JSX.Element
30
+ triggerSlot?: JSX.Element
31
+ }
32
+
33
+ const RLSSearch: Component<RLSSearchProps> = (allProps) => {
34
+ const [props, rest] = splitProps(allProps, [
35
+ "endpoint",
36
+ "placeholder",
37
+ "messages",
38
+ "ui",
39
+ "class",
40
+ "children",
41
+ "triggerSlot"
42
+ ])
43
+
44
+ const endpoint = () => props.endpoint ?? "/api/search"
45
+ const searchPlaceholder = () =>
46
+ props.messages?.searchPlaceholder || props.placeholder || "Search..."
47
+ const noResultsLabel = () => props.messages?.noResultsLabel || "No results found"
48
+ const footerCloseHint = () => props.messages?.footerCloseHint || "Escape to close"
49
+ const footerBackendHint = () => props.messages?.footerBackendHint || "Postgres Full-Text Search"
50
+
51
+ const [isOpen, setIsOpen] = createSignal(false)
52
+ const [query, setQuery] = createSignal("")
53
+ const [results, setResults] = createSignal<SearchResultItem[]>([])
54
+ const [hasSearched, setHasSearched] = createSignal(false)
55
+ const [loading, setLoading] = createSignal(false)
56
+
57
+ let inputRef: HTMLInputElement | null = null
58
+ let debounceTimer: ReturnType<typeof setTimeout> | undefined
59
+
60
+ const openModal = () => {
61
+ setIsOpen(true)
62
+ setTimeout(() => inputRef?.focus(), 50)
63
+ }
64
+
65
+ const closeModal = () => {
66
+ setIsOpen(false)
67
+ setQuery("")
68
+ setResults([])
69
+ setHasSearched(false)
70
+ if (debounceTimer) clearTimeout(debounceTimer)
71
+ }
72
+
73
+ onMount(() => {
74
+ const metaKCleanup = defineShortcuts({
75
+ meta_k: {
76
+ handler: () => openModal(),
77
+ label: "Open Search",
78
+ description: "Open the global site search modal",
79
+ category: "navigation"
80
+ }
81
+ })
82
+
83
+ const onKeyDown = (e: KeyboardEvent) => {
84
+ if (e.key === "Escape" && isOpen()) {
85
+ closeModal()
86
+ }
87
+ }
88
+
89
+ window.addEventListener("keydown", onKeyDown)
90
+
91
+ onCleanup(() => {
92
+ metaKCleanup()
93
+ window.removeEventListener("keydown", onKeyDown)
94
+ if (debounceTimer) clearTimeout(debounceTimer)
95
+ })
96
+ })
97
+
98
+ const handleInput = (val: string) => {
99
+ setQuery(val)
100
+ if (debounceTimer) clearTimeout(debounceTimer)
101
+
102
+ const trimmed = val.trim()
103
+ if (!trimmed) {
104
+ setResults([])
105
+ setHasSearched(false)
106
+ setLoading(false)
107
+ return
108
+ }
109
+
110
+ setLoading(true)
111
+ debounceTimer = setTimeout(async () => {
112
+ try {
113
+ const res = await fetch(`${endpoint()}?q=${encodeURIComponent(trimmed)}`)
114
+ if (!res.ok) throw new Error(`HTTP status ${res.status}`)
115
+ const data = await res.json()
116
+ setResults(Array.isArray(data) ? data : [])
117
+ setHasSearched(true)
118
+ } catch (err) {
119
+ console.error("[RLSSearch] Search query failed:", err)
120
+ setResults([])
121
+ setHasSearched(true)
122
+ } finally {
123
+ setLoading(false)
124
+ }
125
+ }, 200)
126
+ }
127
+
128
+ const resolvedClasses = createMemo(() =>
129
+ searchTheme({
130
+ ui: props.ui,
131
+ class: props.class
132
+ })
133
+ )
134
+
135
+ return (
136
+ <div class={`${resolvedClasses().root} rla-search-wrapper`} data-slot="root" {...rest}>
137
+ {props.triggerSlot ? (
138
+ <div class="cursor-pointer" onClick={openModal}>
139
+ {props.triggerSlot}
140
+ </div>
141
+ ) : props.children ? (
142
+ <div class="cursor-pointer" onClick={openModal}>
143
+ {props.children}
144
+ </div>
145
+ ) : (
146
+ <RLSButton
147
+ variant="ghost"
148
+ color="neutral"
149
+ square
150
+ aria-label="Open Search"
151
+ onClick={openModal}
152
+ >
153
+ <RLSIcon name="i-rl-search" class="w-5 h-5" />
154
+ </RLSButton>
155
+ )}
156
+
157
+ <Show when={isOpen()}>
158
+ <div
159
+ class={resolvedClasses().modal}
160
+ style={{ display: "flex" }}
161
+ data-slot="modal"
162
+ onClick={(e) => {
163
+ if (e.target === e.currentTarget) closeModal()
164
+ }}
165
+ >
166
+ <div class={resolvedClasses().dialog} data-slot="dialog">
167
+ <div class={resolvedClasses().header} data-slot="header">
168
+ <RLSInput
169
+ ref={(el) => (inputRef = el)}
170
+ variant="ghost"
171
+ type="text"
172
+ value={query()}
173
+ placeholder={searchPlaceholder()}
174
+ autocomplete="off"
175
+ onInput={(e) => handleInput(e.currentTarget.value)}
176
+ leading={<RLSIcon name="i-rl-search" class={resolvedClasses().headerIcon} />}
177
+ trailing={
178
+ <div class="flex items-center gap-2">
179
+ <RLSKbd size="sm">Esc</RLSKbd>
180
+ <button
181
+ type="button"
182
+ class={resolvedClasses().close}
183
+ aria-label="Close search"
184
+ onClick={closeModal}
185
+ >
186
+ <RLSIcon name="i-rl-close" class={resolvedClasses().closeIcon} />
187
+ </button>
188
+ </div>
189
+ }
190
+ ui={{
191
+ root: "relative flex items-center w-full",
192
+ input:
193
+ "pl-8 pr-20 py-1.5 text-base outline-none text-white placeholder-neutral-500",
194
+ leading: "absolute left-0 text-neutral-400 flex items-center",
195
+ trailing: "absolute right-0 flex items-center gap-2"
196
+ }}
197
+ />
198
+ </div>
199
+
200
+ <div class={resolvedClasses().results} data-slot="results">
201
+ <For each={results()}>
202
+ {(item) => (
203
+ <a
204
+ href={item.url || "#"}
205
+ class={resolvedClasses().resultItem}
206
+ onClick={closeModal}
207
+ >
208
+ <div class={resolvedClasses().resultHeader}>
209
+ <span class={resolvedClasses().resultTitle}>{item.title || "Untitled"}</span>
210
+ {item.sourceType && (
211
+ <span class={resolvedClasses().resultBadge}>{item.sourceType}</span>
212
+ )}
213
+ </div>
214
+ {item.snippet && <p class={resolvedClasses().resultSnippet}>{item.snippet}</p>}
215
+ </a>
216
+ )}
217
+ </For>
218
+ </div>
219
+
220
+ <Show when={hasSearched() && results().length === 0 && !loading()}>
221
+ <div class={resolvedClasses().empty} style={{ display: "block" }} data-slot="empty">
222
+ {noResultsLabel()}
223
+ </div>
224
+ </Show>
225
+
226
+ <div class={resolvedClasses().footer} data-slot="footer">
227
+ <span>{footerCloseHint()}</span>
228
+ <span>{footerBackendHint()}</span>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ </Show>
233
+ </div>
234
+ )
235
+ }
236
+
237
+ export default RLSSearch