@joewinke/jatui 0.1.11 → 0.1.19

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 (90) hide show
  1. package/README.md +123 -0
  2. package/package.json +2 -1
  3. package/src/lib/actions/railNav.ts +473 -0
  4. package/src/lib/components/AnnotationLayer.svelte +108 -0
  5. package/src/lib/components/AnnotationPanel.svelte +319 -0
  6. package/src/lib/components/AudioWaveform.svelte +9 -5
  7. package/src/lib/components/AvailabilityModal.svelte +7 -3
  8. package/src/lib/components/AvatarUpload.svelte +27 -4
  9. package/src/lib/components/BookingForm.svelte +11 -9
  10. package/src/lib/components/BurndownChart.svelte +778 -0
  11. package/src/lib/components/Button.svelte +10 -1
  12. package/src/lib/components/CalendarPicker.svelte +3 -3
  13. package/src/lib/components/Card.svelte +2 -2
  14. package/src/lib/components/ChipInput.svelte +21 -15
  15. package/src/lib/components/ColorSelector.svelte +17 -13
  16. package/src/lib/components/CommentThread.svelte +773 -0
  17. package/src/lib/components/ConfirmDialog.svelte +348 -0
  18. package/src/lib/components/ConfirmModal.svelte +78 -11
  19. package/src/lib/components/ContextMenu.svelte +59 -19
  20. package/src/lib/components/CountdownTimer.svelte +1 -1
  21. package/src/lib/components/DateRangePicker.svelte +6 -4
  22. package/src/lib/components/Drawer.svelte +36 -3
  23. package/src/lib/components/EntityPreviewCard.svelte +104 -0
  24. package/src/lib/components/FileDropzone.svelte +493 -0
  25. package/src/lib/components/FilePicker.svelte +83 -14
  26. package/src/lib/components/FileThumbnail.svelte +80 -0
  27. package/src/lib/components/FilterDropdown.svelte +11 -11
  28. package/src/lib/components/HunkDiffView.svelte +348 -0
  29. package/src/lib/components/ImageLightbox.svelte +274 -0
  30. package/src/lib/components/ImageUpload.svelte +58 -9
  31. package/src/lib/components/InlineEdit.svelte +15 -9
  32. package/src/lib/components/InputDialog.svelte +327 -0
  33. package/src/lib/components/LazyImage.svelte +1 -0
  34. package/src/lib/components/LinkShortener.svelte +1 -1
  35. package/src/lib/components/LoadingSpinner.svelte +6 -2
  36. package/src/lib/components/MarkupEditor.svelte +485 -0
  37. package/src/lib/components/MarkupOverlay.svelte +55 -0
  38. package/src/lib/components/MediaWorkbench.svelte +871 -0
  39. package/src/lib/components/MilestoneCard.svelte +1 -1
  40. package/src/lib/components/MilestoneTimeline.svelte +1 -1
  41. package/src/lib/components/Modal.svelte +39 -4
  42. package/src/lib/components/PDFViewer.svelte +105 -0
  43. package/src/lib/components/PdfThumbnail.svelte +3 -1
  44. package/src/lib/components/PhoneInput.svelte +1 -1
  45. package/src/lib/components/ResizablePanel.svelte +4 -4
  46. package/src/lib/components/SearchDropdown.svelte +26 -13
  47. package/src/lib/components/SelectInput.svelte +26 -4
  48. package/src/lib/components/SidebarUserFooter.svelte +1 -1
  49. package/src/lib/components/SignaturePad.svelte +8 -4
  50. package/src/lib/components/SmartImageEditor.svelte +720 -0
  51. package/src/lib/components/SortDropdown.svelte +9 -3
  52. package/src/lib/components/Sparkline.svelte +9 -0
  53. package/src/lib/components/StatusBadge.svelte +20 -18
  54. package/src/lib/components/TextArea.svelte +24 -5
  55. package/src/lib/components/TextInput.svelte +29 -6
  56. package/src/lib/components/ThemeSelector.svelte +15 -4
  57. package/src/lib/components/TimeSlotPicker.svelte +7 -7
  58. package/src/lib/components/UserAvatar.svelte +14 -1
  59. package/src/lib/components/VariablePicker.svelte +170 -0
  60. package/src/lib/components/VoicePlayer.svelte +4 -3
  61. package/src/lib/components/markup.ts +287 -0
  62. package/src/lib/components/messaging/ChannelInfoModal.svelte +9 -9
  63. package/src/lib/components/messaging/ChannelList.svelte +1 -1
  64. package/src/lib/components/messaging/ChannelMembersModal.svelte +1 -1
  65. package/src/lib/components/messaging/CreateChannelModal.svelte +1 -1
  66. package/src/lib/components/messaging/DirectMessageList.svelte +1 -1
  67. package/src/lib/components/messaging/EmojiSelector.svelte +2 -1
  68. package/src/lib/components/messaging/MentionAutocomplete.svelte +1 -1
  69. package/src/lib/components/messaging/MessageAttachment.svelte +3 -3
  70. package/src/lib/components/messaging/MessageAttachmentUpload.svelte +3 -3
  71. package/src/lib/components/messaging/MessageInput.svelte +1 -1
  72. package/src/lib/components/messaging/MessageItem.svelte +6 -3
  73. package/src/lib/components/messaging/NotificationSettingsModal.svelte +1 -1
  74. package/src/lib/components/messaging/QuotedMessageDisplay.svelte +6 -1
  75. package/src/lib/components/messaging/StartDMModal.svelte +1 -1
  76. package/src/lib/components/pipeline/Pipeline.svelte +4 -4
  77. package/src/lib/components/pipeline/PipelineCard.svelte +1 -1
  78. package/src/lib/components/pipeline/PipelineColumn.svelte +8 -3
  79. package/src/lib/index.ts +91 -0
  80. package/src/lib/stores/confirmDialog.svelte.ts +48 -0
  81. package/src/lib/stores/inputDialog.svelte.ts +51 -0
  82. package/src/lib/styles/rail.css +63 -0
  83. package/src/lib/types/annotation.ts +38 -0
  84. package/src/lib/types/comments.ts +97 -0
  85. package/src/lib/types/entityPreview.ts +45 -0
  86. package/src/lib/types/filePicker.ts +2 -0
  87. package/src/lib/types/smartImageEditor.ts +39 -0
  88. package/src/lib/types/templateVars.ts +36 -0
  89. package/src/lib/utils/dateFormatters.ts +12 -10
  90. package/src/lib/utils/taskUtils.ts +21 -7
