@joewinke/jatui 0.1.0 → 0.1.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joewinke/jatui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "description": "Shared Svelte 5 component library for JAT projects",
6
6
  "type": "module",
@@ -42,5 +42,8 @@
42
42
  "tailwindcss": "^4.0.0",
43
43
  "typescript": "^5.0.0",
44
44
  "vite": "^6.0.0"
45
+ },
46
+ "dependencies": {
47
+ "svelte-dnd-action": "^0.9.69"
45
48
  }
46
49
  }
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ /**
3
+ * EmojiPicker — Full Unicode emoji selector with search-by-name.
4
+ * Shows a small trigger button with the current emoji, opens a categorized grid dropdown.
5
+ * For assigning emoji icons to items (bases, channels, categories, etc.).
6
+ */
7
+ import { EMOJI_DATA, searchEmojis } from '../data/emojis';
8
+
9
+ let {
10
+ selected = null,
11
+ onSelect,
12
+ size = 'sm',
13
+ }: {
14
+ selected: string | null;
15
+ onSelect: (emoji: string | null) => void;
16
+ size?: 'sm' | 'md';
17
+ } = $props();
18
+
19
+ let open = $state(false);
20
+ let searchQuery = $state('');
21
+ let triggerEl: HTMLButtonElement | undefined = $state(undefined);
22
+ let searchInputEl: HTMLInputElement | undefined = $state(undefined);
23
+
24
+ const searchResults = $derived.by(() => {
25
+ if (!searchQuery.trim()) return null;
26
+ return searchEmojis(searchQuery);
27
+ });
28
+
29
+ function handleSelect(emoji: string) {
30
+ onSelect(emoji);
31
+ open = false;
32
+ searchQuery = '';
33
+ }
34
+
35
+ function handleClear(e: MouseEvent) {
36
+ e.stopPropagation();
37
+ onSelect(null);
38
+ open = false;
39
+ }
40
+
41
+ function handleClickOutside(e: MouseEvent) {
42
+ if (triggerEl && !triggerEl.contains(e.target as Node)) {
43
+ const dropdown = document.querySelector('.emoji-picker-dropdown');
44
+ if (dropdown && !dropdown.contains(e.target as Node)) {
45
+ open = false;
46
+ searchQuery = '';
47
+ }
48
+ }
49
+ }
50
+
51
+ function handleToggle(e: MouseEvent) {
52
+ e.stopPropagation();
53
+ open = !open;
54
+ if (!open) {
55
+ searchQuery = '';
56
+ } else {
57
+ requestAnimationFrame(() => searchInputEl?.focus());
58
+ }
59
+ }
60
+
61
+ const btnSize = $derived(size === 'sm' ? 'w-6 h-6 text-sm' : 'w-8 h-8 text-base');
62
+ </script>
63
+
64
+ <svelte:window onclick={handleClickOutside} />
65
+
66
+ <div class="relative inline-flex">
67
+ <button
68
+ bind:this={triggerEl}
69
+ class="emoji-trigger {btnSize} flex items-center justify-center rounded transition-all duration-100 cursor-pointer"
70
+ style="background: {selected ? 'oklch(0.25 0.02 250)' : 'oklch(0.20 0.01 250)'}; border: 1px solid {open ? 'oklch(0.45 0.10 240)' : 'oklch(0.28 0.02 250)'};"
71
+ onclick={handleToggle}
72
+ title={selected ? `Icon: ${selected} (click to change)` : 'Set icon'}
73
+ >
74
+ {#if selected}
75
+ <span>{selected}</span>
76
+ {:else}
77
+ <span style="color: oklch(0.40 0.02 250); font-size: 0.65em;">+</span>
78
+ {/if}
79
+ </button>
80
+
81
+ {#if open}
82
+ <div
83
+ class="emoji-picker-dropdown absolute left-0 top-full mt-1 z-50 rounded-lg overflow-hidden"
84
+ style="background: oklch(0.18 0.02 250); border: 1px solid oklch(0.30 0.02 250); box-shadow: 0 8px 32px oklch(0 0 0 / 0.5); width: 280px;"
85
+ onclick={(e) => e.stopPropagation()}
86
+ >
87
+ <!-- Search -->
88
+ <div class="px-2 py-1.5" style="border-bottom: 1px solid oklch(0.24 0.01 250);">
89
+ <input
90
+ bind:this={searchInputEl}
91
+ type="text"
92
+ placeholder="Search emojis..."
93
+ bind:value={searchQuery}
94
+ class="w-full px-2 py-1 rounded text-xs border-0 outline-none"
95
+ style="background: oklch(0.22 0.01 250); color: oklch(0.85 0.01 250);"
96
+ />
97
+ </div>
98
+
99
+ <!-- Emoji grid -->
100
+ <div class="max-h-64 overflow-y-auto px-1.5 py-1.5">
101
+ {#if searchResults}
102
+ {#if searchResults.length === 0}
103
+ <div class="text-center py-4 text-xs" style="color: oklch(0.45 0.02 250);">
104
+ No emojis found
105
+ </div>
106
+ {:else}
107
+ <div class="grid grid-cols-8 gap-0.5">
108
+ {#each searchResults as entry}
109
+ <button
110
+ class="w-7 h-7 flex items-center justify-center rounded text-sm cursor-pointer transition-all duration-75 hover:scale-110"
111
+ style="background: {selected === entry.char ? 'oklch(0.45 0.12 240 / 0.3)' : 'transparent'};"
112
+ onclick={() => handleSelect(entry.char)}
113
+ title={entry.name}
114
+ >
115
+ {entry.char}
116
+ </button>
117
+ {/each}
118
+ </div>
119
+ {/if}
120
+ {:else}
121
+ {#each EMOJI_DATA as group}
122
+ <div class="mb-1.5">
123
+ <div class="text-[9px] font-mono uppercase tracking-wider px-1 py-0.5 sticky top-0" style="color: oklch(0.45 0.02 250); background: oklch(0.18 0.02 250);">
124
+ {group.label}
125
+ </div>
126
+ <div class="grid grid-cols-8 gap-0.5">
127
+ {#each group.emojis as entry}
128
+ <button
129
+ class="w-7 h-7 flex items-center justify-center rounded text-sm cursor-pointer transition-all duration-75 hover:scale-110"
130
+ style="background: {selected === entry.char ? 'oklch(0.45 0.12 240 / 0.3)' : 'transparent'};"
131
+ onclick={() => handleSelect(entry.char)}
132
+ title={entry.name}
133
+ >
134
+ {entry.char}
135
+ </button>
136
+ {/each}
137
+ </div>
138
+ </div>
139
+ {/each}
140
+ {/if}
141
+ </div>
142
+
143
+ <!-- Clear button -->
144
+ {#if selected}
145
+ <div class="px-2 py-1.5" style="border-top: 1px solid oklch(0.24 0.01 250);">
146
+ <button
147
+ class="w-full text-center text-[10px] py-1 rounded cursor-pointer transition-colors duration-100"
148
+ style="color: oklch(0.55 0.02 250); background: transparent;"
149
+ onclick={handleClear}
150
+ >
151
+ Clear icon
152
+ </button>
153
+ </div>
154
+ {/if}
155
+ </div>
156
+ {/if}
157
+ </div>
@@ -0,0 +1,274 @@
1
+ <script lang="ts">
2
+ export interface ShortenedLink {
3
+ id: string
4
+ slug: string
5
+ destination_url: string
6
+ title?: string | null
7
+ click_count: number
8
+ created_at: string
9
+ expires_at?: string | null
10
+ is_active: boolean
11
+ }
12
+
13
+ interface Props {
14
+ links: ShortenedLink[]
15
+ baseUrl?: string
16
+ showHeader?: boolean
17
+ onCreate?: (data: { title: string; destination_url: string; slug: string; expires_at: string | null }) => void
18
+ onDelete?: (id: string) => void
19
+ onToggle?: (id: string, is_active: boolean) => void
20
+ class?: string
21
+ }
22
+
23
+ let { links, baseUrl = '', showHeader = true, onCreate, onDelete, onToggle, class: className = '' }: Props = $props()
24
+
25
+ let showForm = $state(false)
26
+ let title = $state('')
27
+ let destinationUrl = $state('')
28
+ let customSlug = $state('')
29
+ let expiresAt = $state('')
30
+ let copiedId = $state<string | null>(null)
31
+ let submitting = $state(false)
32
+
33
+ function generateSlug(): string {
34
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
35
+ let result = ''
36
+ for (let i = 0; i < 6; i++) {
37
+ result += chars.charAt(Math.floor(Math.random() * chars.length))
38
+ }
39
+ return result
40
+ }
41
+
42
+ function handleSubmit(e: Event) {
43
+ e.preventDefault()
44
+ if (!destinationUrl || submitting) return
45
+
46
+ submitting = true
47
+ const slug = customSlug.trim() || generateSlug()
48
+
49
+ onCreate?.({
50
+ title: title.trim(),
51
+ destination_url: destinationUrl.trim(),
52
+ slug,
53
+ expires_at: expiresAt || null
54
+ })
55
+
56
+ title = ''
57
+ destinationUrl = ''
58
+ customSlug = ''
59
+ expiresAt = ''
60
+ showForm = false
61
+ submitting = false
62
+ }
63
+
64
+ async function copyUrl(link: ShortenedLink) {
65
+ const url = `${baseUrl}/l/${link.slug}`
66
+ await navigator.clipboard.writeText(url)
67
+ copiedId = link.id
68
+ setTimeout(() => { copiedId = null }, 2000)
69
+ }
70
+
71
+ function isExpired(link: ShortenedLink): boolean {
72
+ if (!link.expires_at) return false
73
+ return new Date(link.expires_at) < new Date()
74
+ }
75
+ </script>
76
+
77
+ <div class="space-y-4 {className}">
78
+ {#if showHeader}
79
+ <div class="flex items-center justify-between">
80
+ <h2 class="text-xl font-bold">Short Links</h2>
81
+ {#if !showForm}
82
+ <button class="btn btn-primary btn-sm" onclick={() => showForm = true}>
83
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
84
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
85
+ </svg>
86
+ New Link
87
+ </button>
88
+ {/if}
89
+ </div>
90
+ {:else if !showForm}
91
+ <div class="flex justify-end">
92
+ <button class="btn btn-primary btn-sm" onclick={() => showForm = true}>
93
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
94
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
95
+ </svg>
96
+ New Link
97
+ </button>
98
+ </div>
99
+ {/if}
100
+
101
+ {#if showForm}
102
+ <form class="card bg-base-200" onsubmit={handleSubmit}>
103
+ <div class="card-body gap-4">
104
+ <div class="grid sm:grid-cols-2 gap-3">
105
+ <div class="flex flex-col gap-1.5">
106
+ <label class="text-sm font-medium" for="link-title">Title</label>
107
+ <input
108
+ id="link-title"
109
+ type="text"
110
+ class="input input-bordered input-sm w-full"
111
+ placeholder="My Campaign Link"
112
+ bind:value={title}
113
+ />
114
+ </div>
115
+ <div class="flex flex-col gap-1.5">
116
+ <label class="text-sm font-medium" for="link-destination">
117
+ Destination URL <span class="text-error">*</span>
118
+ </label>
119
+ <input
120
+ id="link-destination"
121
+ type="url"
122
+ class="input input-bordered input-sm w-full"
123
+ placeholder="https://example.com/page"
124
+ bind:value={destinationUrl}
125
+ required
126
+ />
127
+ </div>
128
+ </div>
129
+
130
+ <div class="grid sm:grid-cols-2 gap-3">
131
+ <div class="flex flex-col gap-1.5">
132
+ <label class="text-sm font-medium" for="link-slug">Custom Slug</label>
133
+ <div class="join w-full">
134
+ <span class="join-item flex items-center px-3 bg-base-300 text-xs text-base-content/50 border border-base-content/20">/l/</span>
135
+ <input
136
+ id="link-slug"
137
+ type="text"
138
+ class="input input-bordered input-sm join-item flex-1 min-w-0"
139
+ placeholder="auto-generated"
140
+ bind:value={customSlug}
141
+ pattern="[a-zA-Z0-9_-]+"
142
+ />
143
+ </div>
144
+ </div>
145
+ <div class="flex flex-col gap-1.5">
146
+ <label class="text-sm font-medium" for="link-expires">Expires</label>
147
+ <input
148
+ id="link-expires"
149
+ type="date"
150
+ class="input input-bordered input-sm w-full"
151
+ bind:value={expiresAt}
152
+ />
153
+ </div>
154
+ </div>
155
+
156
+ <div class="flex justify-end gap-2 pt-1">
157
+ <button type="button" class="btn btn-ghost btn-sm" onclick={() => showForm = false}>Cancel</button>
158
+ <button type="submit" class="btn btn-primary btn-sm" disabled={!destinationUrl || submitting}>
159
+ {#if submitting}
160
+ <span class="loading loading-spinner loading-xs"></span>
161
+ {/if}
162
+ Create Link
163
+ </button>
164
+ </div>
165
+ </div>
166
+ </form>
167
+ {/if}
168
+
169
+ {#if links.length === 0 && !showForm}
170
+ <div class="card bg-base-200">
171
+ <div class="card-body text-center py-8">
172
+ <p class="text-base-content/60">No short links yet. Create your first one above.</p>
173
+ </div>
174
+ </div>
175
+ {:else if links.length > 0}
176
+ <div class="overflow-x-auto">
177
+ <table class="table">
178
+ <thead>
179
+ <tr>
180
+ <th>Link</th>
181
+ <th>Destination</th>
182
+ <th class="text-center">Clicks</th>
183
+ <th class="text-center">Status</th>
184
+ <th class="text-right">Actions</th>
185
+ </tr>
186
+ </thead>
187
+ <tbody>
188
+ {#each links as link (link.id)}
189
+ <tr class="hover">
190
+ <td>
191
+ <div>
192
+ {#if link.title}
193
+ <div class="font-medium text-sm">{link.title}</div>
194
+ {/if}
195
+ <div class="text-primary text-sm font-mono">/l/{link.slug}</div>
196
+ </div>
197
+ </td>
198
+ <td>
199
+ <a
200
+ href={link.destination_url}
201
+ target="_blank"
202
+ rel="noopener noreferrer"
203
+ class="link link-hover text-sm max-w-xs truncate block"
204
+ title={link.destination_url}
205
+ >
206
+ {link.destination_url}
207
+ </a>
208
+ </td>
209
+ <td class="text-center">
210
+ <span class="badge badge-ghost badge-sm">{link.click_count}</span>
211
+ </td>
212
+ <td class="text-center">
213
+ {#if isExpired(link)}
214
+ <span class="badge badge-error badge-sm">Expired</span>
215
+ {:else if !link.is_active}
216
+ <span class="badge badge-ghost badge-sm">Inactive</span>
217
+ {:else}
218
+ <span class="badge badge-success badge-sm">Active</span>
219
+ {/if}
220
+ </td>
221
+ <td>
222
+ <div class="flex items-center justify-end gap-1">
223
+ <button
224
+ class="btn btn-ghost btn-xs"
225
+ title="Copy short URL"
226
+ onclick={() => copyUrl(link)}
227
+ >
228
+ {#if copiedId === link.id}
229
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor">
230
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
231
+ </svg>
232
+ {:else}
233
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
234
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
235
+ </svg>
236
+ {/if}
237
+ </button>
238
+ {#if onToggle}
239
+ <button
240
+ class="btn btn-ghost btn-xs"
241
+ title={link.is_active ? 'Deactivate' : 'Activate'}
242
+ onclick={() => onToggle(link.id, !link.is_active)}
243
+ >
244
+ {#if link.is_active}
245
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
246
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
247
+ </svg>
248
+ {:else}
249
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
250
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
251
+ </svg>
252
+ {/if}
253
+ </button>
254
+ {/if}
255
+ {#if onDelete}
256
+ <button
257
+ class="btn btn-ghost btn-xs text-error"
258
+ title="Delete link"
259
+ onclick={() => onDelete(link.id)}
260
+ >
261
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
262
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
263
+ </svg>
264
+ </button>
265
+ {/if}
266
+ </div>
267
+ </td>
268
+ </tr>
269
+ {/each}
270
+ </tbody>
271
+ </table>
272
+ </div>
273
+ {/if}
274
+ </div>
@@ -165,15 +165,24 @@
165
165
  <style>
166
166
  .search-dropdown {
167
167
  position: relative;
168
+ --sd-bg: var(--sd-color-bg, oklch(from var(--b1, #1d232a) l c h));
169
+ --sd-bg-hover: var(--sd-color-bg-hover, oklch(from var(--b2, #191e24) l c h));
170
+ --sd-bg-selected: var(--sd-color-bg-selected, oklch(from var(--b3, #15191e) l c h));
171
+ --sd-border: var(--sd-color-border, oklch(from var(--bc, #a6adbb) l c h / 0.2));
172
+ --sd-text: var(--sd-color-text, oklch(from var(--bc, #a6adbb) l c h));
173
+ --sd-text-muted: var(--sd-color-text-muted, oklch(from var(--bc, #a6adbb) l c h / 0.5));
174
+ --sd-text-label: var(--sd-color-text-label, oklch(from var(--p, #7480ff) l c h / 0.7));
175
+ --sd-accent: var(--sd-color-accent, oklch(from var(--p, #7480ff) l c h));
176
+ --sd-success: var(--sd-color-success, oklch(from var(--su, #36d399) l c h));
168
177
  }
169
178
 
170
179
  /* Trigger button */
171
180
  .sd-trigger {
172
181
  width: 100%;
173
182
  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;
183
+ border-radius: var(--rounded-btn, 0.5rem);
184
+ font-family: inherit;
185
+ font-size: inherit;
177
186
  text-align: left;
178
187
  display: flex;
179
188
  align-items: center;
@@ -182,13 +191,12 @@
182
191
  transition: background 0.15s, border-color 0.15s;
183
192
  min-height: 2rem;
184
193
  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);
194
+ background: var(--sd-bg);
195
+ border: 1px solid var(--sd-border);
196
+ color: var(--sd-text);
188
197
  }
189
198
  .sd-trigger:hover:not(:disabled) {
190
- background: oklch(0.18 0.01 250);
191
- border-color: oklch(0.30 0.02 250);
199
+ background: var(--sd-bg-hover);
192
200
  }
193
201
  .sd-trigger:disabled, .sd-disabled {
194
202
  opacity: 0.5;
@@ -211,7 +219,7 @@
211
219
  height: 0.75rem;
212
220
  flex-shrink: 0;
213
221
  transition: transform 0.15s;
214
- color: oklch(0.50 0.02 250);
222
+ color: var(--sd-text-muted);
215
223
  }
216
224
  .sd-chevron-open {
217
225
  transform: rotate(180deg);
@@ -224,17 +232,17 @@
224
232
  margin-top: 0.25rem;
225
233
  width: 100%;
226
234
  min-width: 12rem;
227
- border-radius: 0.5rem;
235
+ border-radius: var(--rounded-box, 0.5rem);
228
236
  overflow: hidden;
229
237
  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);
238
+ background: var(--sd-bg);
239
+ border: 1px solid var(--sd-border);
232
240
  }
233
241
 
234
242
  /* Search section */
235
243
  .sd-search {
236
244
  padding: 0.375rem 0.625rem;
237
- border-bottom: 1px solid oklch(0.22 0.02 250);
245
+ border-bottom: 1px solid var(--sd-border);
238
246
  }
239
247
  .sd-search-inner {
240
248
  display: flex;
@@ -245,22 +253,22 @@
245
253
  width: 0.75rem;
246
254
  height: 0.75rem;
247
255
  flex-shrink: 0;
248
- color: oklch(0.45 0.02 250);
256
+ color: var(--sd-text-muted);
249
257
  }
250
258
  .sd-search-input {
251
259
  width: 100%;
252
260
  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);
261
+ font-size: inherit;
262
+ font-family: inherit;
263
+ color: var(--sd-text);
256
264
  border: none;
257
265
  outline: none;
258
266
  }
259
267
  .sd-search-input::placeholder {
260
- color: oklch(0.40 0.02 250);
268
+ color: var(--sd-text-muted);
261
269
  }
262
270
  .sd-search-clear {
263
- color: oklch(0.40 0.02 250);
271
+ color: var(--sd-text-muted);
264
272
  background: none;
265
273
  border: none;
266
274
  cursor: pointer;
@@ -284,12 +292,12 @@
284
292
  padding: 0.375rem 0.75rem 0.125rem;
285
293
  }
286
294
  .sd-group-label span {
287
- font-size: 0.5625rem;
288
- font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
295
+ font-size: 0.75em;
296
+ font-family: inherit;
289
297
  font-weight: 600;
290
298
  text-transform: uppercase;
291
299
  letter-spacing: 0.05em;
292
- color: oklch(0.50 0.10 250);
300
+ color: var(--sd-text-label);
293
301
  }
294
302
 
295
303
  .sd-option {
@@ -299,21 +307,21 @@
299
307
  align-items: center;
300
308
  gap: 0.5rem;
301
309
  text-align: left;
302
- font-size: 0.6875rem;
303
- font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
310
+ font-size: inherit;
311
+ font-family: inherit;
304
312
  transition: background 0.1s;
305
313
  cursor: pointer;
306
314
  background: transparent;
307
315
  border: none;
308
316
  border-left: 2px solid transparent;
309
- color: oklch(0.80 0.02 250);
317
+ color: var(--sd-text);
310
318
  }
311
319
  .sd-option:hover {
312
- background: oklch(0.19 0.01 250);
320
+ background: var(--sd-bg-hover);
313
321
  }
314
322
  .sd-option-selected {
315
- background: oklch(0.20 0.02 250);
316
- border-left-color: oklch(0.65 0.15 250);
323
+ background: var(--sd-bg-selected);
324
+ border-left-color: var(--sd-accent);
317
325
  }
318
326
 
319
327
  .sd-option-icon {
@@ -328,14 +336,14 @@
328
336
  height: 0.75rem;
329
337
  flex-shrink: 0;
330
338
  margin-left: auto;
331
- color: oklch(0.70 0.15 145);
339
+ color: var(--sd-success);
332
340
  }
333
341
 
334
342
  .sd-empty {
335
343
  padding: 0.75rem;
336
344
  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);
345
+ font-size: inherit;
346
+ font-family: inherit;
347
+ color: var(--sd-text-muted);
340
348
  }
341
349
  </style>