@classic-homes/theme-svelte 0.1.11 → 0.1.13

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.
@@ -1,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  declare const Alert: import("svelte").Component<{
3
3
  [key: string]: unknown;
4
- variant?: "default" | "destructive" | "error" | "warning" | "success" | "info" | undefined;
4
+ variant?: "default" | "destructive" | "error" | "info" | "warning" | "success" | undefined;
5
5
  class?: string;
6
6
  children: Snippet;
7
7
  }, {}, "">;
@@ -1,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  declare const Badge: import("svelte").Component<{
3
3
  [key: string]: unknown;
4
- variant?: "default" | "secondary" | "destructive" | "outline" | "warning" | "success" | "info" | undefined;
4
+ variant?: "default" | "secondary" | "destructive" | "outline" | "info" | "warning" | "success" | undefined;
5
5
  size?: "default" | "sm" | "dot" | undefined;
6
6
  clickable?: boolean;
7
7
  class?: string;
@@ -133,8 +133,10 @@
133
133
  </div>
134
134
 
135
135
  <ComboboxPrimitive.Content
136
- class="relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
137
- sideOffset={4}
136
+ class="relative z-50 w-[var(--bits-floating-anchor-width)] min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:border-t-0 data-[side=bottom]:rounded-t-none data-[side=top]:border-b-0 data-[side=top]:rounded-b-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
137
+ sideOffset={-1}
138
+ collisionPadding={8}
139
+ avoidCollisions={true}
138
140
  >
139
141
  <div class="p-1 max-h-[300px] overflow-y-auto">
140
142
  {#if loading}
@@ -65,12 +65,21 @@
65
65
  let minute = $state(0);
66
66
  let period = $state<'AM' | 'PM'>('PM');
67
67
 
68
- // Sync state when value changes externally
68
+ // Track if we're currently selecting to prevent effect from interfering
69
+ let isSelecting = $state(false);
70
+
71
+ // Sync state when value changes externally (not during our own selection)
69
72
  $effect(() => {
70
- if (value) {
71
- hour = value.getHours();
73
+ if (value && !isSelecting) {
74
+ const hours = value.getHours();
72
75
  minute = value.getMinutes();
73
- period = value.getHours() >= 12 ? 'PM' : 'AM';
76
+ period = hours >= 12 ? 'PM' : 'AM';
77
+ // Convert 24h to 12h format for the hour state
78
+ if (timeFormat === '12h') {
79
+ hour = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
80
+ } else {
81
+ hour = hours;
82
+ }
74
83
  viewMonth = value.getMonth();
75
84
  viewYear = value.getFullYear();
76
85
  }
@@ -147,7 +156,11 @@
147
156
  function selectDate(day: number) {
148
157
  if (isDateDisabled(day)) return;
149
158
 
150
- const newDate = new Date(viewYear, viewMonth, day);
159
+ // Capture current view state before any reactive updates
160
+ const currentMonth = viewMonth;
161
+ const currentYear = viewYear;
162
+
163
+ const newDate = new Date(currentYear, currentMonth, day);
151
164
 
152
165
  if (dateOnly) {
153
166
  // Set to midnight for date-only mode
@@ -161,11 +174,16 @@
161
174
  newDate.setHours(h, minute, 0, 0);
162
175
  }
163
176
 
177
+ // Prevent the effect from running during our selection
178
+ isSelecting = true;
164
179
  value = newDate;
165
180
  onValueChange?.(newDate);
181
+ // Allow effect to run again for external changes
182
+ isSelecting = false;
166
183
  }
167
184
 
168
185
  function updateTime() {
186
+ isSelecting = true;
169
187
  if (!value) {
170
188
  // If no date selected, use today
171
189
  const today = new Date();
@@ -186,6 +204,7 @@
186
204
  value = newDate;
187
205
  onValueChange?.(newDate);
188
206
  }
207
+ isSelecting = false;
189
208
  }
190
209
 
191
210
  function prevMonth() {
@@ -280,7 +299,7 @@
280
299
  <button
281
300
  type="button"
282
301
  onclick={prevMonth}
283
- class="h-7 w-7 inline-flex items-center justify-center rounded-md hover:bg-accent hover:text-accent-foreground"
302
+ class="h-7 w-7 inline-flex items-center justify-center rounded-md bg-transparent transition-colors duration-150 hover:bg-accent hover:text-accent-foreground"
284
303
  aria-label="Previous month"
285
304
  >
286
305
  <svg
@@ -304,7 +323,7 @@
304
323
  <button
305
324
  type="button"
306
325
  onclick={nextMonth}
307
- class="h-7 w-7 inline-flex items-center justify-center rounded-md hover:bg-accent hover:text-accent-foreground"
326
+ class="h-7 w-7 inline-flex items-center justify-center rounded-md bg-transparent transition-colors duration-150 hover:bg-accent hover:text-accent-foreground"
308
327
  aria-label="Next month"
309
328
  >
310
329
  <svg
@@ -345,7 +364,7 @@
345
364
  onclick={() => selectDate(day)}
346
365
  disabled={isDateDisabled(day)}
347
366
  class={cn(
348
- 'h-8 w-8 inline-flex items-center justify-center rounded-md text-sm transition-colors',
367
+ 'h-8 w-8 inline-flex items-center justify-center rounded-md text-sm bg-transparent transition-colors duration-150',
349
368
  'hover:bg-accent hover:text-accent-foreground',
350
369
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
351
370
  'disabled:pointer-events-none disabled:opacity-50',
@@ -67,69 +67,73 @@
67
67
  <DropdownMenuPrimitive.Content
68
68
  {side}
69
69
  {align}
70
- sideOffset={4}
70
+ sideOffset={-1}
71
+ collisionPadding={8}
72
+ avoidCollisions={true}
71
73
  class={cn(
72
74
  'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
73
75
  className
74
76
  )}
75
77
  >
76
- {#each items as item}
77
- {#if isGroup(item)}
78
- <DropdownMenuPrimitive.Group>
79
- {#if item.label}
80
- <div class="px-2 py-1.5 text-sm font-semibold">
81
- {item.label}
82
- </div>
83
- {/if}
84
- {#each item.items as subItem}
85
- {#if subItem.type === 'separator'}
86
- <DropdownMenuPrimitive.Separator class="-mx-1 my-1 h-px bg-muted" />
87
- {:else if subItem.type === 'label'}
78
+ <div class="max-h-[300px] overflow-y-auto">
79
+ {#each items as item}
80
+ {#if isGroup(item)}
81
+ <DropdownMenuPrimitive.Group>
82
+ {#if item.label}
88
83
  <div class="px-2 py-1.5 text-sm font-semibold">
89
- {subItem.label}
84
+ {item.label}
90
85
  </div>
91
- {:else}
92
- <DropdownMenuPrimitive.Item
93
- disabled={subItem.disabled}
94
- onSelect={() => subItem.onSelect?.()}
95
- class={cn(
96
- 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default',
97
- subItem.destructive && 'text-destructive data-[highlighted]:text-destructive'
98
- )}
99
- >
100
- {subItem.label}
101
- {#if subItem.shortcut}
102
- <span class="ml-auto text-xs tracking-widest opacity-60">
103
- {subItem.shortcut}
104
- </span>
105
- {/if}
106
- </DropdownMenuPrimitive.Item>
107
86
  {/if}
108
- {/each}
109
- </DropdownMenuPrimitive.Group>
110
- {:else if item.type === 'separator'}
111
- <DropdownMenuPrimitive.Separator class="-mx-1 my-1 h-px bg-muted" />
112
- {:else if item.type === 'label'}
113
- <div class="px-2 py-1.5 text-sm font-semibold">
114
- {item.label}
115
- </div>
116
- {:else}
117
- <DropdownMenuPrimitive.Item
118
- disabled={item.disabled}
119
- onSelect={() => item.onSelect?.()}
120
- class={cn(
121
- 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default',
122
- item.destructive && 'text-destructive data-[highlighted]:text-destructive'
123
- )}
124
- >
125
- {item.label}
126
- {#if item.shortcut}
127
- <span class="ml-auto text-xs tracking-widest opacity-60">
128
- {item.shortcut}
129
- </span>
130
- {/if}
131
- </DropdownMenuPrimitive.Item>
132
- {/if}
133
- {/each}
87
+ {#each item.items as subItem}
88
+ {#if subItem.type === 'separator'}
89
+ <DropdownMenuPrimitive.Separator class="-mx-1 my-1 h-px bg-muted" />
90
+ {:else if subItem.type === 'label'}
91
+ <div class="px-2 py-1.5 text-sm font-semibold">
92
+ {subItem.label}
93
+ </div>
94
+ {:else}
95
+ <DropdownMenuPrimitive.Item
96
+ disabled={subItem.disabled}
97
+ onSelect={() => subItem.onSelect?.()}
98
+ class={cn(
99
+ 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default',
100
+ subItem.destructive && 'text-destructive data-[highlighted]:text-destructive'
101
+ )}
102
+ >
103
+ {subItem.label}
104
+ {#if subItem.shortcut}
105
+ <span class="ml-auto text-xs tracking-widest opacity-60">
106
+ {subItem.shortcut}
107
+ </span>
108
+ {/if}
109
+ </DropdownMenuPrimitive.Item>
110
+ {/if}
111
+ {/each}
112
+ </DropdownMenuPrimitive.Group>
113
+ {:else if item.type === 'separator'}
114
+ <DropdownMenuPrimitive.Separator class="-mx-1 my-1 h-px bg-muted" />
115
+ {:else if item.type === 'label'}
116
+ <div class="px-2 py-1.5 text-sm font-semibold">
117
+ {item.label}
118
+ </div>
119
+ {:else}
120
+ <DropdownMenuPrimitive.Item
121
+ disabled={item.disabled}
122
+ onSelect={() => item.onSelect?.()}
123
+ class={cn(
124
+ 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:cursor-default',
125
+ item.destructive && 'text-destructive data-[highlighted]:text-destructive'
126
+ )}
127
+ >
128
+ {item.label}
129
+ {#if item.shortcut}
130
+ <span class="ml-auto text-xs tracking-widest opacity-60">
131
+ {item.shortcut}
132
+ </span>
133
+ {/if}
134
+ </DropdownMenuPrimitive.Item>
135
+ {/if}
136
+ {/each}
137
+ </div>
134
138
  </DropdownMenuPrimitive.Content>
135
139
  </DropdownMenuPrimitive.Root>
@@ -0,0 +1,370 @@
1
+ <script lang="ts">
2
+ import { cn } from '../utils.js';
3
+
4
+ /**
5
+ * Available icon names for the Icon component.
6
+ * These are common icons useful across applications.
7
+ */
8
+ export type IconName =
9
+ | 'close'
10
+ | 'check'
11
+ | 'plus'
12
+ | 'minus'
13
+ | 'edit'
14
+ | 'trash'
15
+ | 'info'
16
+ | 'alert-circle'
17
+ | 'alert-triangle'
18
+ | 'chevron-down'
19
+ | 'chevron-up'
20
+ | 'chevron-left'
21
+ | 'chevron-right'
22
+ | 'search'
23
+ | 'settings'
24
+ | 'user'
25
+ | 'home'
26
+ | 'external-link'
27
+ | 'loading'
28
+ | 'send'
29
+ | 'more'
30
+ | 'more-horizontal'
31
+ | 'grip'
32
+ | 'history'
33
+ | 'message'
34
+ | 'chat'
35
+ | 'eye'
36
+ | 'eye-off'
37
+ | 'copy'
38
+ | 'download'
39
+ | 'upload'
40
+ | 'refresh'
41
+ | 'arrow-left'
42
+ | 'arrow-right'
43
+ | 'menu'
44
+ | 'filter'
45
+ | 'sort'
46
+ | 'calendar'
47
+ | 'clock'
48
+ | 'mail'
49
+ | 'phone'
50
+ | 'link'
51
+ | 'image'
52
+ | 'file'
53
+ | 'folder'
54
+ | 'star'
55
+ | 'heart'
56
+ | 'bookmark'
57
+ | 'lock'
58
+ | 'unlock';
59
+
60
+ interface Props {
61
+ /** The name of the icon to display */
62
+ name: IconName;
63
+ /** Size of the icon in pixels (default: 24) */
64
+ size?: number;
65
+ /** Additional CSS classes */
66
+ class?: string;
67
+ /** Stroke width for stroke-based icons (default: 2) */
68
+ strokeWidth?: number;
69
+ /** Accessible label for the icon (renders as visually hidden text) */
70
+ label?: string;
71
+ /** Additional attributes to spread onto the SVG element */
72
+ [key: string]: unknown;
73
+ }
74
+
75
+ let { name, size = 24, class: className, strokeWidth = 2, label, ...restProps }: Props = $props();
76
+
77
+ // SVG path definitions for each icon
78
+ // All icons use a 24x24 viewBox
79
+ const icons: Record<IconName, { path: string; fill?: boolean }> = {
80
+ // Close (X)
81
+ close: {
82
+ path: 'M18 6L6 18M6 6l12 12',
83
+ },
84
+ // Check mark
85
+ check: {
86
+ path: 'M20 6L9 17l-5-5',
87
+ },
88
+ // Plus
89
+ plus: {
90
+ path: 'M12 5v14M5 12h14',
91
+ },
92
+ // Minus
93
+ minus: {
94
+ path: 'M5 12h14',
95
+ },
96
+ // Edit (pencil)
97
+ edit: {
98
+ path: 'M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5ZM15 5l4 4',
99
+ },
100
+ // Trash
101
+ trash: {
102
+ path: 'M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6',
103
+ },
104
+ // Info
105
+ info: {
106
+ path: 'M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 16v-4M12 8h.01',
107
+ },
108
+ // Alert circle
109
+ 'alert-circle': {
110
+ path: 'M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 8v4M12 16h.01',
111
+ },
112
+ // Alert triangle
113
+ 'alert-triangle': {
114
+ path: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01',
115
+ },
116
+ // Chevrons
117
+ 'chevron-down': {
118
+ path: 'M6 9l6 6 6-6',
119
+ },
120
+ 'chevron-up': {
121
+ path: 'M18 15l-6-6-6 6',
122
+ },
123
+ 'chevron-left': {
124
+ path: 'M15 18l-6-6 6-6',
125
+ },
126
+ 'chevron-right': {
127
+ path: 'M9 18l6-6-6-6',
128
+ },
129
+ // Search
130
+ search: {
131
+ path: 'M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM21 21l-4.35-4.35',
132
+ },
133
+ // Settings (gear)
134
+ settings: {
135
+ path: 'M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',
136
+ },
137
+ // User
138
+ user: {
139
+ path: 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z',
140
+ },
141
+ // Home
142
+ home: {
143
+ path: 'M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9zM9 22V12h6v10',
144
+ },
145
+ // External link
146
+ 'external-link': {
147
+ path: 'M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3',
148
+ },
149
+ // Loading spinner (animated)
150
+ loading: {
151
+ path: '', // Special case - handled separately
152
+ },
153
+ // Send
154
+ send: {
155
+ path: 'M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z',
156
+ },
157
+ // More (vertical dots)
158
+ more: {
159
+ path: '', // Special case - uses filled circles
160
+ fill: true,
161
+ },
162
+ // More horizontal (horizontal dots)
163
+ 'more-horizontal': {
164
+ path: '', // Special case - uses filled circles
165
+ fill: true,
166
+ },
167
+ // Grip (drag handle)
168
+ grip: {
169
+ path: '', // Special case - uses filled circles
170
+ fill: true,
171
+ },
172
+ // History (clock)
173
+ history: {
174
+ path: 'M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 6v6l4 2',
175
+ },
176
+ // Message bubble
177
+ message: {
178
+ path: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z',
179
+ },
180
+ // Chat bubble (simpler)
181
+ chat: {
182
+ path: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z',
183
+ },
184
+ // Eye
185
+ eye: {
186
+ path: 'M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8zM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',
187
+ },
188
+ // Eye off
189
+ 'eye-off': {
190
+ path: 'M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24M1 1l22 22',
191
+ },
192
+ // Copy
193
+ copy: {
194
+ path: 'M20 9h-9a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2zM5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1',
195
+ },
196
+ // Download
197
+ download: {
198
+ path: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3',
199
+ },
200
+ // Upload
201
+ upload: {
202
+ path: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12',
203
+ },
204
+ // Refresh
205
+ refresh: {
206
+ path: 'M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15',
207
+ },
208
+ // Arrow left
209
+ 'arrow-left': {
210
+ path: 'M19 12H5M12 19l-7-7 7-7',
211
+ },
212
+ // Arrow right
213
+ 'arrow-right': {
214
+ path: 'M5 12h14M12 5l7 7-7 7',
215
+ },
216
+ // Menu (hamburger)
217
+ menu: {
218
+ path: 'M3 12h18M3 6h18M3 18h18',
219
+ },
220
+ // Filter
221
+ filter: {
222
+ path: 'M22 3H2l8 9.46V19l4 2v-8.54L22 3z',
223
+ },
224
+ // Sort
225
+ sort: {
226
+ path: 'M11 5h10M11 9h7M11 13h4M3 17l3 3 3-3M6 18V4',
227
+ },
228
+ // Calendar
229
+ calendar: {
230
+ path: 'M19 4H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zM16 2v4M8 2v4M3 10h18',
231
+ },
232
+ // Clock
233
+ clock: {
234
+ path: 'M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 6v6l4 2',
235
+ },
236
+ // Mail
237
+ mail: {
238
+ path: 'M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6',
239
+ },
240
+ // Phone
241
+ phone: {
242
+ path: 'M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z',
243
+ },
244
+ // Link
245
+ link: {
246
+ path: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71',
247
+ },
248
+ // Image
249
+ image: {
250
+ path: 'M19 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zM8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM21 15l-5-5L5 21',
251
+ },
252
+ // File
253
+ file: {
254
+ path: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M16 13H8M16 17H8M10 9H8',
255
+ },
256
+ // Folder
257
+ folder: {
258
+ path: 'M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z',
259
+ },
260
+ // Star
261
+ star: {
262
+ path: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z',
263
+ },
264
+ // Heart
265
+ heart: {
266
+ path: 'M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z',
267
+ },
268
+ // Bookmark
269
+ bookmark: {
270
+ path: 'M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z',
271
+ },
272
+ // Lock
273
+ lock: {
274
+ path: 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2zM7 11V7a5 5 0 0 1 10 0v4',
275
+ },
276
+ // Unlock
277
+ unlock: {
278
+ path: 'M19 11H5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2zM7 11V7a5 5 0 0 1 9.9-1',
279
+ },
280
+ };
281
+
282
+ const iconDef = $derived(icons[name]);
283
+ </script>
284
+
285
+ {#if name === 'loading'}
286
+ <!-- Loading spinner with animation -->
287
+ <svg
288
+ aria-hidden={!label}
289
+ width={size}
290
+ height={size}
291
+ viewBox="0 0 24 24"
292
+ fill="none"
293
+ stroke="currentColor"
294
+ stroke-width={strokeWidth}
295
+ class={cn('animate-spin', className)}
296
+ {...restProps}
297
+ >
298
+ <circle cx="12" cy="12" r="10" stroke-opacity="0.25"></circle>
299
+ <path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"></path>
300
+ </svg>
301
+ {:else if name === 'more'}
302
+ <!-- Vertical dots -->
303
+ <svg
304
+ aria-hidden={!label}
305
+ width={size}
306
+ height={size}
307
+ viewBox="0 0 24 24"
308
+ fill="currentColor"
309
+ class={className}
310
+ {...restProps}
311
+ >
312
+ <circle cx="12" cy="5" r="1.5"></circle>
313
+ <circle cx="12" cy="12" r="1.5"></circle>
314
+ <circle cx="12" cy="19" r="1.5"></circle>
315
+ </svg>
316
+ {:else if name === 'more-horizontal'}
317
+ <!-- Horizontal dots -->
318
+ <svg
319
+ aria-hidden={!label}
320
+ width={size}
321
+ height={size}
322
+ viewBox="0 0 24 24"
323
+ fill="currentColor"
324
+ class={className}
325
+ {...restProps}
326
+ >
327
+ <circle cx="5" cy="12" r="1.5"></circle>
328
+ <circle cx="12" cy="12" r="1.5"></circle>
329
+ <circle cx="19" cy="12" r="1.5"></circle>
330
+ </svg>
331
+ {:else if name === 'grip'}
332
+ <!-- Grip handle (6 dots for drag) -->
333
+ <svg
334
+ aria-hidden={!label}
335
+ width={size}
336
+ height={size}
337
+ viewBox="0 0 24 24"
338
+ fill="currentColor"
339
+ class={className}
340
+ {...restProps}
341
+ >
342
+ <circle cx="8" cy="6" r="2"></circle>
343
+ <circle cx="16" cy="6" r="2"></circle>
344
+ <circle cx="8" cy="12" r="2"></circle>
345
+ <circle cx="16" cy="12" r="2"></circle>
346
+ <circle cx="8" cy="18" r="2"></circle>
347
+ <circle cx="16" cy="18" r="2"></circle>
348
+ </svg>
349
+ {:else if iconDef}
350
+ <!-- Standard path-based icons -->
351
+ <svg
352
+ aria-hidden={!label}
353
+ width={size}
354
+ height={size}
355
+ viewBox="0 0 24 24"
356
+ fill={iconDef.fill ? 'currentColor' : 'none'}
357
+ stroke={iconDef.fill ? 'none' : 'currentColor'}
358
+ stroke-width={iconDef.fill ? 0 : strokeWidth}
359
+ stroke-linecap="round"
360
+ stroke-linejoin="round"
361
+ class={className}
362
+ {...restProps}
363
+ >
364
+ <path d={iconDef.path}></path>
365
+ </svg>
366
+ {/if}
367
+
368
+ {#if label}
369
+ <span class="sr-only">{label}</span>
370
+ {/if}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Available icon names for the Icon component.
3
+ * These are common icons useful across applications.
4
+ */
5
+ export type IconName = 'close' | 'check' | 'plus' | 'minus' | 'edit' | 'trash' | 'info' | 'alert-circle' | 'alert-triangle' | 'chevron-down' | 'chevron-up' | 'chevron-left' | 'chevron-right' | 'search' | 'settings' | 'user' | 'home' | 'external-link' | 'loading' | 'send' | 'more' | 'more-horizontal' | 'grip' | 'history' | 'message' | 'chat' | 'eye' | 'eye-off' | 'copy' | 'download' | 'upload' | 'refresh' | 'arrow-left' | 'arrow-right' | 'menu' | 'filter' | 'sort' | 'calendar' | 'clock' | 'mail' | 'phone' | 'link' | 'image' | 'file' | 'folder' | 'star' | 'heart' | 'bookmark' | 'lock' | 'unlock';
6
+ interface Props {
7
+ /** The name of the icon to display */
8
+ name: IconName;
9
+ /** Size of the icon in pixels (default: 24) */
10
+ size?: number;
11
+ /** Additional CSS classes */
12
+ class?: string;
13
+ /** Stroke width for stroke-based icons (default: 2) */
14
+ strokeWidth?: number;
15
+ /** Accessible label for the icon (renders as visually hidden text) */
16
+ label?: string;
17
+ /** Additional attributes to spread onto the SVG element */
18
+ [key: string]: unknown;
19
+ }
20
+ declare const Icon: import("svelte").Component<Props, {}, "">;
21
+ type Icon = ReturnType<typeof Icon>;
22
+ export default Icon;
@@ -185,8 +185,10 @@
185
185
  </div>
186
186
 
187
187
  <ComboboxPrimitive.Content
188
- class="relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
189
- sideOffset={4}
188
+ class="relative z-50 w-[var(--bits-floating-anchor-width)] min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[side=bottom]:border-t-0 data-[side=bottom]:rounded-t-none data-[side=top]:border-b-0 data-[side=top]:rounded-b-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
189
+ sideOffset={-1}
190
+ collisionPadding={8}
191
+ avoidCollisions={true}
190
192
  >
191
193
  <div class="p-1 max-h-[300px] overflow-y-auto">
192
194
  {#if loading}