@@ -39,9 +39,26 @@
39
39
  let uploadError = $state<string | null>(null)
40
40
  let searchQuery = $state("")
41
41
  let fileInput = $state<HTMLInputElement>(undefined!)
42
+ let dragOver = $state(false)
42
43
 
43
44
  const currentFiles = $derived(activeTab === "personal" ? personalFiles : teamFiles)
44
45
 
46
+ const IMAGE_EXTS = new Set(["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "bmp", "ico", "tiff", "tif"])
47
+ const PDF_EXTS = new Set(["pdf"])
48
+
49
+ const EXT_TO_MIME: Record<string, string> = {
50
+ jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif",
51
+ webp: "image/webp", svg: "image/svg+xml", avif: "image/avif",
52
+ bmp: "image/bmp", ico: "image/x-icon", tiff: "image/tiff", tif: "image/tiff",
53
+ pdf: "application/pdf",
54
+ }
55
+
56
+ function getEffectiveMime(file: FilePickerFile): string {
57
+ if (file.metadata?.mimetype) return file.metadata.mimetype
58
+ const ext = file.name.split(".").pop()?.toLowerCase() ?? ""
59
+ return EXT_TO_MIME[ext] ?? ""
60
+ }
61
+
45
62
  const filteredFiles = $derived(() => {
46
63
  let files = currentFiles
47
64
  if (searchQuery.trim()) {
@@ -51,8 +68,8 @@
51
68
  if (accept) {
52
69
  const acceptTypes = accept.split(",").map((t) => t.trim().toLowerCase())
53
70
  files = files.filter((f) => {
54
- const mime = f.metadata?.mimetype || ""
55
- const ext = "." + f.name.split(".").pop()?.toLowerCase()
71
+ const mime = getEffectiveMime(f)
72
+ const ext = "." + (f.name.split(".").pop()?.toLowerCase() ?? "")
56
73
  return acceptTypes.some(
57
74
  (t) =>
58
75
  t === mime ||
@@ -121,7 +138,7 @@
121
138
  if (file) {
122
139
  selections.push({
123
140
  file,
124
- url: `${apiBase}/${encodeURIComponent(file.name)}/view?scope=${file.scope}`,
141
+ url: file.publicUrl ?? `${apiBase}/${encodeURIComponent(file.name)}/view?scope=${file.scope}`,
125
142
  downloadUrl: `${apiBase}/${encodeURIComponent(file.name)}/download?scope=${file.scope}`,
126
143
  })
127
144
  }
@@ -165,11 +182,17 @@
165
182
  }
166
183
 
167
184
  function isImage(file: FilePickerFile): boolean {
168
- return file.metadata?.mimetype?.startsWith("image/") ?? false
185
+ const mime = getEffectiveMime(file)
186
+ if (mime) return mime.startsWith("image/")
187
+ const ext = file.name.split(".").pop()?.toLowerCase() ?? ""
188
+ return IMAGE_EXTS.has(ext)
169
189
  }
170
190
 
171
191
  function isPdf(file: FilePickerFile): boolean {
172
- return file.metadata?.mimetype === "application/pdf"
192
+ const mime = getEffectiveMime(file)
193
+ if (mime) return mime === "application/pdf"
194
+ const ext = file.name.split(".").pop()?.toLowerCase() ?? ""
195
+ return PDF_EXTS.has(ext)
173
196
  }
174
197
 
175
198
  function getPreviewUrl(file: FilePickerFile): string {
@@ -238,6 +261,7 @@
238
261
  <div class="flex items-center gap-3 mb-4 shrink-0 flex-wrap">
239
262
  <div role="tablist" class="tabs tabs-boxed tabs-sm">
240
263
  <button
264
+ type="button"
241
265
  role="tab"
242
266
  class="tab {activeTab === 'personal' ? 'tab-active' : ''}"
243
267
  onclick={() => (activeTab = "personal")}
@@ -248,6 +272,7 @@
248
272
  {/if}
249
273
  </button>
250
274
  <button
275
+ type="button"
251
276
  role="tab"
252
277
  class="tab {activeTab === 'team' ? 'tab-active' : ''}"
253
278
  onclick={() => (activeTab = "team")}
@@ -275,6 +300,7 @@
275
300
  class="hidden"
276
301
  />
277
302
  <button
303
+ type="button"
278
304
  class="btn btn-sm btn-primary"
279
305
  onclick={() => fileInput?.click()}
280
306
  disabled={isUploading}
@@ -293,12 +319,35 @@
293
319
  {#if uploadError}
294
320
  <div class="alert alert-warning alert-sm mb-3 shrink-0">
295
321
  <span class="text-sm">{uploadError}</span>
296
- <button class="btn btn-xs btn-ghost" onclick={() => (uploadError = null)}>Dismiss</button>
322
+ <button type="button" class="btn btn-xs btn-ghost" onclick={() => (uploadError = null)}>Dismiss</button>
297
323
  </div>
298
324
  {/if}
299
325
 
300
326
  <!-- File grid -->
301
- <div class="flex-1 overflow-y-auto min-h-0">
327
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
328
+ <div
329
+ class="flex-1 overflow-y-auto min-h-0 relative"
330
+ ondragover={(e) => { e.preventDefault(); dragOver = true }}
331
+ ondragleave={(e) => { if (!e.currentTarget.contains(e.relatedTarget as Node)) dragOver = false }}
332
+ ondrop={(e) => {
333
+ e.preventDefault()
334
+ dragOver = false
335
+ if (e.dataTransfer?.files.length) {
336
+ const dt = new DataTransfer()
337
+ Array.from(e.dataTransfer.files).forEach(f => dt.items.add(f))
338
+ fileInput.files = dt.files
339
+ fileInput.dispatchEvent(new Event('change'))
340
+ }
341
+ }}
342
+ >
343
+ {#if dragOver && !loading}
344
+ <div class="absolute inset-0 z-10 flex flex-col items-center justify-center rounded-md border-2 border-dashed border-primary bg-primary/5 pointer-events-none">
345
+ <svg class="h-10 w-10 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
346
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
347
+ </svg>
348
+ <p class="mt-3 text-sm font-medium text-primary">Drop to upload</p>
349
+ </div>
350
+ {/if}
302
351
  {#if loading}
303
352
  <div class="flex items-center justify-center py-16">
304
353
  <span class="loading loading-spinner loading-lg text-primary"></span>
@@ -306,21 +355,40 @@
306
355
  {:else if error}
307
356
  <div class="text-center py-16">
308
357
  <p class="text-error text-sm">{error}</p>
309
- <button class="btn btn-sm btn-ghost mt-2" onclick={loadFiles}>Retry</button>
358
+ <button type="button" class="btn btn-sm btn-ghost mt-2" onclick={loadFiles}>Retry</button>
310
359
  </div>
311
360
  {:else if filteredFiles().length === 0}
312
- <div class="text-center py-16">
313
- <svg class="mx-auto h-10 w-10 text-base-content/20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
314
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
361
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
362
+ <div
363
+ class="flex flex-col items-center justify-center py-16 rounded-md border-2 border-dashed transition-colors duration-150 cursor-pointer
364
+ {dragOver ? 'border-primary bg-primary/5' : 'border-base-300 hover:border-base-content/20 hover:bg-base-200/40'}"
365
+ onclick={() => fileInput?.click()}
366
+ role="button"
367
+ tabindex="0"
368
+ onkeydown={(e) => e.key === 'Enter' && fileInput?.click()}
369
+ aria-label="Drop files here or click to upload"
370
+ >
371
+ <svg class="h-10 w-10 {dragOver ? 'text-primary' : 'text-base-content/20'} transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
372
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
315
373
  </svg>
316
- <p class="mt-3 text-sm text-base-content/50">
317
- {searchQuery ? "No files match your search" : "No files yet. Upload one above."}
374
+ <p class="mt-3 text-sm font-medium {dragOver ? 'text-primary' : 'text-base-content/50'}">
375
+ {#if dragOver}
376
+ Drop to upload
377
+ {:else if searchQuery}
378
+ No files match your search
379
+ {:else}
380
+ Drop a file here, or <span class="text-primary underline">browse</span>
381
+ {/if}
318
382
  </p>
383
+ {#if !searchQuery && !dragOver}
384
+ <p class="mt-1 text-xs text-base-content/30">{accept || "Any file type"}</p>
385
+ {/if}
319
386
  </div>
320
387
  {:else}
321
388
  <div class="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-3">
322
389
  {#each filteredFiles() as file (file.id)}
323
390
  <button
391
+ type="button"
324
392
  class="flex flex-col bg-base-100 border rounded-lg overflow-hidden text-left transition-all duration-150 hover:shadow-md
325
393
  {isSelected(file)
326
394
  ? 'border-primary ring-2 ring-primary/30'
@@ -393,8 +461,9 @@
393
461
  {/if}
394
462
  </span>
395
463
  <div class="flex gap-2">
396
- <button class="btn btn-sm btn-ghost" onclick={close}>Cancel</button>
464
+ <button type="button" class="btn btn-sm btn-ghost" onclick={close}>Cancel</button>
397
465
  <button
466
+ type="button"
398
467
  class="btn btn-sm btn-primary"
399
468
  disabled={selected.size === 0}
400
469
  onclick={confirmSelection}
@@ -0,0 +1,80 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** Display name of the file */
4
+ name: string
5
+ /** MIME type — used to pick the right icon / thumbnail */
6
+ mimeType: string
7
+ /** URL to render as image thumbnail (for image files) */
8
+ url?: string
9
+ size?: "sm" | "md" | "lg" | "xl"
10
+ }
11
+
12
+ let { name, mimeType, url, size = "md" }: Props = $props()
13
+
14
+ let imageError = $state(false)
15
+ let isLoading = $state(true)
16
+
17
+ const sizeClasses: Record<string, string> = {
18
+ sm: "h-8 w-8",
19
+ md: "h-16 w-16",
20
+ lg: "h-24 w-24",
21
+ xl: "h-32 w-32",
22
+ }
23
+
24
+ const isImageFile = $derived(mimeType.startsWith("image/"))
25
+ const isPdfFile = $derived(mimeType === "application/pdf")
26
+ const isCadFile = $derived(mimeType.includes("dwg") || mimeType.includes("autocad"))
27
+ const isZipFile = $derived(
28
+ mimeType === "application/zip" ||
29
+ mimeType === "application/x-zip-compressed" ||
30
+ mimeType === "application/x-zip"
31
+ )
32
+ </script>
33
+
34
+ <div class="relative {sizeClasses[size]} flex-shrink-0">
35
+ {#if isImageFile && url && !imageError}
36
+ <img
37
+ src={url}
38
+ alt={name}
39
+ class="w-full h-full object-cover rounded border border-base-300 shadow-sm"
40
+ onload={() => { isLoading = false }}
41
+ onerror={() => { imageError = true; isLoading = false }}
42
+ />
43
+ {:else}
44
+ <div class="w-full h-full bg-base-200 rounded border border-base-300 flex items-center justify-center">
45
+ {#if isPdfFile}
46
+ <svg class="h-6 w-6 text-error" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
47
+ <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20M10.92,12.31C10.68,11.54 10.15,9.08 11.55,9.04C12.95,9 12.03,12.16 12.03,12.16C12.42,13.65 14.05,14.72 14.05,14.72C14.55,14.57 17.4,14.24 17,15.72C16.57,17.2 13.5,15.81 13.5,15.81C11.55,15.95 10.09,16.47 10.09,16.47C8.96,18.58 7.64,19.5 7.1,18.61C6.43,17.5 9.23,16.07 9.23,16.07C10.68,13.67 10.92,12.31 10.92,12.31Z" />
48
+ </svg>
49
+ {:else if isCadFile}
50
+ <svg class="h-6 w-6 text-info" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
51
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 10l5-5 5 5" />
53
+ </svg>
54
+ {:else if isZipFile}
55
+ <svg class="h-6 w-6 text-warning" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
56
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
57
+ </svg>
58
+ {:else}
59
+ <svg class="h-6 w-6 text-base-content/40" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
60
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
61
+ </svg>
62
+ {/if}
63
+ </div>
64
+ {/if}
65
+
66
+ {#if isLoading && isImageFile && url}
67
+ <div class="absolute inset-0 bg-base-200 rounded animate-pulse"></div>
68
+ {/if}
69
+
70
+ <!-- File type badge for small size -->
71
+ {#if size === "sm" && (isPdfFile || isCadFile)}
72
+ <div
73
+ class="absolute -top-1 -right-1 w-3 h-3 rounded-full text-xs font-bold text-white flex items-center justify-center
74
+ {isPdfFile ? 'bg-error' : 'bg-info'}"
75
+ aria-hidden="true"
76
+ >
77
+ {isPdfFile ? "P" : "D"}
78
+ </div>
79
+ {/if}
80
+ </div>
@@ -109,18 +109,18 @@
109
109
  onchange={() => handleToggle(opt.value)}
110
110
  />
111
111
  <span>{opt.label}</span>
112
- <span class="text-xs opacity-60">({opt.count})</span>
112
+ <span class="text-[0.75rem] opacity-60">({opt.count})</span>
113
113
  </label>
114
114
  {:else}
115
115
  <button
116
116
  class="badge badge-sm transition-all duration-200 cursor-pointer {selected.has(opt.value)
117
- ? colorFn(opt.value, true) + ' shadow-md'
118
- : 'badge-ghost hover:badge-primary/20 hover:shadow-sm hover:scale-105'}"
117
+ ? colorFn(opt.value, true)
118
+ : 'badge-ghost hover:badge-primary/20'}"
119
119
  onclick={() => handleToggle(opt.value)}
120
120
  onkeydown={(e) => handleKeydown(e, opt.value)}
121
121
  >
122
122
  {opt.label}
123
- <span class="ml-1 opacity-70">({opt.count})</span>
123
+ <span class="ml-1 text-[0.75rem] opacity-60">({opt.count})</span>
124
124
  </button>
125
125
  {/if}
126
126
  {/each}
@@ -132,11 +132,11 @@
132
132
  <div
133
133
  tabindex="0"
134
134
  role="button"
135
- class="px-2.5 py-1 rounded cursor-pointer transition-all flex items-center gap-1.5 font-mono text-xs tracking-wider bg-base-200 border border-base-300 text-base-content/60"
135
+ class="px-2.5 py-1 rounded cursor-pointer transition-all flex items-center gap-1.5 text-[0.8125rem] bg-base-200 border border-base-300 text-base-content/60"
136
136
  >
137
- <span class="uppercase">{label}</span>
137
+ <span>{label}</span>
138
138
  <span
139
- class="px-1.5 py-0.5 rounded text-xs font-mono {selected.size > 0 && !(emptyMeansAll && selected.size === 0) ? 'bg-primary/20 text-primary' : 'bg-base-300 text-base-content opacity-60'}"
139
+ class="px-1.5 py-0.5 rounded text-[0.75rem] {selected.size > 0 && !(emptyMeansAll && selected.size === 0) ? 'bg-primary/20 text-primary' : 'bg-base-300 text-base-content opacity-60'}"
140
140
  >
141
141
  {displayText}
142
142
  </span>
@@ -171,8 +171,8 @@
171
171
  checked={selected.has(opt.value)}
172
172
  onchange={() => handleToggle(opt.value)}
173
173
  />
174
- <span class="truncate font-mono text-xs">{opt.label}</span>
175
- <span class="text-xs font-mono text-base-content/50">({opt.count})</span>
174
+ <span class="truncate text-[0.8125rem]">{opt.label}</span>
175
+ <span class="text-[0.75rem] text-base-content/45">({opt.count})</span>
176
176
  </label>
177
177
  </li>
178
178
  {/each}
@@ -183,12 +183,12 @@
183
183
  <div
184
184
  tabindex="0"
185
185
  role="menu"
186
- class="dropdown-content rounded-box shadow-lg p-2 z-40 {menuWidth} mt-1 {maxHeight ? maxHeight + ' overflow-y-auto' : ''} bg-base-200 border border-base-300"
186
+ class="dropdown-content rounded-box p-2 z-40 {menuWidth} mt-1 {maxHeight ? maxHeight + ' overflow-y-auto' : ''} bg-base-200 border border-base-300"
187
187
  >
188
188
  <div class="flex flex-wrap gap-1.5">
189
189
  {#each options as opt, index}
190
190
  <button
191
- class="px-2 py-0.5 rounded font-mono text-xs transition-all cursor-pointer border {selected.has(opt.value) ? 'bg-primary/20 border-primary/40 text-primary' : 'bg-base-300 border-base-content/20 text-base-content opacity-70 hover:opacity-100'}"
191
+ class="px-2 py-0.5 rounded text-[0.75rem] transition-all cursor-pointer border {selected.has(opt.value) ? 'bg-primary/20 border-primary/40 text-primary' : 'bg-base-300 border-base-content/20 text-base-content opacity-70 hover:opacity-100'}"
192
192
  onclick={() => handleToggle(opt.value)}
193
193
  onkeydown={(e) => handleKeydown(e, opt.value)}
194
194
  >