@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,545 @@
1
+ import {
2
+ createEffect,
3
+ createMemo,
4
+ createSignal,
5
+ onCleanup,
6
+ onMount,
7
+ splitProps,
8
+ type Component,
9
+ type JSX
10
+ } from "solid-js"
11
+ import type { ImageMetadata } from "astro"
12
+ import type { ImageProps } from "./image"
13
+ import { imageTheme } from "./image.theme"
14
+ import RLSIcon from "../icon/RLSIcon"
15
+
16
+ export interface RLSImageProps extends ImageProps {
17
+ trigger?: JSX.Element
18
+ captionSlot?: JSX.Element
19
+ children?: JSX.Element
20
+ }
21
+
22
+ const RLSImage: Component<RLSImageProps> = (allProps) => {
23
+ const [props, rest] = splitProps(allProps, [
24
+ "src",
25
+ "lightSrc",
26
+ "darkSrc",
27
+ "mobileSrc",
28
+ "desktopSrc",
29
+ "breakpoint",
30
+ "formats",
31
+ "widths",
32
+ "sizes",
33
+ "densities",
34
+ "alt",
35
+ "height",
36
+ "width",
37
+ "loading",
38
+ "decoding",
39
+ "fit",
40
+ "caption",
41
+ "figure",
42
+ "lightbox",
43
+ "interactive",
44
+ "theme",
45
+ "metadata",
46
+ "open",
47
+ "metaSource",
48
+ "metaFormat",
49
+ "metaDimensions",
50
+ "metaSize",
51
+ "metaAlt",
52
+ "metaDownload",
53
+ "ui",
54
+ "class",
55
+ "trigger",
56
+ "captionSlot",
57
+ "children"
58
+ ])
59
+
60
+ const isInteractive = props.lightbox ?? props.interactive ?? true
61
+ const isFigure = props.figure ?? !!props.caption
62
+
63
+ const isResponsive = !!(props.mobileSrc && props.desktopSrc)
64
+ const effectiveLight =
65
+ props.lightSrc || (!props.darkSrc ? props.src : props.src || props.lightSrc)
66
+ const effectiveDark = props.darkSrc
67
+ const hasColorMode =
68
+ !isResponsive && !!(effectiveLight && effectiveDark && effectiveLight !== effectiveDark)
69
+ const breakpointVal = props.breakpoint ?? 768
70
+ const activeSrc = isResponsive ? props.desktopSrc : effectiveLight || props.src
71
+
72
+ const urlOf = (v?: string | ImageMetadata): string | undefined =>
73
+ typeof v === "string" ? v : (v as ImageMetadata | undefined)?.src
74
+
75
+ const widthOf = (v?: string | ImageMetadata): string | number | undefined =>
76
+ v && typeof v !== "string" ? (v as ImageMetadata).width : undefined
77
+
78
+ const heightOf = (v?: string | ImageMetadata): string | number | undefined =>
79
+ v && typeof v !== "string" ? (v as ImageMetadata).height : undefined
80
+
81
+ const formatOf = (v?: string | ImageMetadata): string | undefined =>
82
+ v && typeof v !== "string" ? (v as ImageMetadata).format : undefined
83
+
84
+ const activeUrl = urlOf(activeSrc)
85
+ const lightUrl = effectiveLight ? (urlOf(effectiveLight) ?? "") : ""
86
+ const darkUrl = effectiveDark ? (urlOf(effectiveDark) ?? "") : ""
87
+
88
+ const imgWidth = props.width ?? widthOf(activeSrc)
89
+ const imgHeight = props.height ?? heightOf(activeSrc)
90
+ const imgFormat = formatOf(activeSrc) ?? props.metadata?.format
91
+ const rawName = activeUrl ? activeUrl.split("/").pop() || "image" : "image"
92
+ const fileName = rawName.split("?")[0] ?? ""
93
+ const fileExtension =
94
+ imgFormat?.toUpperCase() ||
95
+ (fileName.includes(".") ? (fileName.split(".").pop()?.toUpperCase() ?? "IMG") : "IMG")
96
+
97
+ const resolvedClasses = createMemo(() =>
98
+ imageTheme({ fit: props.fit ?? "cover", ui: props.ui, class: props.class })
99
+ )
100
+
101
+ const alt = props.alt ?? ""
102
+ const loading = props.loading ?? "lazy"
103
+ const decoding = props.decoding ?? "async"
104
+
105
+ const [show, setShow] = createSignal(loading !== "lazy")
106
+ const [status, setStatus] = createSignal<"loading" | "ok" | "error">("loading")
107
+ const [open, setOpen] = createSignal(Boolean(props.open))
108
+ const [zoom, setZoom] = createSignal(false)
109
+ const [metaSizeTxt, setMetaSizeTxt] = createSignal<string>(
110
+ props.metadata?.size !== undefined ? `${props.metadata.size} bytes` : "fetching..."
111
+ )
112
+ const [metaFormatTxt, setMetaFormatTxt] = createSignal<string>(fileExtension)
113
+
114
+ let rootRef: HTMLDivElement | undefined
115
+ let dialogRef: HTMLDialogElement | undefined
116
+ let fetched = false
117
+
118
+ const handleError = () => setStatus("error")
119
+ const handleLoad = () => setStatus("ok")
120
+
121
+ const activeModalSrc = () => {
122
+ if (typeof document === "undefined") return activeUrl ?? ""
123
+ const rootTheme = document.documentElement.getAttribute("data-theme")
124
+ const isDark =
125
+ rootTheme === "dark" ||
126
+ (typeof window !== "undefined" &&
127
+ (window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false) &&
128
+ rootTheme !== "light")
129
+ if (isDark && darkUrl) return darkUrl
130
+ return lightUrl || activeUrl || ""
131
+ }
132
+
133
+ const openDialog = () => setOpen(true)
134
+
135
+ const handleDownload = async () => {
136
+ const url = activeModalSrc()
137
+ if (!url) return
138
+ try {
139
+ const response = await fetch(url)
140
+ const blob = await response.blob()
141
+ const objectUrl = URL.createObjectURL(blob)
142
+ const link = document.createElement("a")
143
+ link.href = objectUrl
144
+ link.download = (url.split("/").pop() || "").split("?")[0] || `image-${Date.now()}`
145
+ document.body.appendChild(link)
146
+ link.click()
147
+ document.body.removeChild(link)
148
+ URL.revokeObjectURL(objectUrl)
149
+ } catch (err) {
150
+ console.error("Download failed", err)
151
+ }
152
+ }
153
+
154
+ createEffect(() => {
155
+ const el = rootRef
156
+ if (!el || loading !== "lazy" || show()) return
157
+ const io = new IntersectionObserver(
158
+ (entries) => {
159
+ if (entries.some((e) => e.isIntersecting)) {
160
+ setShow(true)
161
+ io.disconnect()
162
+ }
163
+ },
164
+ { rootMargin: "200px" }
165
+ )
166
+ io.observe(el)
167
+ onCleanup(() => io.disconnect())
168
+ })
169
+
170
+ createEffect(() => {
171
+ const dialog = dialogRef
172
+ if (!dialog) return
173
+ if (open()) {
174
+ if (!dialog.open) dialog.showModal()
175
+ if (!fetched) {
176
+ fetched = true
177
+ const url = activeModalSrc()
178
+ if (url) {
179
+ fetch(url)
180
+ .then((r) => r.blob())
181
+ .then((blob) => {
182
+ setMetaSizeTxt(`${(blob.size / 1024).toFixed(2)} KB`)
183
+ const mime = blob.type.split("/").pop()?.toUpperCase()
184
+ if (mime && mime !== "OCTET-STREAM") setMetaFormatTxt(mime)
185
+ })
186
+ .catch(() => setMetaSizeTxt("unavailable"))
187
+ }
188
+ }
189
+ } else if (dialog.open) {
190
+ dialog.close()
191
+ }
192
+ })
193
+
194
+ onMount(() => {
195
+ const dialog = dialogRef
196
+ if (dialog) {
197
+ const onClose = () => setOpen(false)
198
+ dialog.addEventListener("close", onClose)
199
+ onCleanup(() => dialog.removeEventListener("close", onClose))
200
+ }
201
+ })
202
+
203
+ const fallbackBox = () => (
204
+ <div class="flex w-full h-full min-h-40 items-center justify-center gap-2 rounded-lg bg-elevated text-muted">
205
+ <RLSIcon name="image" class="size-8" aria-hidden="true" />
206
+ <span class="text-sm">Image unavailable</span>
207
+ </div>
208
+ )
209
+
210
+ const triggerImage = () => {
211
+ if (status() === "error") return fallbackBox()
212
+
213
+ if (isResponsive) {
214
+ return (
215
+ <picture>
216
+ <source
217
+ media={`(max-width: ${breakpointVal - 1}px)`}
218
+ srcset={show() ? urlOf(props.mobileSrc) : undefined}
219
+ />
220
+ <source
221
+ media={`(min-width: ${breakpointVal}px)`}
222
+ srcset={show() ? urlOf(props.desktopSrc) : undefined}
223
+ />
224
+ <img
225
+ src={show() ? urlOf(props.desktopSrc) : undefined}
226
+ alt={alt}
227
+ loading={loading}
228
+ decoding={decoding}
229
+ class={resolvedClasses().triggerImage}
230
+ onError={handleError}
231
+ onLoad={handleLoad}
232
+ />
233
+ </picture>
234
+ )
235
+ }
236
+
237
+ if (hasColorMode) {
238
+ return (
239
+ <div
240
+ class="rla-image-color-mode w-full h-full relative overflow-hidden rounded-lg flex items-center justify-center"
241
+ style={`--rla-img-light: url('${lightUrl}'); --rla-img-dark: url('${darkUrl}');`}
242
+ >
243
+ <img
244
+ src={show() ? lightUrl : undefined}
245
+ alt={alt}
246
+ loading={loading}
247
+ decoding={decoding}
248
+ class={`${resolvedClasses().triggerImage} rla-img-light w-full h-full object-cover rounded-lg`}
249
+ onError={handleError}
250
+ onLoad={handleLoad}
251
+ />
252
+ <img
253
+ src={show() ? darkUrl : undefined}
254
+ alt={alt}
255
+ loading={loading}
256
+ decoding={decoding}
257
+ class={`${resolvedClasses().triggerImage} rla-img-dark w-full h-full object-cover rounded-lg hidden`}
258
+ />
259
+ </div>
260
+ )
261
+ }
262
+
263
+ return (
264
+ <img
265
+ src={show() ? activeUrl : undefined}
266
+ alt={alt}
267
+ loading={loading}
268
+ decoding={decoding}
269
+ width={imgWidth !== undefined ? String(imgWidth) : undefined}
270
+ height={imgHeight !== undefined ? String(imgHeight) : undefined}
271
+ sizes={props.sizes}
272
+ class={resolvedClasses().triggerImage}
273
+ onError={handleError}
274
+ onLoad={handleLoad}
275
+ />
276
+ )
277
+ }
278
+
279
+ const renderTrigger = () =>
280
+ props.trigger ? (
281
+ props.trigger
282
+ ) : isInteractive ? (
283
+ <button
284
+ type="button"
285
+ class={resolvedClasses().trigger}
286
+ data-image-trigger
287
+ onClick={openDialog}
288
+ >
289
+ {triggerImage()}
290
+ </button>
291
+ ) : (
292
+ <div class="overflow-hidden rounded-lg block">{triggerImage()}</div>
293
+ )
294
+
295
+ return (
296
+ <div
297
+ ref={(el) => (rootRef = el)}
298
+ class={resolvedClasses().root}
299
+ data-slot="root"
300
+ data-theme={props.theme}
301
+ style={props.theme ? `color-scheme: ${props.theme};` : undefined}
302
+ {...rest}
303
+ >
304
+ {isFigure ? (
305
+ <figure class={resolvedClasses().figure} data-slot="figure">
306
+ {renderTrigger()}
307
+ {props.caption && (
308
+ <figcaption class={resolvedClasses().caption} data-slot="caption">
309
+ {props.caption}
310
+ </figcaption>
311
+ )}
312
+ {props.captionSlot}
313
+ </figure>
314
+ ) : (
315
+ <>
316
+ {renderTrigger()}
317
+ {props.caption && (
318
+ <p class={resolvedClasses().caption} data-slot="caption">
319
+ {props.caption}
320
+ </p>
321
+ )}
322
+ {props.captionSlot}
323
+ </>
324
+ )}
325
+
326
+ {isInteractive && (
327
+ <dialog
328
+ ref={(el) => (dialogRef = el)}
329
+ class={resolvedClasses().dialog}
330
+ data-image-dialog
331
+ onClick={(e) => {
332
+ if (e.target === e.currentTarget) setOpen(false)
333
+ }}
334
+ >
335
+ <div class="flex flex-col bg-default text-highlighted max-h-[90vh]">
336
+ <div
337
+ class="flex-1 min-h-0 flex items-center justify-center p-4 overflow-hidden"
338
+ onClick={() => setZoom(!zoom())}
339
+ >
340
+ {status() === "error" ? (
341
+ fallbackBox()
342
+ ) : isResponsive ? (
343
+ <picture class="max-w-full max-h-[65vh] flex items-center justify-center overflow-hidden">
344
+ <source
345
+ media={`(max-width: ${breakpointVal - 1}px)`}
346
+ srcset={show() ? urlOf(props.mobileSrc) : undefined}
347
+ />
348
+ <source
349
+ media={`(min-width: ${breakpointVal}px)`}
350
+ srcset={show() ? urlOf(props.desktopSrc) : undefined}
351
+ />
352
+ <img
353
+ src={show() ? urlOf(props.desktopSrc) : undefined}
354
+ alt={alt}
355
+ class={`${resolvedClasses().image}${zoom() ? " max-w-none max-h-none" : ""}`}
356
+ data-modal-img
357
+ />
358
+ </picture>
359
+ ) : hasColorMode ? (
360
+ <div
361
+ class="rla-image-color-mode max-w-full max-h-[65vh] relative flex items-center justify-center overflow-hidden"
362
+ style={`--rla-img-light: url('${lightUrl}'); --rla-img-dark: url('${darkUrl}');`}
363
+ >
364
+ <img
365
+ src={show() ? lightUrl : undefined}
366
+ alt={alt}
367
+ class={`${resolvedClasses().image} rla-img-light${zoom() ? " max-w-none max-h-none" : ""}`}
368
+ data-modal-img
369
+ data-light-src={lightUrl}
370
+ data-dark-src={darkUrl}
371
+ />
372
+ <img
373
+ src={show() ? darkUrl : undefined}
374
+ alt={alt}
375
+ class={`${resolvedClasses().image} rla-img-dark hidden${zoom() ? " max-w-none max-h-none" : ""}`}
376
+ data-modal-img-dark
377
+ />
378
+ </div>
379
+ ) : (
380
+ <img
381
+ src={show() ? activeUrl : undefined}
382
+ alt={alt}
383
+ class={`${resolvedClasses().image}${zoom() ? " max-w-none max-h-none" : ""}`}
384
+ data-modal-img
385
+ />
386
+ )}
387
+ </div>
388
+
389
+ <hr class="border-default mx-4 mb-3 shrink-0" />
390
+
391
+ <div class={resolvedClasses().fileInfo}>
392
+ <div class="flex flex-col gap-xs text-sm">
393
+ {props.metaSource !== false && (
394
+ <div class="flex flex-row gap-xs items-center">
395
+ <RLSIcon name="image" class="size-4 shrink-0 text-muted" aria-hidden="true" />
396
+ <span class="text-muted">Source:</span>
397
+ <span class="text-dimmed truncate max-w-[40vw]">{fileName}</span>
398
+ </div>
399
+ )}
400
+ {props.metaFormat !== false && (
401
+ <div class="flex flex-row gap-xs items-center">
402
+ <RLSIcon
403
+ name="fileQuestion"
404
+ class="size-4 shrink-0 text-muted"
405
+ aria-hidden="true"
406
+ />
407
+ <span class="text-muted">Format:</span>
408
+ <span class="text-dimmed" data-metadata-format>
409
+ {metaFormatTxt()}
410
+ </span>
411
+ </div>
412
+ )}
413
+ {props.metaDimensions !== false && (
414
+ <div class="flex flex-row gap-xs items-center">
415
+ <RLSIcon
416
+ name="imageUpscale"
417
+ class="size-4 shrink-0 text-muted"
418
+ aria-hidden="true"
419
+ />
420
+ <span class="text-muted">Dimensions:</span>
421
+ <span class="text-dimmed">
422
+ {imgWidth || "?"} x {imgHeight || "?"}
423
+ </span>
424
+ </div>
425
+ )}
426
+ {props.metaSize !== false && (
427
+ <div class="flex flex-row gap-xs items-center">
428
+ <RLSIcon
429
+ name="hardDrive"
430
+ class="size-4 shrink-0 text-muted"
431
+ aria-hidden="true"
432
+ />
433
+ <span class="text-muted">Size:</span>
434
+ <span class="text-dimmed" data-metadata-size>
435
+ {metaSizeTxt()}
436
+ </span>
437
+ </div>
438
+ )}
439
+ {props.metaAlt !== false && (
440
+ <div class="flex flex-row gap-xs items-center">
441
+ <RLSIcon
442
+ name="alignLeft"
443
+ class="size-4 shrink-0 text-muted"
444
+ aria-hidden="true"
445
+ />
446
+ <span class="text-muted">Alt:</span>
447
+ <span
448
+ class={`text-dimmed truncate max-w-[30vw]${alt ? "" : " italic opacity-50"}`}
449
+ >
450
+ {alt || "none"}
451
+ </span>
452
+ </div>
453
+ )}
454
+ </div>
455
+
456
+ {props.metaDownload !== false && (
457
+ <button
458
+ type="button"
459
+ class={resolvedClasses().download}
460
+ data-image-download
461
+ onClick={handleDownload}
462
+ >
463
+ <RLSIcon name="download" class="size-4 shrink-0" aria-hidden="true" />
464
+ Download Original
465
+ </button>
466
+ )}
467
+ </div>
468
+
469
+ <form method="dialog" class="absolute top-2 right-2">
470
+ <button
471
+ type="submit"
472
+ class={resolvedClasses().closeButton}
473
+ aria-label="Close"
474
+ onClick={() => setOpen(false)}
475
+ >
476
+ <RLSIcon name="close" class="size-4" aria-hidden="true" />
477
+ </button>
478
+ </form>
479
+ </div>
480
+ </dialog>
481
+ )}
482
+
483
+ <style>{`
484
+ :root[data-theme="dark"] .rla-img-light,
485
+ [data-theme="dark"] .rla-img-light,
486
+ [data-theme="dark"] .rla-image-color-mode .rla-img-light,
487
+ .rla-image-color-mode[data-theme="dark"] .rla-img-light {
488
+ display: none !important;
489
+ }
490
+
491
+ :root[data-theme="dark"] .rla-img-dark,
492
+ [data-theme="dark"] .rla-img-dark,
493
+ [data-theme="dark"] .rla-image-color-mode .rla-img-dark,
494
+ .rla-image-color-mode[data-theme="dark"] .rla-img-dark {
495
+ display: block !important;
496
+ }
497
+
498
+ :root[data-theme="light"] .rla-img-light,
499
+ [data-theme="light"] .rla-img-light,
500
+ [data-theme="light"] .rla-image-color-mode .rla-img-light,
501
+ .rla-image-color-mode[data-theme="light"] .rla-img-light {
502
+ display: block !important;
503
+ }
504
+
505
+ :root[data-theme="light"] .rla-img-dark,
506
+ [data-theme="light"] .rla-img-dark,
507
+ [data-theme="light"] .rla-image-color-mode .rla-img-dark,
508
+ .rla-image-color-mode[data-theme="light"] .rla-img-dark {
509
+ display: none !important;
510
+ }
511
+
512
+ @media (prefers-color-scheme: dark) {
513
+ :root:not([data-theme="light"]) .rla-img-light {
514
+ display: none !important;
515
+ }
516
+ :root:not([data-theme="light"]) .rla-img-dark {
517
+ display: block !important;
518
+ }
519
+ :root:not([data-theme="light"]) [data-theme="light"] .rla-img-light {
520
+ display: block !important;
521
+ }
522
+ :root:not([data-theme="light"]) [data-theme="light"] .rla-img-dark {
523
+ display: none !important;
524
+ }
525
+ }
526
+
527
+ @supports (background-image: light-dark(none, none)) {
528
+ .rla-image-color-mode {
529
+ background-image: light-dark(var(--rla-img-light), var(--rla-img-dark));
530
+ background-size: cover;
531
+ background-position: center;
532
+ background-repeat: no-repeat;
533
+ }
534
+ .rla-image-color-mode > .rla-img-light,
535
+ .rla-image-color-mode > .rla-img-dark {
536
+ opacity: 0 !important;
537
+ visibility: hidden !important;
538
+ }
539
+ }
540
+ `}</style>
541
+ </div>
542
+ )
543
+ }
544
+
545
+ export default RLSImage
@@ -3,6 +3,8 @@ import { defineTheme } from "../../resolver"
3
3
  export const imageTheme = defineTheme("image", {
4
4
  slots: [
5
5
  "root",
6
+ "figure",
7
+ "caption",
6
8
  "trigger",
7
9
  "triggerImage",
8
10
  "dialog",
@@ -13,9 +15,11 @@ export const imageTheme = defineTheme("image", {
13
15
  ],
14
16
  base: {
15
17
  root: "relative inline-block group/image",
18
+ figure: "m-0 p-0 inline-flex flex-col max-w-full",
19
+ caption: "mt-2 text-xs text-muted text-center italic",
16
20
  trigger:
17
21
  "cursor-pointer overflow-hidden rounded-lg block hover:scale-[1.02] active:scale-95 transition-transform duration-300",
18
- triggerImage: "",
22
+ triggerImage: "max-w-full h-auto object-cover rounded-lg",
19
23
  dialog:
20
24
  "backdrop:bg-black/60 max-w-[98vw] sm:max-w-[95vw] w-fit max-h-[90vh] rounded-xl p-0 overflow-hidden border-0 m-auto",
21
25
  image: "object-contain max-w-full max-h-[65vh] rounded-lg",
@@ -25,6 +29,18 @@ export const imageTheme = defineTheme("image", {
25
29
  closeButton:
26
30
  "inline-flex items-center justify-center size-8 rounded-full bg-default/80 hover:bg-accented text-muted hover:text-highlighted transition-colors"
27
31
  },
28
- variants: {},
29
- defaultVariants: {}
32
+ variants: {
33
+ fit: {
34
+ "cover": { triggerImage: "object-cover", image: "object-cover" },
35
+ "contain": { triggerImage: "object-contain", image: "object-contain" },
36
+ "fill": { triggerImage: "object-fill", image: "object-fill" },
37
+ "none": { triggerImage: "object-none", image: "object-none" },
38
+ "scale-down": { triggerImage: "object-scale-down", image: "object-scale-down" },
39
+ "inside": { triggerImage: "object-contain", image: "object-contain" },
40
+ "outside": { triggerImage: "object-cover", image: "object-cover" }
41
+ }
42
+ },
43
+ defaultVariants: {
44
+ fit: "cover"
45
+ }
30
46
  })
@@ -9,15 +9,45 @@ export interface ImageMetadataInfo {
9
9
 
10
10
  export interface ImageProps {
11
11
  src?: string | ImageMetadata
12
+ lightSrc?: string | ImageMetadata
13
+ darkSrc?: string | ImageMetadata
12
14
  mobileSrc?: string | ImageMetadata
13
15
  desktopSrc?: string | ImageMetadata
14
16
  breakpoint?: number
15
17
  formats?: ("avif" | "webp" | "png" | "jpg" | "jpeg" | "svg")[]
18
+ widths?: (number | `${number}w` | `${number}x`)[] | number[]
19
+ sizes?: string
20
+ densities?: (number | `${number}x`)[]
16
21
  alt?: string
17
22
  height?: string | number
18
23
  width?: string | number
19
24
  loading?: "lazy" | "eager"
20
- fit?: "cover" | "contain" | "fill" | "inside" | "outside"
25
+ decoding?: "async" | "sync" | "auto"
26
+ fit?: "cover" | "contain" | "fill" | "none" | "scale-down" | "inside" | "outside"
27
+ /**
28
+ * Optional caption text rendered in a `<figcaption>`.
29
+ */
30
+ caption?: string
31
+ /**
32
+ * Whether to wrap the image in a semantic `<figure>` element. Defaults to `true` when `caption`
33
+ * is present, or `false` otherwise.
34
+ */
35
+ figure?: boolean
36
+ /**
37
+ * Whether clicking the image opens the interactive metadata dialog / lightbox. Defaults to
38
+ * `true`. Set to `false` for a lightweight, non-interactive image without modal or scripts.
39
+ */
40
+ lightbox?: boolean
41
+ /**
42
+ * Alias for `lightbox`.
43
+ */
44
+ interactive?: boolean
45
+ /**
46
+ * Theme override — pins the image rendering to `"light"` or `"dark"` color-scheme context,
47
+ * causing `light-dark()` variables to resolve to the correct scheme regardless of the global page
48
+ * mode.
49
+ */
50
+ theme?: "light" | "dark"
21
51
  metadata?: ImageMetadataInfo
22
52
  open?: boolean
23
53
  metaSource?: boolean