@aspect-ops/exon-ui 0.2.0 → 0.2.2

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 (74) hide show
  1. package/README.md +200 -0
  2. package/dist/components/Accordion/Accordion.svelte +2 -2
  3. package/dist/components/Accordion/AccordionItem.svelte +2 -2
  4. package/dist/components/AspectRatio/AspectRatio.svelte +1 -0
  5. package/dist/components/Card/FlipCard.svelte +155 -0
  6. package/dist/components/Card/FlipCard.svelte.d.ts +13 -0
  7. package/dist/components/Card/index.d.ts +1 -0
  8. package/dist/components/Card/index.js +1 -0
  9. package/dist/components/Chatbot/ChatMessage.svelte +143 -0
  10. package/dist/components/Chatbot/ChatMessage.svelte.d.ts +8 -0
  11. package/dist/components/Chatbot/Chatbot.svelte +640 -0
  12. package/dist/components/Chatbot/Chatbot.svelte.d.ts +22 -0
  13. package/dist/components/Chatbot/index.d.ts +3 -0
  14. package/dist/components/Chatbot/index.js +2 -0
  15. package/dist/components/Chatbot/types.d.ts +48 -0
  16. package/dist/components/Chatbot/types.js +2 -0
  17. package/dist/components/ContactForm/ContactForm.svelte +564 -0
  18. package/dist/components/ContactForm/ContactForm.svelte.d.ts +44 -0
  19. package/dist/components/ContactForm/index.d.ts +1 -0
  20. package/dist/components/ContactForm/index.js +1 -0
  21. package/dist/components/Container/Container.svelte +1 -0
  22. package/dist/components/DataTable/DataTable.svelte +460 -0
  23. package/dist/components/DataTable/DataTable.svelte.d.ts +49 -0
  24. package/dist/components/DataTable/index.d.ts +2 -0
  25. package/dist/components/DataTable/index.js +1 -0
  26. package/dist/components/DoughnutChart/DoughnutChart.svelte +390 -0
  27. package/dist/components/DoughnutChart/DoughnutChart.svelte.d.ts +25 -0
  28. package/dist/components/DoughnutChart/index.d.ts +1 -0
  29. package/dist/components/DoughnutChart/index.js +1 -0
  30. package/dist/components/FAB/FAB.svelte +5 -1
  31. package/dist/components/FAB/FABGroup.svelte +10 -2
  32. package/dist/components/FileUpload/FileUpload.svelte +12 -12
  33. package/dist/components/Icon/Icon.svelte +15 -18
  34. package/dist/components/Icon/Icon.svelte.d.ts +2 -1
  35. package/dist/components/Menu/MenuContent.svelte +1 -0
  36. package/dist/components/Menu/MenuSubContent.svelte +1 -0
  37. package/dist/components/Mermaid/Mermaid.svelte +320 -0
  38. package/dist/components/Mermaid/Mermaid.svelte.d.ts +38 -0
  39. package/dist/components/Mermaid/index.d.ts +1 -0
  40. package/dist/components/Mermaid/index.js +1 -0
  41. package/dist/components/Mermaid/mermaid.d.ts +21 -0
  42. package/dist/components/PageHeader/PageHeader.svelte +140 -0
  43. package/dist/components/PageHeader/PageHeader.svelte.d.ts +30 -0
  44. package/dist/components/PageHeader/index.d.ts +1 -0
  45. package/dist/components/PageHeader/index.js +1 -0
  46. package/dist/components/Popover/PopoverTrigger.svelte +1 -3
  47. package/dist/components/StatCircle/StatCircle.svelte +172 -0
  48. package/dist/components/StatCircle/StatCircle.svelte.d.ts +19 -0
  49. package/dist/components/StatCircle/index.d.ts +1 -0
  50. package/dist/components/StatCircle/index.js +1 -0
  51. package/dist/components/StatsCard/StatsCard.svelte +301 -0
  52. package/dist/components/StatsCard/StatsCard.svelte.d.ts +32 -0
  53. package/dist/components/StatsCard/index.d.ts +2 -0
  54. package/dist/components/StatsCard/index.js +1 -0
  55. package/dist/components/StatusBadge/StatusBadge.svelte +221 -0
  56. package/dist/components/StatusBadge/StatusBadge.svelte.d.ts +22 -0
  57. package/dist/components/StatusBadge/index.d.ts +2 -0
  58. package/dist/components/StatusBadge/index.js +1 -0
  59. package/dist/components/StatusBanner/StatusBanner.svelte +325 -0
  60. package/dist/components/StatusBanner/StatusBanner.svelte.d.ts +13 -0
  61. package/dist/components/StatusBanner/index.d.ts +1 -0
  62. package/dist/components/StatusBanner/index.js +1 -0
  63. package/dist/components/ViewCounter/ViewCounter.svelte +157 -0
  64. package/dist/components/ViewCounter/ViewCounter.svelte.d.ts +17 -0
  65. package/dist/components/ViewCounter/index.d.ts +1 -0
  66. package/dist/components/ViewCounter/index.js +1 -0
  67. package/dist/index.d.ts +17 -2
  68. package/dist/index.js +16 -1
  69. package/dist/styles/tokens.css +2 -1
  70. package/dist/types/data-display.d.ts +72 -0
  71. package/dist/types/feedback.d.ts +10 -0
  72. package/dist/types/index.d.ts +3 -3
  73. package/dist/types/input.d.ts +20 -0
  74. package/package.json +4 -2
