@joewinke/jatui 0.1.0

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 (62) hide show
  1. package/package.json +46 -0
  2. package/src/lib/components/AudioWaveform.svelte +694 -0
  3. package/src/lib/components/AvailabilityModal.svelte +173 -0
  4. package/src/lib/components/Badge.svelte +38 -0
  5. package/src/lib/components/BookingForm.svelte +276 -0
  6. package/src/lib/components/Button.svelte +72 -0
  7. package/src/lib/components/CalendarPicker.svelte +284 -0
  8. package/src/lib/components/Card.svelte +67 -0
  9. package/src/lib/components/CharacterCounter.svelte +82 -0
  10. package/src/lib/components/ChipInput.svelte +596 -0
  11. package/src/lib/components/ColorSelector.svelte +163 -0
  12. package/src/lib/components/ConfirmModal.svelte +75 -0
  13. package/src/lib/components/CountdownTimer.svelte +94 -0
  14. package/src/lib/components/DateRangePicker.svelte +192 -0
  15. package/src/lib/components/Drawer.svelte +110 -0
  16. package/src/lib/components/FilterDropdown.svelte +202 -0
  17. package/src/lib/components/ImageUpload.svelte +97 -0
  18. package/src/lib/components/InlineEdit.svelte +283 -0
  19. package/src/lib/components/LazyImage.svelte +122 -0
  20. package/src/lib/components/LoadingSpinner.svelte +102 -0
  21. package/src/lib/components/Modal.svelte +208 -0
  22. package/src/lib/components/PhoneInput.svelte +92 -0
  23. package/src/lib/components/ResizableDivider.svelte +305 -0
  24. package/src/lib/components/ResizablePanel.svelte +302 -0
  25. package/src/lib/components/SearchDropdown.svelte +341 -0
  26. package/src/lib/components/SelectInput.svelte +215 -0
  27. package/src/lib/components/SignaturePad.svelte +171 -0
  28. package/src/lib/components/SortDropdown.svelte +148 -0
  29. package/src/lib/components/Sparkline.svelte +107 -0
  30. package/src/lib/components/SpeechForm.svelte +114 -0
  31. package/src/lib/components/StatusBadge.svelte +155 -0
  32. package/src/lib/components/TextArea.svelte +143 -0
  33. package/src/lib/components/TextInput.svelte +108 -0
  34. package/src/lib/components/ThemeSelector.svelte +195 -0
  35. package/src/lib/components/TimeSlotPicker.svelte +162 -0
  36. package/src/lib/components/VoicePlayer.svelte +420 -0
  37. package/src/lib/components/messaging/Avatar.svelte +81 -0
  38. package/src/lib/components/messaging/ChannelInfoModal.svelte +163 -0
  39. package/src/lib/components/messaging/ChannelList.svelte +107 -0
  40. package/src/lib/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
  41. package/src/lib/components/messaging/ChannelMembersModal.svelte +182 -0
  42. package/src/lib/components/messaging/CreateChannelModal.svelte +190 -0
  43. package/src/lib/components/messaging/DirectMessageList.svelte +145 -0
  44. package/src/lib/components/messaging/EmojiSelector.svelte +260 -0
  45. package/src/lib/components/messaging/MentionAutocomplete.svelte +193 -0
  46. package/src/lib/components/messaging/MessageAttachment.svelte +270 -0
  47. package/src/lib/components/messaging/MessageAttachmentUpload.svelte +243 -0
  48. package/src/lib/components/messaging/MessageInput.svelte +451 -0
  49. package/src/lib/components/messaging/MessageItem.svelte +338 -0
  50. package/src/lib/components/messaging/MessageThread.svelte +306 -0
  51. package/src/lib/components/messaging/NotificationSettingsModal.svelte +234 -0
  52. package/src/lib/components/messaging/QuotedMessageDisplay.svelte +118 -0
  53. package/src/lib/components/messaging/StartDMModal.svelte +100 -0
  54. package/src/lib/components/messaging/ThreadPanel.svelte +153 -0
  55. package/src/lib/index.ts +185 -0
  56. package/src/lib/types/booking.ts +143 -0
  57. package/src/lib/types/messaging.ts +459 -0
  58. package/src/lib/utils/currency.ts +20 -0
  59. package/src/lib/utils/daisyuiColors.ts +243 -0
  60. package/src/lib/utils/dateFormatters.ts +153 -0
  61. package/src/lib/utils/mentionParser.ts +188 -0
  62. package/src/lib/utils/phoneFormat.ts +74 -0
