@rimelight/ui 0.0.50 → 0.0.52

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.
@@ -9,7 +9,6 @@ import {
9
9
  type Component,
10
10
  type JSX
11
11
  } from "solid-js"
12
- import { Portal } from "solid-js/web"
13
12
  import type { DropdownMenuProps, DropdownMenuItem } from "./dropdown-menu"
14
13
  import { dropdownMenuTheme } from "./dropdown-menu.theme"
15
14
  import RLSIcon from "../icon/RLSIcon"
@@ -60,6 +59,7 @@ const RLSDropdownMenu: Component<RLSDropdownMenuProps> = (allProps) => {
60
59
  }
61
60
 
62
61
  onMount(() => {
62
+ if (typeof window === "undefined" || typeof document === "undefined") return
63
63
  const handleOutsideClick = (e: MouseEvent) => {
64
64
  const target = e.target as Node
65
65
  if (
@@ -102,13 +102,17 @@ const RLSDropdownMenu: Component<RLSDropdownMenuProps> = (allProps) => {
102
102
 
103
103
  return (
104
104
  <div
105
- ref={(el) => (menuContainer = el)}
105
+ ref={(el) => {
106
+ menuContainer = el
107
+ }}
106
108
  class={resolvedClasses().root}
107
109
  data-slot="root"
108
110
  {...rest}
109
111
  >
110
112
  <button
111
- ref={(el) => (triggerBtn = el)}
113
+ ref={(el) => {
114
+ triggerBtn = el
115
+ }}
112
116
  type="button"
113
117
  disabled={props.disabled}
114
118
  onClick={(e) => {
@@ -117,174 +121,191 @@ const RLSDropdownMenu: Component<RLSDropdownMenuProps> = (allProps) => {
117
121
  if (next) updatePosition()
118
122
  setIsOpen(next)
119
123
  }}
120
- class={resolvedClasses().trigger}
124
+ class={`${resolvedClasses().trigger} ${!props.label && !props.children && !props.trigger ? "aspect-square !px-1.5 !py-1.5 inline-flex items-center justify-center shrink-0" : ""}`}
121
125
  data-slot="trigger"
122
126
  >
123
- {props.trigger ? (
124
- props.trigger
125
- ) : props.children ? (
126
- props.children
127
- ) : (
128
- <>
129
- {props.icon && <RLSIcon name={props.icon} size="sm" class="shrink-0" />}
130
- {props.label && <span>{props.label}</span>}
131
- {!props.icon && !props.label && <RLSIcon name="ellipsisVertical" size="sm" />}
132
- </>
133
- )}
127
+ <Show
128
+ when={props.trigger}
129
+ fallback={
130
+ <Show
131
+ when={props.children}
132
+ fallback={
133
+ <Show
134
+ when={props.icon}
135
+ fallback={
136
+ <Show
137
+ when={props.label}
138
+ fallback={<span class="i-lucide-ellipsis-vertical size-4" />}
139
+ >
140
+ <span>{props.label}</span>
141
+ </Show>
142
+ }
143
+ >
144
+ <RLSIcon name={props.icon!} size="sm" class="shrink-0" />
145
+ <Show when={props.label}>
146
+ <span>{props.label}</span>
147
+ </Show>
148
+ </Show>
149
+ }
150
+ >
151
+ {props.children}
152
+ </Show>
153
+ }
154
+ >
155
+ {props.trigger}
156
+ </Show>
134
157
  </button>
135
158
 
136
159
  <Show when={isOpen()}>
137
- <Portal>
138
- <div
139
- ref={(el) => (menuContentRef = el)}
140
- class={resolvedClasses().content}
141
- style={{
142
- "position": "fixed",
143
- "z-index": "99999",
144
- "top": `${coords().top}px`,
145
- ...(coords().right !== undefined
146
- ? { right: `${coords().right}px`, left: "auto" }
147
- : {}),
148
- ...(coords().left !== undefined ? { left: `${coords().left}px` } : {})
149
- }}
150
- data-slot="content"
151
- role="menu"
152
- >
153
- {props.arrow && <div class={resolvedClasses().arrow} />}
154
- <div class={resolvedClasses().viewport}>
155
- <For each={itemGroups()}>
156
- {(group, groupIdx) => (
157
- <div class={resolvedClasses().group}>
158
- {groupIdx() > 0 && <div class={resolvedClasses().separator} />}
159
- <For each={group}>
160
- {(item) => {
161
- if (item.type === "separator") {
162
- return <div class={resolvedClasses().separator} />
163
- }
164
- if (item.type === "label") {
165
- return <div class={resolvedClasses().label}>{item.label}</div>
166
- }
167
-
168
- const itemColor = () =>
169
- item.color === "error"
170
- ? "text-destructive hover:bg-destructive/10"
171
- : item.color === "primary"
172
- ? "text-primary hover:bg-primary/10"
173
- : ""
160
+ <div
161
+ ref={(el) => {
162
+ menuContentRef = el
163
+ }}
164
+ class={resolvedClasses().content}
165
+ style={{
166
+ "position": "fixed",
167
+ "z-index": "99999",
168
+ "top": `${coords().top}px`,
169
+ ...(coords().right !== undefined ? { right: `${coords().right}px`, left: "auto" } : {}),
170
+ ...(coords().left !== undefined ? { left: `${coords().left}px` } : {})
171
+ }}
172
+ data-slot="content"
173
+ role="menu"
174
+ >
175
+ {props.arrow && <div class={resolvedClasses().arrow} />}
176
+ <div class={resolvedClasses().viewport}>
177
+ <For each={itemGroups()}>
178
+ {(group, groupIdx) => (
179
+ <div class={resolvedClasses().group}>
180
+ {groupIdx() > 0 && <div class={resolvedClasses().separator} />}
181
+ <For each={group}>
182
+ {(item) => {
183
+ if (item.type === "separator") {
184
+ return <div class={resolvedClasses().separator} />
185
+ }
186
+ if (item.type === "label") {
187
+ return <div class={resolvedClasses().label}>{item.label}</div>
188
+ }
174
189
 
175
- const renderLeadingContent = () => {
176
- if (item.avatar) {
177
- return (
178
- <img
179
- src={item.avatar.src}
180
- alt={item.avatar.alt || item.label || ""}
181
- class={resolvedClasses().itemLeadingAvatar}
182
- />
183
- )
184
- }
185
- if (item.icon) {
186
- return (
187
- <RLSIcon
188
- name={item.icon}
189
- size="sm"
190
- class={resolvedClasses().itemLeadingIcon}
191
- />
192
- )
193
- }
194
- return null
195
- }
190
+ const itemColor = () =>
191
+ item.color === "error"
192
+ ? "text-destructive hover:bg-destructive/10"
193
+ : item.color === "primary"
194
+ ? "text-primary hover:bg-primary/10"
195
+ : ""
196
196
 
197
- const renderTrailingContent = () => {
198
- if (!item.shortcut && (!item.kbds || item.kbds.length === 0)) return null
197
+ const renderLeadingContent = () => {
198
+ if (item.avatar) {
199
199
  return (
200
- <span class={resolvedClasses().itemTrailing}>
201
- {item.shortcut && (
202
- <span class={resolvedClasses().shortcut}>{item.shortcut}</span>
203
- )}
204
- {item.kbds && (
205
- <For each={item.kbds}>
206
- {(kbd) => (
207
- <kbd class={resolvedClasses().itemTrailingKbds}>{kbd}</kbd>
208
- )}
209
- </For>
210
- )}
211
- </span>
200
+ <img
201
+ src={item.avatar.src}
202
+ alt={item.avatar.alt || item.label || ""}
203
+ class={resolvedClasses().itemLeadingAvatar}
204
+ />
212
205
  )
213
206
  }
214
-
215
- if (item.to) {
207
+ if (item.icon) {
216
208
  return (
217
- <a
218
- href={item.to}
219
- target={item.target}
220
- class={`${resolvedClasses().item} ${itemColor()} ${item.class || ""}`}
221
- data-slot="item"
222
- role="menuitem"
223
- onClick={(e) => {
224
- setIsOpen(false)
225
- item.onSelect?.(e)
226
- }}
227
- >
228
- {renderLeadingContent()}
229
- <span class={resolvedClasses().itemLabel}>{item.label}</span>
230
- {renderTrailingContent()}
231
- </a>
209
+ <RLSIcon
210
+ name={item.icon}
211
+ size="sm"
212
+ class={resolvedClasses().itemLeadingIcon}
213
+ />
232
214
  )
233
215
  }
216
+ return null
217
+ }
234
218
 
235
- if (item.type === "checkbox") {
236
- const isChecked = () => !!item.checked
237
- return (
238
- <button
239
- type="button"
240
- disabled={item.disabled}
241
- class={`${resolvedClasses().item} ${itemColor()} ${item.class || ""}`}
242
- data-slot="item"
243
- role="menuitemcheckbox"
244
- aria-checked={isChecked()}
245
- onClick={(e) => {
246
- const next = !isChecked()
247
- item.onUpdateChecked?.(next)
248
- item.onSelect?.(e)
249
- }}
250
- >
251
- <span class="shrink-0 size-4 flex items-center justify-center border border-default rounded bg-default">
252
- <Show when={isChecked()}>
253
- <RLSIcon name="check" size="xs" class="text-primary" />
254
- </Show>
255
- </span>
256
- {renderLeadingContent()}
257
- <span class={resolvedClasses().itemLabel}>{item.label}</span>
258
- {renderTrailingContent()}
259
- </button>
260
- )
261
- }
219
+ const renderTrailingContent = () => {
220
+ if (!item.shortcut && (!item.kbds || item.kbds.length === 0)) return null
221
+ return (
222
+ <span class={resolvedClasses().itemTrailing}>
223
+ {item.shortcut && (
224
+ <span class={resolvedClasses().shortcut}>{item.shortcut}</span>
225
+ )}
226
+ {item.kbds && (
227
+ <For each={item.kbds}>
228
+ {(kbd) => (
229
+ <kbd class={resolvedClasses().itemTrailingKbds}>{kbd}</kbd>
230
+ )}
231
+ </For>
232
+ )}
233
+ </span>
234
+ )
235
+ }
236
+
237
+ if (item.to) {
238
+ return (
239
+ <a
240
+ href={item.to}
241
+ target={item.target}
242
+ class={`${resolvedClasses().item} ${itemColor()} ${item.class || ""}`}
243
+ data-slot="item"
244
+ role="menuitem"
245
+ onClick={(e) => {
246
+ setIsOpen(false)
247
+ item.onSelect?.(e)
248
+ }}
249
+ >
250
+ {renderLeadingContent()}
251
+ <span class={resolvedClasses().itemLabel}>{item.label}</span>
252
+ {renderTrailingContent()}
253
+ </a>
254
+ )
255
+ }
262
256
 
257
+ if (item.type === "checkbox") {
258
+ const isChecked = () => !!item.checked
263
259
  return (
264
260
  <button
265
261
  type="button"
266
262
  disabled={item.disabled}
267
263
  class={`${resolvedClasses().item} ${itemColor()} ${item.class || ""}`}
268
264
  data-slot="item"
269
- role="menuitem"
265
+ role="menuitemcheckbox"
266
+ aria-checked={isChecked()}
270
267
  onClick={(e) => {
271
- setIsOpen(false)
268
+ const next = !isChecked()
269
+ item.onUpdateChecked?.(next)
272
270
  item.onSelect?.(e)
273
271
  }}
274
272
  >
273
+ <span class="shrink-0 size-4 flex items-center justify-center border border-default rounded bg-default">
274
+ <Show when={isChecked()}>
275
+ <RLSIcon name="check" size="xs" class="text-primary" />
276
+ </Show>
277
+ </span>
275
278
  {renderLeadingContent()}
276
279
  <span class={resolvedClasses().itemLabel}>{item.label}</span>
277
280
  {renderTrailingContent()}
278
281
  </button>
279
282
  )
280
- }}
281
- </For>
282
- </div>
283
- )}
284
- </For>
285
- </div>
283
+ }
284
+
285
+ return (
286
+ <button
287
+ type="button"
288
+ disabled={item.disabled}
289
+ class={`${resolvedClasses().item} ${itemColor()} ${item.class || ""}`}
290
+ data-slot="item"
291
+ role="menuitem"
292
+ onClick={(e) => {
293
+ setIsOpen(false)
294
+ item.onSelect?.(e)
295
+ }}
296
+ >
297
+ {renderLeadingContent()}
298
+ <span class={resolvedClasses().itemLabel}>{item.label}</span>
299
+ {renderTrailingContent()}
300
+ </button>
301
+ )
302
+ }}
303
+ </For>
304
+ </div>
305
+ )}
306
+ </For>
286
307
  </div>
287
- </Portal>
308
+ </div>
288
309
  </Show>
289
310
  </div>
290
311
  )
@@ -27,13 +27,8 @@ const RLSHead: Component<RLSHeadProps> = (_allProps) => {
27
27
  if (localStorage.getItem("theme") === "system") applyTheme()
28
28
  }
29
29
 
30
- if (typeof mql.addEventListener === "function") {
31
- mql.addEventListener("change", onChange)
32
- onCleanup(() => mql.removeEventListener("change", onChange))
33
- } else {
34
- mql.addListener(onChange)
35
- onCleanup(() => mql.removeListener(onChange))
36
- }
30
+ mql.addEventListener("change", onChange)
31
+ onCleanup(() => mql.removeEventListener("change", onChange))
37
32
  })
38
33
 
39
34
  return null
@@ -202,14 +202,13 @@ const popoverId = `rla-popover-${Math.random().toString(36).substring(2, 9)}`
202
202
  const popoverId = container.querySelector<HTMLElement>('[data-slot="content"]')?.id;
203
203
  if (!popoverId) return;
204
204
 
205
- const panel = document.getElementById(popoverId) as HTMLElement & { hidePopover?: () => void; showPopover?: () => void } | null;
205
+ const panel = document.getElementById(popoverId) as HTMLElement & { hidePopover?: () => void; showPopover?: () => void; togglePopover?: () => void } | null;
206
206
  if (!panel || typeof panel.showPopover !== 'function') return;
207
207
 
208
208
  // For slot-based triggers (not a <button popovertarget>), wire click manually
209
209
  const trigger = container.querySelector<HTMLElement>('[data-slot="trigger"]');
210
210
  if (trigger && !trigger.querySelector('[popovertarget]')) {
211
211
  trigger.addEventListener('click', () => {
212
- // @ts-ignore
213
212
  panel.togglePopover?.();
214
213
  });
215
214
  }
@@ -172,7 +172,7 @@ const currentLabel =
172
172
  <script>
173
173
  document.addEventListener("change", (event) => {
174
174
  const target = event.target as HTMLSelectElement | null
175
- if (!target || target.dataset.slot !== "select") return
175
+ if (!target || target.dataset["slot"] !== "select") return
176
176
  const root = target.closest('[data-slot="root"]')
177
177
  const label = root?.querySelector('[data-slot="label"]')
178
178
  if (label) {