@@ -0,0 +1,390 @@
1
+ <script lang="ts">
2
+ interface DataItem {
3
+ label: string;
4
+ value: number;
5
+ }
6
+
7
+ interface Props {
8
+ /** Chart title */
9
+ title?: string;
10
+ /** Data items for the chart */
11
+ data: DataItem[];
12
+ /** Chart size in pixels */
13
+ size?: number;
14
+ /** Doughnut thickness as ratio (0-1) */
15
+ thickness?: number;
16
+ /** Custom color palette */
17
+ colors?: string[];
18
+ /** Whether to show legend */
19
+ showLegend?: boolean;
20
+ /** Whether to show center total */
21
+ showTotal?: boolean;
22
+ /** Custom class */
23
+ class?: string;
24
+ }
25
+
26
+ // Default color palette
27
+ const defaultColors = [
28
+ '#8FBE6D',
29
+ '#F5D76E',
30
+ '#F7CA4F',
31
+ '#E8945A',
32
+ '#E25D5D',
33
+ '#6B5B95',
34
+ '#4A90A4',
35
+ '#5FBFBF'
36
+ ];
37
+
38
+ let {
39
+ title = '',
40
+ data = [],
41
+ size = 200,
42
+ thickness = 0.6,
43
+ colors = defaultColors,
44
+ showLegend = true,
45
+ showTotal = true,
46
+ class: className = ''
47
+ }: Props = $props();
48
+
49
+ // Calculate total
50
+ const total = $derived(data.reduce((sum, item) => sum + item.value, 0));
51
+
52
+ // Calculate arc data
53
+ interface ArcData extends DataItem {
54
+ percentage: number;
55
+ color: string;
56
+ startAngle: number;
57
+ endAngle: number;
58
+ path: string;
59
+ }
60
+
61
+ const arcs = $derived.by(() => {
62
+ if (total === 0) return [];
63
+
64
+ const result: ArcData[] = [];
65
+ let currentAngle = -90; // Start from top
66
+
67
+ data.forEach((item, index) => {
68
+ const percentage = (item.value / total) * 100;
69
+ const angle = (item.value / total) * 360;
70
+ const startAngle = currentAngle;
71
+ const endAngle = currentAngle + angle;
72
+
73
+ const path = describeArc(size / 2, size / 2, size / 2 - 10, startAngle, endAngle);
74
+
75
+ result.push({
76
+ ...item,
77
+ percentage,
78
+ color: colors[index % colors.length],
79
+ startAngle,
80
+ endAngle,
81
+ path
82
+ });
83
+
84
+ currentAngle = endAngle;
85
+ });
86
+
87
+ return result;
88
+ });
89
+
90
+ // Calculate inner radius for doughnut
91
+ const innerRadius = $derived((size / 2 - 10) * (1 - thickness));
92
+ const outerRadius = $derived(size / 2 - 10);
93
+
94
+ // Helper to convert polar to cartesian coordinates
95
+ function polarToCartesian(
96
+ centerX: number,
97
+ centerY: number,
98
+ radius: number,
99
+ angleInDegrees: number
100
+ ): { x: number; y: number } {
101
+ const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;
102
+ return {
103
+ x: centerX + radius * Math.cos(angleInRadians),
104
+ y: centerY + radius * Math.sin(angleInRadians)
105
+ };
106
+ }
107
+
108
+ // Helper to describe an arc path
109
+ function describeArc(
110
+ x: number,
111
+ y: number,
112
+ radius: number,
113
+ startAngle: number,
114
+ endAngle: number
115
+ ): string {
116
+ // Adjust angles (add 90 to start from top)
117
+ const start = polarToCartesian(x, y, radius, endAngle + 90);
118
+ const end = polarToCartesian(x, y, radius, startAngle + 90);
119
+ const innerStart = polarToCartesian(x, y, innerRadius, endAngle + 90);
120
+ const innerEnd = polarToCartesian(x, y, innerRadius, startAngle + 90);
121
+
122
+ const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';
123
+
124
+ // Create doughnut arc path
125
+ const d = [
126
+ 'M',
127
+ start.x,
128
+ start.y,
129
+ 'A',
130
+ radius,
131
+ radius,
132
+ 0,
133
+ largeArcFlag,
134
+ 0,
135
+ end.x,
136
+ end.y,
137
+ 'L',
138
+ innerEnd.x,
139
+ innerEnd.y,
140
+ 'A',
141
+ innerRadius,
142
+ innerRadius,
143
+ 0,
144
+ largeArcFlag,
145
+ 1,
146
+ innerStart.x,
147
+ innerStart.y,
148
+ 'Z'
149
+ ].join(' ');
150
+
151
+ return d;
152
+ }
153
+
154
+ // Format number for display
155
+ function formatNumber(num: number): string {
156
+ if (num >= 1000000) {
157
+ return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
158
+ }
159
+ if (num >= 1000) {
160
+ return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
161
+ }
162
+ return num.toLocaleString();
163
+ }
164
+
165
+ // Hovered arc index
166
+ let hoveredIndex = $state<number | null>(null);
167
+ </script>
168
+
169
+ <div class="doughnut-chart {className}">
170
+ {#if title}
171
+ <h3 class="doughnut-chart__title">{title}</h3>
172
+ {/if}
173
+
174
+ <div class="doughnut-chart__content">
175
+ <!-- SVG Chart -->
176
+ <div class="doughnut-chart__svg-container">
177
+ <svg
178
+ width={size}
179
+ height={size}
180
+ viewBox="0 0 {size} {size}"
181
+ class="doughnut-chart__svg"
182
+ role="img"
183
+ aria-label={title || 'Doughnut chart'}
184
+ >
185
+ {#each arcs as arc, index}
186
+ <path
187
+ d={arc.path}
188
+ fill={arc.color}
189
+ class="doughnut-chart__arc"
190
+ class:doughnut-chart__arc--hovered={hoveredIndex === index}
191
+ onmouseenter={() => (hoveredIndex = index)}
192
+ onmouseleave={() => (hoveredIndex = null)}
193
+ role="presentation"
194
+ >
195
+ <title>{arc.label}: {formatNumber(arc.value)} ({arc.percentage.toFixed(1)}%)</title>
196
+ </path>
197
+
198
+ <!-- Percentage label on arc -->
199
+ {#if arc.percentage >= 5}
200
+ {@const midAngle = (arc.startAngle + arc.endAngle) / 2}
201
+ {@const labelRadius = (outerRadius + innerRadius) / 2}
202
+ {@const labelPos = polarToCartesian(size / 2, size / 2, labelRadius, midAngle + 90)}
203
+ <text
204
+ x={labelPos.x}
205
+ y={labelPos.y}
206
+ text-anchor="middle"
207
+ dominant-baseline="middle"
208
+ class="doughnut-chart__label"
209
+ fill="#ffffff"
210
+ >
211
+ {arc.percentage.toFixed(0)}%
212
+ </text>
213
+ {/if}
214
+ {/each}
215
+
216
+ <!-- Background circle for center content -->
217
+ {#if showTotal}
218
+ <circle
219
+ cx={size / 2}
220
+ cy={size / 2}
221
+ r={innerRadius * 0.95}
222
+ fill="var(--color-bg, #ffffff)"
223
+ class="doughnut-chart__center-bg"
224
+ />
225
+ {/if}
226
+ </svg>
227
+
228
+ <!-- Center total -->
229
+ {#if showTotal}
230
+ <div class="doughnut-chart__center">
231
+ <span class="doughnut-chart__total">{formatNumber(total)}</span>
232
+ <span class="doughnut-chart__total-label">Total</span>
233
+ </div>
234
+ {/if}
235
+ </div>
236
+
237
+ <!-- Legend -->
238
+ {#if showLegend}
239
+ <ul class="doughnut-chart__legend">
240
+ {#each arcs as arc, index}
241
+ <li
242
+ class="doughnut-chart__legend-item"
243
+ class:doughnut-chart__legend-item--hovered={hoveredIndex === index}
244
+ onmouseenter={() => (hoveredIndex = index)}
245
+ onmouseleave={() => (hoveredIndex = null)}
246
+ >
247
+ <span class="doughnut-chart__legend-color" style="background-color: {arc.color}"></span>
248
+ <span class="doughnut-chart__legend-label">{arc.label}</span>
249
+ <span class="doughnut-chart__legend-value">{formatNumber(arc.value)}</span>
250
+ </li>
251
+ {/each}
252
+ </ul>
253
+ {/if}
254
+ </div>
255
+ </div>
256
+
257
+ <style>
258
+ .doughnut-chart {
259
+ font-family: inherit;
260
+ }
261
+
262
+ .doughnut-chart__title {
263
+ font-size: var(--text-lg, 1.125rem);
264
+ font-weight: var(--font-semibold, 600);
265
+ color: var(--color-text, #1f2937);
266
+ margin: 0 0 var(--space-md, 1rem);
267
+ }
268
+
269
+ .doughnut-chart__content {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: var(--space-xl, 2rem);
273
+ flex-wrap: wrap;
274
+ }
275
+
276
+ /* SVG Container */
277
+ .doughnut-chart__svg-container {
278
+ position: relative;
279
+ flex-shrink: 0;
280
+ }
281
+
282
+ .doughnut-chart__svg {
283
+ display: block;
284
+ }
285
+
286
+ /* Arc */
287
+ .doughnut-chart__arc {
288
+ cursor: pointer;
289
+ transition: opacity var(--transition-fast, 150ms ease);
290
+ }
291
+
292
+ .doughnut-chart__arc--hovered {
293
+ opacity: 0.85;
294
+ filter: brightness(1.1);
295
+ }
296
+
297
+ /* Arc Label */
298
+ .doughnut-chart__label {
299
+ font-size: var(--text-xs, 0.75rem);
300
+ font-weight: var(--font-semibold, 600);
301
+ pointer-events: none;
302
+ }
303
+
304
+ /* Center Background */
305
+ .doughnut-chart__center-bg {
306
+ filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
307
+ }
308
+
309
+ /* Center */
310
+ .doughnut-chart__center {
311
+ position: absolute;
312
+ top: 50%;
313
+ left: 50%;
314
+ transform: translate(-50%, -50%);
315
+ text-align: center;
316
+ pointer-events: none;
317
+ z-index: 10;
318
+ }
319
+
320
+ .doughnut-chart__total {
321
+ display: block;
322
+ font-size: var(--text-lg, 1.125rem);
323
+ font-weight: var(--font-semibold, 600);
324
+ color: var(--color-text, #1f2937);
325
+ line-height: 1.2;
326
+ }
327
+
328
+ .doughnut-chart__total-label {
329
+ display: block;
330
+ font-size: var(--text-xs, 0.75rem);
331
+ color: var(--color-text-muted, #6b7280);
332
+ text-transform: uppercase;
333
+ letter-spacing: 0.05em;
334
+ margin-top: 2px;
335
+ }
336
+
337
+ /* Legend */
338
+ .doughnut-chart__legend {
339
+ list-style: none;
340
+ margin: 0;
341
+ padding: 0;
342
+ display: flex;
343
+ flex-direction: column;
344
+ gap: var(--space-sm, 0.5rem);
345
+ }
346
+
347
+ .doughnut-chart__legend-item {
348
+ display: flex;
349
+ align-items: center;
350
+ gap: var(--space-sm, 0.5rem);
351
+ padding: var(--space-xs, 0.25rem) 0;
352
+ cursor: pointer;
353
+ transition: opacity var(--transition-fast, 150ms ease);
354
+ }
355
+
356
+ .doughnut-chart__legend-item--hovered {
357
+ opacity: 0.85;
358
+ }
359
+
360
+ .doughnut-chart__legend-color {
361
+ width: 12px;
362
+ height: 12px;
363
+ border-radius: 2px;
364
+ flex-shrink: 0;
365
+ }
366
+
367
+ .doughnut-chart__legend-label {
368
+ flex: 1;
369
+ font-size: var(--text-sm, 0.875rem);
370
+ color: var(--color-text, #1f2937);
371
+ }
372
+
373
+ .doughnut-chart__legend-value {
374
+ font-size: var(--text-sm, 0.875rem);
375
+ font-weight: var(--font-medium, 500);
376
+ color: var(--color-text-muted, #6b7280);
377
+ }
378
+
379
+ /* Responsive */
380
+ @media (max-width: 480px) {
381
+ .doughnut-chart__content {
382
+ flex-direction: column;
383
+ align-items: center;
384
+ }
385
+
386
+ .doughnut-chart__legend {
387
+ width: 100%;
388
+ }
389
+ }
390
+ </style>
@@ -0,0 +1,25 @@
1
+ interface DataItem {
2
+ label: string;
3
+ value: number;
4
+ }
5
+ interface Props {
6
+ /** Chart title */
7
+ title?: string;
8
+ /** Data items for the chart */
9
+ data: DataItem[];
10
+ /** Chart size in pixels */
11
+ size?: number;
12
+ /** Doughnut thickness as ratio (0-1) */
13
+ thickness?: number;
14
+ /** Custom color palette */
15
+ colors?: string[];
16
+ /** Whether to show legend */
17
+ showLegend?: boolean;
18
+ /** Whether to show center total */
19
+ showTotal?: boolean;
20
+ /** Custom class */
21
+ class?: string;
22
+ }
23
+ declare const DoughnutChart: import("svelte").Component<Props, {}, "">;
24
+ type DoughnutChart = ReturnType<typeof DoughnutChart>;
25
+ export default DoughnutChart;
@@ -0,0 +1 @@
1
+ export { default as DoughnutChart } from './DoughnutChart.svelte';
@@ -0,0 +1 @@
1
+ export { default as DoughnutChart } from './DoughnutChart.svelte';
@@ -176,8 +176,12 @@
176
176
 
177
177
  /* Focus state */
178
178
  .fab:focus-visible {
179
- outline: 2px solid var(--color-primary, #3b82f6);
179
+ outline: 2px solid var(--color-text-inverse, #ffffff);
180
180
  outline-offset: 2px;
181
+ box-shadow:
182
+ 0 0 0 4px var(--color-primary-active, #1d4ed8),
183
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
184
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1);
181
185
  }
182
186
 
183
187
  /* Disabled state */
@@ -240,8 +240,12 @@
240
240
  }
241
241
 
242
242
  .fab-group__trigger:focus-visible {
243
- outline: 2px solid var(--color-primary, #3b82f6);
243
+ outline: 2px solid var(--color-text-inverse, #ffffff);
244
244
  outline-offset: 2px;
245
+ box-shadow:
246
+ 0 0 0 4px var(--color-primary-active, #1d4ed8),
247
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
248
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1);
245
249
  }
246
250
 
247
251
  /* Rotation when open */
@@ -364,8 +368,12 @@
364
368
  }
365
369
 
366
370
  .fab-group__action:focus-visible {
367
- outline: 2px solid var(--color-primary, #3b82f6);
371
+ outline: 2px solid var(--color-primary, #0090ff);
368
372
  outline-offset: 2px;
373
+ box-shadow:
374
+ 0 0 0 4px rgba(0, 144, 255, 0.2),
375
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
376
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1);
369
377
  }
370
378
 
371
379
  /* Action icon */
@@ -328,13 +328,13 @@
328
328
  text-align: center;
329
329
  cursor: pointer;
330
330
  transition: all 0.2s ease;
331
- background-color: var(--color-background, #ffffff);
331
+ background-color: var(--color-bg, #ffffff);
332
332
  min-height: var(--touch-target-min, 44px);
333
333
  }
334
334
 
335
335
  .drop-zone:hover:not(.disabled) {
336
336
  border-color: var(--color-primary, #3b82f6);
337
- background-color: var(--color-background-hover, #f9fafb);
337
+ background-color: var(--color-bg-hover, #f9fafb);
338
338
  }
339
339
 
340
340
  .drop-zone:focus-visible {
@@ -344,7 +344,7 @@
344
344
 
345
345
  .drop-zone.dragging {
346
346
  border-color: var(--color-primary, #3b82f6);
347
- background-color: var(--color-primary-light, #eff6ff);
347
+ background-color: var(--color-primary-bg, #eff6ff);
348
348
  }
349
349
 
350
350
  .drop-zone.has-files {
@@ -365,7 +365,7 @@
365
365
  }
366
366
 
367
367
  .upload-icon {
368
- color: var(--color-text-secondary, #6b7280);
368
+ color: var(--color-text-muted, #6b7280);
369
369
  }
370
370
 
371
371
  .instructions {
@@ -378,7 +378,7 @@
378
378
  .hint {
379
379
  margin: 0;
380
380
  font-size: var(--text-sm, 0.875rem);
381
- color: var(--color-text-secondary, #6b7280);
381
+ color: var(--color-text-muted, #6b7280);
382
382
  }
383
383
 
384
384
  .file-list {
@@ -395,7 +395,7 @@
395
395
  padding: var(--space-3, 0.75rem);
396
396
  border: 1px solid var(--color-border, #e5e7eb);
397
397
  border-radius: var(--radius-md, 8px);
398
- background-color: var(--color-background, #ffffff);
398
+ background-color: var(--color-bg, #ffffff);
399
399
  }
400
400
 
401
401
  .file-preview {
@@ -406,7 +406,7 @@
406
406
  align-items: center;
407
407
  justify-content: center;
408
408
  border-radius: var(--radius-sm, 4px);
409
- background-color: var(--color-background-secondary, #f3f4f6);
409
+ background-color: var(--color-bg-muted, #f3f4f6);
410
410
  overflow: hidden;
411
411
  }
412
412
 
@@ -417,7 +417,7 @@
417
417
  }
418
418
 
419
419
  .file-icon {
420
- color: var(--color-text-secondary, #6b7280);
420
+ color: var(--color-text-muted, #6b7280);
421
421
  }
422
422
 
423
423
  .file-info {
@@ -439,7 +439,7 @@
439
439
 
440
440
  .file-size {
441
441
  font-size: var(--text-xs, 0.75rem);
442
- color: var(--color-text-secondary, #6b7280);
442
+ color: var(--color-text-muted, #6b7280);
443
443
  }
444
444
 
445
445
  .remove-btn {
@@ -451,15 +451,15 @@
451
451
  justify-content: center;
452
452
  border: none;
453
453
  background: transparent;
454
- color: var(--color-text-secondary, #6b7280);
454
+ color: var(--color-text-muted, #6b7280);
455
455
  cursor: pointer;
456
456
  border-radius: var(--radius-sm, 4px);
457
457
  transition: all 0.2s ease;
458
458
  }
459
459
 
460
460
  .remove-btn:hover:not(:disabled) {
461
- background-color: var(--color-error-light, #fef2f2);
462
- color: var(--color-error, #ef4444);
461
+ background-color: var(--color-error-bg, #fef2f2);
462
+ color: var(--color-destructive, #ef4444);
463
463
  }
464
464
 
465
465
  .remove-btn:focus-visible {
@@ -1,8 +1,9 @@
1
1
  <script lang="ts">
2
2
  import type { IconSize } from '../../types/index.js';
3
+ import type { ComponentType } from 'svelte';
3
4
 
4
5
  interface Props {
5
- name: string;
6
+ icon: ComponentType;
6
7
  size?: IconSize;
7
8
  class?: string;
8
9
  'aria-label'?: string;
@@ -10,38 +11,34 @@
10
11
  }
11
12
 
12
13
  let {
13
- name,
14
+ icon: IconComponent,
14
15
  size = 'md',
15
16
  class: className = '',
16
17
  'aria-label': ariaLabel,
17
18
  'aria-hidden': ariaHidden = !ariaLabel
18
19
  }: Props = $props();
19
20
 
20
- // Size map aligned with typography scale
21
- const sizeMap: Record<IconSize, string> = {
22
- xs: '0.75rem',
23
- sm: '1rem',
24
- md: '1.25rem',
25
- lg: '1.5rem',
26
- xl: '2rem'
21
+ // Size map aligned with typography scale (in pixels for Lucide)
22
+ const sizeMap: Record<IconSize, number> = {
23
+ xs: 12,
24
+ sm: 16,
25
+ md: 20,
26
+ lg: 24,
27
+ xl: 32
27
28
  };
28
29
  </script>
29
30
 
30
- <svg
31
+ <IconComponent
32
+ size={sizeMap[size]}
31
33
  class="icon icon--{size} {className}"
32
- style="--icon-size: {sizeMap[size]}"
33
34
  aria-label={ariaLabel}
34
35
  aria-hidden={ariaHidden}
35
36
  role={ariaLabel ? 'img' : undefined}
36
- >
37
- <use href="#{name}" />
38
- </svg>
37
+ />
39
38
 
40
39
  <style>
41
- .icon {
42
- width: var(--icon-size);
43
- height: var(--icon-size);
44
- fill: currentColor;
40
+ :global(.icon) {
45
41
  flex-shrink: 0;
42
+ color: currentColor;
46
43
  }
47
44
  </style>
@@ -1,6 +1,7 @@
1
1
  import type { IconSize } from '../../types/index.js';
2
+ import type { ComponentType } from 'svelte';
2
3
  interface Props {
3
- name: string;
4
+ icon: ComponentType;
4
5
  size?: IconSize;
5
6
  class?: string;
6
7
  'aria-label'?: string;
@@ -36,6 +36,7 @@
36
36
  z-index: 50;
37
37
  min-width: 8rem;
38
38
  max-width: 20rem;
39
+ overflow: hidden;
39
40
  }
40
41
 
41
42
  /* Animation states */
@@ -25,6 +25,7 @@
25
25
  z-index: 51;
26
26
  min-width: 8rem;
27
27
  max-width: 20rem;
28
+ overflow: hidden;
28
29
  }
29
30
 
30
31
  /* Animation states */