@@ -0,0 +1,302 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte"
3
+ import { onMount } from "svelte"
4
+
5
+ let {
6
+ width = 320,
7
+ minWidth = 280,
8
+ maxWidth = 800,
9
+ position = "right",
10
+ enableResize = true,
11
+ enableDragToSwap = true,
12
+ color = "neutral",
13
+ animateWidth = false,
14
+ onResize,
15
+ onResizeEnd,
16
+ onSwap,
17
+ children,
18
+ } = $props<{
19
+ width?: number
20
+ minWidth?: number
21
+ maxWidth?: number
22
+ position?: "left" | "right"
23
+ enableResize?: boolean
24
+ enableDragToSwap?: boolean
25
+ color?: string
26
+ animateWidth?: boolean
27
+ onResize?: (width: number) => void
28
+ onResizeEnd?: (width: number) => void
29
+ onSwap?: (newPosition: "left" | "right") => void
30
+ children?: Snippet
31
+ }>()
32
+
33
+ let panel = $state<HTMLElement | null>(null)
34
+ let resizeSide = $state<"left" | "right" | null>(null)
35
+ let startWidth = $state(0)
36
+ let startX = $state(0)
37
+ let isResizing = $state(false)
38
+ let isDragging = $state(false)
39
+ let dragIndicatorPosition = $state<{ x: number; y: number } | null>(null)
40
+
41
+ onMount(() => {
42
+ if (panel) {
43
+ width = panel.offsetWidth
44
+ }
45
+ })
46
+
47
+ function handleResizeStart(e: MouseEvent | TouchEvent, side: "left" | "right") {
48
+ if (!enableResize) return
49
+
50
+ isResizing = true
51
+ startWidth = width
52
+ resizeSide = side
53
+
54
+ if ("touches" in e) {
55
+ startX = e.touches[0].clientX
56
+ } else {
57
+ startX = e.clientX
58
+ }
59
+
60
+ window.addEventListener("mousemove", handleResize)
61
+ window.addEventListener("touchmove", handleResize, { passive: false })
62
+ window.addEventListener("mouseup", handleResizeEnd)
63
+ window.addEventListener("touchend", handleResizeEnd)
64
+
65
+ document.body.style.userSelect = "none"
66
+ e.stopPropagation()
67
+ }
68
+
69
+ function handleResize(e: MouseEvent | TouchEvent) {
70
+ if (!isResizing || !resizeSide) return
71
+
72
+ if ("touches" in e) {
73
+ e.preventDefault()
74
+ const currentX = e.touches[0].clientX
75
+ const diff = resizeSide === "right" ? startX - currentX : currentX - startX
76
+ width = Math.max(minWidth, Math.min(maxWidth, startWidth + diff))
77
+ } else {
78
+ const currentX = e.clientX
79
+ const diff = resizeSide === "right" ? startX - currentX : currentX - startX
80
+ width = Math.max(minWidth, Math.min(maxWidth, startWidth + diff))
81
+ }
82
+
83
+ onResize?.(width)
84
+ }
85
+
86
+ function handleResizeEnd() {
87
+ if (!isResizing) return
88
+
89
+ isResizing = false
90
+ resizeSide = null
91
+
92
+ window.removeEventListener("mousemove", handleResize)
93
+ window.removeEventListener("touchmove", handleResize)
94
+ window.removeEventListener("mouseup", handleResizeEnd)
95
+ window.removeEventListener("touchend", handleResizeEnd)
96
+
97
+ document.body.style.userSelect = ""
98
+ onResizeEnd?.(width)
99
+ }
100
+
101
+ function handleDragStart(e: MouseEvent | TouchEvent) {
102
+ if (!enableDragToSwap) return
103
+
104
+ isDragging = true
105
+
106
+ if ("touches" in e) {
107
+ dragIndicatorPosition = { x: e.touches[0].clientX, y: e.touches[0].clientY }
108
+ } else {
109
+ dragIndicatorPosition = { x: e.clientX, y: e.clientY }
110
+ }
111
+
112
+ window.addEventListener("mousemove", handleDrag)
113
+ window.addEventListener("touchmove", handleDrag, { passive: false })
114
+ window.addEventListener("mouseup", handleDragEnd)
115
+ window.addEventListener("touchend", handleDragEnd)
116
+
117
+ e.stopPropagation()
118
+ }
119
+
120
+ function handleDrag(e: MouseEvent | TouchEvent) {
121
+ if (!isDragging) return
122
+
123
+ if ("touches" in e) {
124
+ e.preventDefault()
125
+ dragIndicatorPosition = { x: e.touches[0].clientX, y: e.touches[0].clientY }
126
+ } else {
127
+ dragIndicatorPosition = { x: e.clientX, y: e.clientY }
128
+ }
129
+ }
130
+
131
+ function handleDragEnd(e: MouseEvent | TouchEvent) {
132
+ if (!isDragging) return
133
+
134
+ isDragging = false
135
+ dragIndicatorPosition = null
136
+
137
+ window.removeEventListener("mousemove", handleDrag)
138
+ window.removeEventListener("touchmove", handleDrag)
139
+ window.removeEventListener("mouseup", handleDragEnd)
140
+ window.removeEventListener("touchend", handleDragEnd)
141
+
142
+ let clientX: number
143
+ if ("changedTouches" in e) {
144
+ clientX = e.changedTouches[0].clientX
145
+ } else {
146
+ clientX = e.clientX
147
+ }
148
+
149
+ const viewportWidth = window.innerWidth
150
+ const threshold = viewportWidth * 0.4
151
+
152
+ const shouldSwap =
153
+ (position === "right" && clientX < threshold) ||
154
+ (position === "left" && clientX > viewportWidth - threshold)
155
+
156
+ if (shouldSwap) {
157
+ onSwap?.(position === "right" ? "left" : "right")
158
+ }
159
+ }
160
+ </script>
161
+
162
+ <div
163
+ bind:this={panel}
164
+ class={`resizable-panel border-l border-r border-${color}/30 ${isResizing ? " resizing" : ""}${isDragging ? " dragging" : ""}${animateWidth && !isResizing ? " animate-width" : ""}`}
165
+ style="width: {width}px;"
166
+ >
167
+ {#if enableResize}
168
+ <div
169
+ class="resize-handle left"
170
+ style="right: 0;"
171
+ onmousedown={(e) => handleResizeStart(e, "left")}
172
+ ontouchstart={(e) => handleResizeStart(e, "left")}
173
+ >
174
+ <div class="handle-indicator"></div>
175
+ </div>
176
+ <div
177
+ class="resize-handle right"
178
+ style="left: 0;"
179
+ onmousedown={(e) => handleResizeStart(e, "right")}
180
+ ontouchstart={(e) => handleResizeStart(e, "right")}
181
+ >
182
+ <div class="handle-indicator"></div>
183
+ </div>
184
+ {/if}
185
+
186
+ {#if enableDragToSwap}
187
+ <div
188
+ class="drag-handle"
189
+ onmousedown={handleDragStart}
190
+ ontouchstart={handleDragStart}
191
+ >
192
+ <div class="drag-indicator"></div>
193
+ </div>
194
+ {/if}
195
+
196
+ <div class="panel-content min-w-0">
197
+ {#if children}
198
+ {@render children()}
199
+ {/if}
200
+ </div>
201
+ </div>
202
+
203
+ {#if isDragging && dragIndicatorPosition}
204
+ <div
205
+ class="drag-preview"
206
+ style="left: {dragIndicatorPosition.x}px; top: {dragIndicatorPosition.y}px;"
207
+ >
208
+ <div class="drag-preview-inner"></div>
209
+ </div>
210
+ {/if}
211
+
212
+ <style>
213
+ .resizable-panel {
214
+ position: relative;
215
+ height: 100%;
216
+ overflow: hidden;
217
+ transition: box-shadow 0.2s ease;
218
+ }
219
+
220
+ .resizable-panel.animate-width {
221
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
222
+ }
223
+
224
+ .resizable-panel.resizing { user-select: none; }
225
+
226
+ .resize-handle {
227
+ position: absolute;
228
+ width: 10px;
229
+ height: 100%;
230
+ cursor: col-resize;
231
+ z-index: 100;
232
+ }
233
+
234
+ .resize-handle.right { left: 0; width: 15px; }
235
+ .resize-handle.left { right: 0; width: 15px; }
236
+
237
+ .handle-indicator {
238
+ position: absolute;
239
+ left: 50%;
240
+ top: 0;
241
+ bottom: 0;
242
+ width: 6px;
243
+ border-radius: 2px;
244
+ transform: translateX(-50%);
245
+ background-color: transparent;
246
+ transition: background-color 0.2s ease;
247
+ }
248
+
249
+ .resize-handle:hover .handle-indicator,
250
+ .resizing .handle-indicator {
251
+ background-color: var(--color-accent);
252
+ }
253
+
254
+ .drag-handle {
255
+ position: absolute;
256
+ top: 0;
257
+ left: 0;
258
+ right: 0;
259
+ height: 10px;
260
+ cursor: move;
261
+ z-index: 10;
262
+ display: flex;
263
+ justify-content: center;
264
+ align-items: center;
265
+ }
266
+
267
+ .drag-indicator {
268
+ width: 30px;
269
+ height: 4px;
270
+ border-radius: 2px;
271
+ background-color: var(--color-base-300, #cbd5e1);
272
+ transition: background-color 0.2s ease;
273
+ }
274
+
275
+ .drag-handle:hover .drag-indicator,
276
+ .dragging .drag-indicator {
277
+ background-color: var(--color-primary, #3b82f6);
278
+ }
279
+
280
+ .panel-content {
281
+ height: 100%;
282
+ overflow: hidden;
283
+ display: flex;
284
+ flex-direction: column;
285
+ }
286
+
287
+ .drag-preview {
288
+ position: fixed;
289
+ pointer-events: none;
290
+ z-index: 9999;
291
+ transform: translate(-50%, -50%);
292
+ }
293
+
294
+ .drag-preview-inner {
295
+ width: 100px;
296
+ height: 60px;
297
+ background-color: var(--color-primary, #3b82f6);
298
+ opacity: 0.3;
299
+ border-radius: 4px;
300
+ border: 2px solid var(--color-primary, #3b82f6);
301
+ }
302
+ </style>
@@ -0,0 +1,341 @@
1
+ <script module lang="ts">
2
+ export type SearchDropdownOption = {
3
+ value: string;
4
+ label: string;
5
+ icon?: string;
6
+ };
7
+
8
+ export type SearchDropdownGroup = {
9
+ label: string;
10
+ options: SearchDropdownOption[];
11
+ };
12
+ </script>
13
+
14
+ <script lang="ts">
15
+ import { tick } from 'svelte';
16
+ import { slide } from 'svelte/transition';
17
+
18
+ let {
19
+ value = '',
20
+ groups = [],
21
+ placeholder = 'Filter...',
22
+ disabled = false,
23
+ displayValue,
24
+ onChange,
25
+ }: {
26
+ value: string;
27
+ groups: SearchDropdownGroup[];
28
+ placeholder?: string;
29
+ disabled?: boolean;
30
+ displayValue?: string;
31
+ onChange: (value: string) => void;
32
+ } = $props();
33
+
34
+ let open = $state(false);
35
+ let searchQuery = $state('');
36
+ let searchInput: HTMLInputElement | undefined;
37
+ let containerRef: HTMLDivElement | undefined;
38
+
39
+ // Find the currently selected option across all groups
40
+ const selectedOption = $derived.by(() => {
41
+ for (const group of groups) {
42
+ const found = group.options.find(o => o.value === value);
43
+ if (found) return found;
44
+ }
45
+ return null;
46
+ });
47
+
48
+ const triggerLabel = $derived(displayValue || selectedOption?.label || value || placeholder);
49
+ const triggerIcon = $derived(selectedOption?.icon || '');
50
+
51
+ const filteredGroups = $derived.by(() => {
52
+ if (!searchQuery.trim()) return groups;
53
+ const q = searchQuery.toLowerCase();
54
+ const result: SearchDropdownGroup[] = [];
55
+ for (const group of groups) {
56
+ const filtered = group.options.filter(o =>
57
+ o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q) || (o.icon && o.icon.toLowerCase().includes(q))
58
+ );
59
+ if (filtered.length > 0) result.push({ label: group.label, options: filtered });
60
+ }
61
+ return result;
62
+ });
63
+
64
+ function select(optionValue: string) {
65
+ open = false;
66
+ searchQuery = '';
67
+ onChange(optionValue);
68
+ }
69
+
70
+ function handleClickOutside(e: MouseEvent) {
71
+ if (containerRef && !containerRef.contains(e.target as Node)) {
72
+ open = false;
73
+ searchQuery = '';
74
+ }
75
+ }
76
+
77
+ $effect(() => {
78
+ if (open) {
79
+ document.addEventListener('mousedown', handleClickOutside);
80
+ tick().then(() => searchInput?.focus());
81
+ return () => document.removeEventListener('mousedown', handleClickOutside);
82
+ }
83
+ });
84
+ </script>
85
+
86
+ <div class="search-dropdown" bind:this={containerRef}>
87
+ <button
88
+ type="button"
89
+ class="sd-trigger"
90
+ class:sd-disabled={disabled}
91
+ onclick={() => { if (!disabled) open = !open; }}
92
+ {disabled}
93
+ >
94
+ <span class="sd-trigger-label">
95
+ {#if triggerIcon}<span class="sd-trigger-icon">{triggerIcon}</span>{/if}
96
+ <span class="truncate">{triggerLabel}</span>
97
+ </span>
98
+ <svg class="sd-chevron" class:sd-chevron-open={open} fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
99
+ <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
100
+ </svg>
101
+ </button>
102
+
103
+ {#if open}
104
+ <div
105
+ class="sd-panel"
106
+ transition:slide={{ duration: 120 }}
107
+ >
108
+ <!-- Search input -->
109
+ <div class="sd-search">
110
+ <div class="sd-search-inner">
111
+ <svg class="sd-search-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
112
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
113
+ </svg>
114
+ <input
115
+ bind:this={searchInput}
116
+ bind:value={searchQuery}
117
+ onkeydown={(e) => {
118
+ if (e.key === 'Escape') { e.stopPropagation(); open = false; searchQuery = ''; }
119
+ }}
120
+ type="text"
121
+ {placeholder}
122
+ class="sd-search-input"
123
+ autocomplete="off"
124
+ />
125
+ {#if searchQuery}
126
+ <button type="button" onclick={() => { searchQuery = ''; searchInput?.focus(); }} class="sd-search-clear">
127
+ <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
128
+ </button>
129
+ {/if}
130
+ </div>
131
+ </div>
132
+
133
+ <!-- Options list -->
134
+ <ul class="sd-list">
135
+ {#if filteredGroups.length > 0}
136
+ {#each filteredGroups as group}
137
+ <li class="sd-group-label">
138
+ <span>{group.label}</span>
139
+ </li>
140
+ {#each group.options as option}
141
+ <li>
142
+ <button
143
+ type="button"
144
+ onclick={() => select(option.value)}
145
+ class="sd-option"
146
+ class:sd-option-selected={value === option.value}
147
+ >
148
+ {#if option.icon}<span class="sd-option-icon">{option.icon}</span>{/if}
149
+ <span class="truncate">{option.label}</span>
150
+ {#if value === option.value}
151
+ <svg class="sd-check" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" /></svg>
152
+ {/if}
153
+ </button>
154
+ </li>
155
+ {/each}
156
+ {/each}
157
+ {:else}
158
+ <li class="sd-empty">No matches for "{searchQuery}"</li>
159
+ {/if}
160
+ </ul>
161
+ </div>
162
+ {/if}
163
+ </div>
164
+
165
+ <style>
166
+ .search-dropdown {
167
+ position: relative;
168
+ }
169
+
170
+ /* Trigger button */
171
+ .sd-trigger {
172
+ width: 100%;
173
+ padding: 0.25rem 0.5rem;
174
+ border-radius: 0.5rem;
175
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
176
+ font-size: 0.8125rem;
177
+ text-align: left;
178
+ display: flex;
179
+ align-items: center;
180
+ justify-content: space-between;
181
+ gap: 0.375rem;
182
+ transition: background 0.15s, border-color 0.15s;
183
+ min-height: 2rem;
184
+ cursor: pointer;
185
+ background: oklch(0.16 0.01 250);
186
+ border: 1px solid oklch(0.25 0.02 250);
187
+ color: oklch(0.85 0.02 250);
188
+ }
189
+ .sd-trigger:hover:not(:disabled) {
190
+ background: oklch(0.18 0.01 250);
191
+ border-color: oklch(0.30 0.02 250);
192
+ }
193
+ .sd-trigger:disabled, .sd-disabled {
194
+ opacity: 0.5;
195
+ cursor: not-allowed;
196
+ }
197
+
198
+ .sd-trigger-label {
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 0.375rem;
202
+ min-width: 0;
203
+ }
204
+ .sd-trigger-icon {
205
+ flex-shrink: 0;
206
+ font-size: 0.75rem;
207
+ }
208
+
209
+ .sd-chevron {
210
+ width: 0.75rem;
211
+ height: 0.75rem;
212
+ flex-shrink: 0;
213
+ transition: transform 0.15s;
214
+ color: oklch(0.50 0.02 250);
215
+ }
216
+ .sd-chevron-open {
217
+ transform: rotate(180deg);
218
+ }
219
+
220
+ /* Dropdown panel */
221
+ .sd-panel {
222
+ position: absolute;
223
+ z-index: 50;
224
+ margin-top: 0.25rem;
225
+ width: 100%;
226
+ min-width: 12rem;
227
+ border-radius: 0.5rem;
228
+ overflow: hidden;
229
+ box-shadow: 0 4px 24px oklch(0 0 0 / 0.4);
230
+ background: oklch(0.16 0.01 250);
231
+ border: 1px solid oklch(0.25 0.02 250);
232
+ }
233
+
234
+ /* Search section */
235
+ .sd-search {
236
+ padding: 0.375rem 0.625rem;
237
+ border-bottom: 1px solid oklch(0.22 0.02 250);
238
+ }
239
+ .sd-search-inner {
240
+ display: flex;
241
+ align-items: center;
242
+ gap: 0.375rem;
243
+ }
244
+ .sd-search-icon {
245
+ width: 0.75rem;
246
+ height: 0.75rem;
247
+ flex-shrink: 0;
248
+ color: oklch(0.45 0.02 250);
249
+ }
250
+ .sd-search-input {
251
+ width: 100%;
252
+ background: transparent;
253
+ font-size: 0.625rem;
254
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
255
+ color: oklch(0.75 0.02 250);
256
+ border: none;
257
+ outline: none;
258
+ }
259
+ .sd-search-input::placeholder {
260
+ color: oklch(0.40 0.02 250);
261
+ }
262
+ .sd-search-clear {
263
+ color: oklch(0.40 0.02 250);
264
+ background: none;
265
+ border: none;
266
+ cursor: pointer;
267
+ padding: 0;
268
+ display: flex;
269
+ }
270
+ .sd-search-clear:hover {
271
+ opacity: 0.8;
272
+ }
273
+
274
+ /* Options list */
275
+ .sd-list {
276
+ padding: 0.125rem 0;
277
+ max-height: 280px;
278
+ overflow-y: auto;
279
+ list-style: none;
280
+ margin: 0;
281
+ }
282
+
283
+ .sd-group-label {
284
+ padding: 0.375rem 0.75rem 0.125rem;
285
+ }
286
+ .sd-group-label span {
287
+ font-size: 0.5625rem;
288
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
289
+ font-weight: 600;
290
+ text-transform: uppercase;
291
+ letter-spacing: 0.05em;
292
+ color: oklch(0.50 0.10 250);
293
+ }
294
+
295
+ .sd-option {
296
+ width: 100%;
297
+ padding: 0.375rem 0.75rem;
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 0.5rem;
301
+ text-align: left;
302
+ font-size: 0.6875rem;
303
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
304
+ transition: background 0.1s;
305
+ cursor: pointer;
306
+ background: transparent;
307
+ border: none;
308
+ border-left: 2px solid transparent;
309
+ color: oklch(0.80 0.02 250);
310
+ }
311
+ .sd-option:hover {
312
+ background: oklch(0.19 0.01 250);
313
+ }
314
+ .sd-option-selected {
315
+ background: oklch(0.20 0.02 250);
316
+ border-left-color: oklch(0.65 0.15 250);
317
+ }
318
+
319
+ .sd-option-icon {
320
+ flex-shrink: 0;
321
+ width: 1.25rem;
322
+ text-align: center;
323
+ font-size: 0.75rem;
324
+ }
325
+
326
+ .sd-check {
327
+ width: 0.75rem;
328
+ height: 0.75rem;
329
+ flex-shrink: 0;
330
+ margin-left: auto;
331
+ color: oklch(0.70 0.15 145);
332
+ }
333
+
334
+ .sd-empty {
335
+ padding: 0.75rem;
336
+ text-align: center;
337
+ font-size: 0.625rem;
338
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
339
+ color: oklch(0.45 0.02 250);
340
+ }
341
+ </style>