@reshape-biotech/design-system 2.7.34 → 2.7.36

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 (37) hide show
  1. package/dist/app.css +2 -2
  2. package/dist/components/button/Button.stories.svelte +27 -7
  3. package/dist/components/button/Button.svelte +107 -24
  4. package/dist/components/combobox/Combobox.stories.svelte +483 -89
  5. package/dist/components/combobox/components/combobox-add.svelte +10 -4
  6. package/dist/components/combobox/components/combobox-content.svelte +35 -58
  7. package/dist/components/combobox/components/combobox-indicator.svelte +1 -1
  8. package/dist/components/combobox/types.d.ts +1 -0
  9. package/dist/components/dropdown/Dropdown.stories.svelte +36 -16
  10. package/dist/components/dropdown/components/dropdown-content.svelte +6 -3
  11. package/dist/components/dropdown/components/dropdown-item.svelte +8 -1
  12. package/dist/components/dropdown/components/dropdown-separator.svelte +10 -0
  13. package/dist/components/dropdown/components/dropdown-separator.svelte.d.ts +5 -0
  14. package/dist/components/dropdown/components/dropdown-sub-content.svelte +4 -2
  15. package/dist/components/dropdown/components/dropdown-sub-trigger.svelte +10 -3
  16. package/dist/components/dropdown/index.d.ts +2 -2
  17. package/dist/components/dropdown/index.js +2 -2
  18. package/dist/components/dropdown/types.d.ts +1 -0
  19. package/dist/components/icon-button/IconButton.svelte +1 -1
  20. package/dist/components/icons/AnalysisIcon.svelte +7 -7
  21. package/dist/components/modal/Modal.stories.svelte +3 -0
  22. package/dist/components/modal/components/modal-title.svelte +7 -2
  23. package/dist/components/modal/types.d.ts +1 -0
  24. package/dist/components/select/Select.stories.svelte +158 -13
  25. package/dist/components/select/components/SelectContent.svelte +2 -2
  26. package/dist/components/select/components/SelectGroupHeading.svelte +1 -1
  27. package/dist/components/select/components/SelectItem.svelte +1 -1
  28. package/dist/components/select/components/SelectTrigger.svelte +13 -5
  29. package/dist/components/select/components/SelectTrigger.svelte.d.ts +1 -0
  30. package/dist/components/stat-card/StatCard.stories.svelte +113 -47
  31. package/dist/components/stat-card/StatCard.svelte +27 -6
  32. package/dist/components/stat-card/StatCard.svelte.d.ts +4 -0
  33. package/dist/components/status-badge/StatusBadge.svelte +4 -3
  34. package/dist/components/stepper/components/stepper-step.svelte +3 -3
  35. package/dist/tokens.d.ts +11 -1
  36. package/dist/tokens.js +13 -3
  37. package/package.json +1 -1
@@ -2,6 +2,11 @@
2
2
  import { defineMeta } from '@storybook/addon-svelte-csf';
3
3
  import * as Select from './index';
4
4
  import { Icon } from '../icons';
5
+ import Hash from 'phosphor-svelte/lib/Hash';
6
+ import Ruler from 'phosphor-svelte/lib/Ruler';
7
+ import Timer from 'phosphor-svelte/lib/Timer';
8
+ import ChartLine from 'phosphor-svelte/lib/ChartLine';
9
+ import Database from 'phosphor-svelte/lib/Database';
5
10
 
6
11
  const { Story } = defineMeta({
7
12
  title: 'Design System/Select(New)',
@@ -33,6 +38,8 @@
33
38
  let selectedValueMultiple = $state<string[]>([]);
34
39
  let selectedValueSinglePreselected = $state<string | undefined>('vegetables');
35
40
  let selectedValueMultiplePreselected = $state<string[]>(['fruits', 'grains']);
41
+ let selectedValueSm = $state<string | undefined>(undefined);
42
+ let selectedValueMd = $state<string | undefined>(undefined);
36
43
 
37
44
  const customIconItems = [
38
45
  { value: 'insect_count', label: 'Insect count', iconName: 'Hash' },
@@ -42,6 +49,15 @@
42
49
  ];
43
50
  let selectedCustomItems = $state<string[]>(['insect_count', 'leaf_area']);
44
51
 
52
+ const itemsWithIcons = [
53
+ { value: 'analytics', label: 'Analytics', icon: Hash },
54
+ { value: 'measurements', label: 'Measurements', icon: Ruler },
55
+ { value: 'tracking', label: 'Time Tracking', icon: Timer },
56
+ { value: 'charts', label: 'Charts', icon: ChartLine },
57
+ { value: 'database', label: 'Database', icon: Database },
58
+ ];
59
+ let selectedValueWithIcons = $state<string | undefined>(undefined);
60
+
45
61
  const handleRemoveCustomItem = (itemValue: string) => {
46
62
  selectedCustomItems = selectedCustomItems.filter((v) => v !== itemValue);
47
63
  };
@@ -53,8 +69,102 @@
53
69
  (v) => v !== itemValue
54
70
  );
55
71
  };
72
+
73
+ // Generate a long list of countries to test scrolling
74
+ const countries = [
75
+ { value: 'us', label: 'United States' },
76
+ { value: 'uk', label: 'United Kingdom' },
77
+ { value: 'ca', label: 'Canada' },
78
+ { value: 'au', label: 'Australia' },
79
+ { value: 'de', label: 'Germany' },
80
+ { value: 'fr', label: 'France' },
81
+ { value: 'it', label: 'Italy' },
82
+ { value: 'es', label: 'Spain' },
83
+ { value: 'jp', label: 'Japan' },
84
+ { value: 'cn', label: 'China' },
85
+ { value: 'in', label: 'India' },
86
+ { value: 'br', label: 'Brazil' },
87
+ { value: 'mx', label: 'Mexico' },
88
+ { value: 'za', label: 'South Africa' },
89
+ { value: 'eg', label: 'Egypt' },
90
+ { value: 'ng', label: 'Nigeria' },
91
+ { value: 'ar', label: 'Argentina' },
92
+ { value: 'cl', label: 'Chile' },
93
+ { value: 'co', label: 'Colombia' },
94
+ { value: 'pe', label: 'Peru' },
95
+ { value: 'se', label: 'Sweden' },
96
+ { value: 'no', label: 'Norway' },
97
+ { value: 'dk', label: 'Denmark' },
98
+ { value: 'fi', label: 'Finland' },
99
+ { value: 'pl', label: 'Poland' },
100
+ { value: 'nl', label: 'Netherlands' },
101
+ { value: 'be', label: 'Belgium' },
102
+ { value: 'ch', label: 'Switzerland' },
103
+ { value: 'at', label: 'Austria' },
104
+ { value: 'gr', label: 'Greece' },
105
+ { value: 'pt', label: 'Portugal' },
106
+ { value: 'ie', label: 'Ireland' },
107
+ { value: 'nz', label: 'New Zealand' },
108
+ { value: 'sg', label: 'Singapore' },
109
+ { value: 'my', label: 'Malaysia' },
110
+ { value: 'th', label: 'Thailand' },
111
+ { value: 'id', label: 'Indonesia' },
112
+ { value: 'ph', label: 'Philippines' },
113
+ { value: 'vn', label: 'Vietnam' },
114
+ { value: 'kr', label: 'South Korea' },
115
+ ];
116
+ let selectedCountry = $state<string | undefined>(undefined);
56
117
  </script>
57
118
 
119
+ <Story name="Size Variants" asChild>
120
+ <div class="p-4">
121
+ <div class="flex flex-col gap-6">
122
+ <div class="flex flex-col gap-2">
123
+ <p class="text-sm font-medium text-secondary">Small (sm)</p>
124
+ <Select.Root bind:value={selectedValueSm} items={foodGroups} type="single">
125
+ <Select.Trigger
126
+ class="w-[250px]"
127
+ size="sm"
128
+ placeholder={'Select a food group'}
129
+ displayValue={foodGroups.find((f) => f.value === selectedValueSm)?.label}
130
+ />
131
+ <Select.Portal>
132
+ <Select.Content>
133
+ {#each foodGroups as item (item.value)}
134
+ <Select.Item value={item.value} label={item.label} />
135
+ {/each}
136
+ </Select.Content>
137
+ </Select.Portal>
138
+ </Select.Root>
139
+ <div class="text-xs text-tertiary">
140
+ Selected: {selectedValueSm ?? 'Nothing'}
141
+ </div>
142
+ </div>
143
+ <div class="flex flex-col gap-2">
144
+ <p class="text-sm font-medium text-secondary">Medium (md) - Default</p>
145
+ <Select.Root bind:value={selectedValueMd} items={foodGroups} type="single">
146
+ <Select.Trigger
147
+ class="w-[250px]"
148
+ size="md"
149
+ placeholder={'Select a food group'}
150
+ displayValue={foodGroups.find((f) => f.value === selectedValueMd)?.label}
151
+ />
152
+ <Select.Portal>
153
+ <Select.Content>
154
+ {#each foodGroups as item (item.value)}
155
+ <Select.Item value={item.value} label={item.label} />
156
+ {/each}
157
+ </Select.Content>
158
+ </Select.Portal>
159
+ </Select.Root>
160
+ <div class="text-xs text-tertiary">
161
+ Selected: {selectedValueMd ?? 'Nothing'}
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </Story>
167
+
58
168
  <Story name="Default (Single Select)" asChild>
59
169
  <div class="p-4">
60
170
  <Select.Root bind:value={selectedValueSingle} items={foodGroups} type="single" open>
@@ -187,23 +297,33 @@
187
297
  </div>
188
298
  </Story>
189
299
 
190
- <Story name="Multiple Select with Custom Icons and Dismissible Tags" asChild>
300
+ <Story name="With Icons (Single Select)" asChild>
191
301
  <div class="p-4">
192
- <Select.Root bind:value={selectedCustomItems} items={customIconItems} type="multiple">
193
- <Select.MultiSelectTrigger
194
- class="w-[350px]"
195
- selectedValues={selectedCustomItems}
196
- items={customIconItems}
197
- placeholder="Select metrics..."
198
- onRemoveItem={handleRemoveCustomItem}
199
- />
302
+ <Select.Root bind:value={selectedValueWithIcons} items={itemsWithIcons} type="single">
303
+ <Select.Trigger class="w-[250px]" placeholder={'Select an option'}>
304
+ {#snippet children()}
305
+ {#if selectedValueWithIcons}
306
+ {@const selectedItem = itemsWithIcons.find((item) => item.value === selectedValueWithIcons)}
307
+ {#if selectedItem}
308
+ <div class="flex items-center gap-2 pl-1">
309
+ <Icon icon={selectedItem.icon} color="secondary" />
310
+ <span>{selectedItem.label}</span>
311
+ </div>
312
+ {:else}
313
+ <span class="text-tertiary pl-1">Select an option</span>
314
+ {/if}
315
+ {:else}
316
+ <span class="text-tertiary pl-1">Select an option</span>
317
+ {/if}
318
+ {/snippet}
319
+ </Select.Trigger>
200
320
  <Select.Portal>
201
321
  <Select.Content>
202
- {#each customIconItems as item (item.value)}
322
+ {#each itemsWithIcons as item (item.value)}
203
323
  <Select.Item value={item.value} label={item.label}>
204
324
  {#snippet children()}
205
325
  <div class="flex items-center gap-2">
206
- <Icon icon={item.iconName as any} class="h-4 w-4" />
326
+ <Icon icon={item.icon} color="secondary" />
207
327
  <span>{item.label}</span>
208
328
  </div>
209
329
  {/snippet}
@@ -212,8 +332,33 @@
212
332
  </Select.Content>
213
333
  </Select.Portal>
214
334
  </Select.Root>
215
- <div class="mt-2 rounded bg-neutral p-2 text-sm">
216
- Selected Values: {selectedCustomItems.join(', ') || 'Nothing'}
335
+ <div class="mt-2 rounded bg-gray-100 p-2 text-sm">
336
+ Selected: {selectedValueWithIcons ?? 'Nothing'}
337
+ </div>
338
+ </div>
339
+ </Story>
340
+
341
+ <Story name="Long List with Scrolling" asChild>
342
+ <div class="p-4">
343
+ <Select.Root bind:value={selectedCountry} items={countries} type="single">
344
+ <Select.Trigger
345
+ class="w-[250px]"
346
+ placeholder={'Select a country'}
347
+ displayValue={countries.find((c) => c.value === selectedCountry)?.label}
348
+ />
349
+ <Select.Portal>
350
+ <Select.Content>
351
+ {#each countries as item (item.value)}
352
+ <Select.Item value={item.value} label={item.label} />
353
+ {/each}
354
+ </Select.Content>
355
+ </Select.Portal>
356
+ </Select.Root>
357
+ <div class="mt-2 rounded bg-gray-100 p-2 text-sm">
358
+ Selected: {selectedCountry ?? 'Nothing'}
359
+ </div>
360
+ <div class="mt-2 rounded bg-blue-50 p-2 text-xs text-blue-700">
361
+ This story demonstrates the 80vh max-height with overflow scrolling for long lists (40 items).
217
362
  </div>
218
363
  </div>
219
364
  </Story>
@@ -21,14 +21,14 @@
21
21
  {...restProps}
22
22
  {sideOffset}
23
23
  class={twMerge(
24
- 'relative z-50 w-[var(--bits-select-anchor-width)] min-w-[8rem] overflow-hidden rounded-lg bg-surface text-primary shadow-menu',
24
+ 'relative z-50 w-[var(--bits-select-anchor-width)] min-w-[8rem] max-h-[min(calc(var(--bits-select-content-available-height)-40px),800px)] overflow-hidden rounded-lg bg-surface text-primary shadow-menu',
25
25
  className
26
26
  )}
27
27
  >
28
28
  <SelectPrimitive.ScrollUpButton class="flex justify-center">
29
29
  <Icon color="tertiary" icon={CaretUp} />
30
30
  </SelectPrimitive.ScrollUpButton>
31
- <SelectPrimitive.Viewport class="p-1 ">
31
+ <SelectPrimitive.Viewport class="p-1 overflow-y-auto">
32
32
  {@render children()}
33
33
  </SelectPrimitive.Viewport>
34
34
  <SelectPrimitive.ScrollDownButton class="flex justify-center">
@@ -11,7 +11,7 @@
11
11
 
12
12
  let { class: className = '', children, ...restProps }: Props = $props();
13
13
 
14
- const baseClasses = 'py-1.5 pl-2 pr-2 text-sm font-semibold text-muted-foreground';
14
+ const baseClasses = 'py-1.5 pl-2 pr-2 text-label font-medium text-tertiary';
15
15
  </script>
16
16
 
17
17
  <SelectPrimitive.GroupHeading {...restProps} class="{baseClasses} {className}">
@@ -14,7 +14,7 @@
14
14
  let { class: className = '', children, ...restProps }: Props = $props();
15
15
 
16
16
  const baseClasses = `
17
- relative flex w-full h-auto gap-2 cursor-default select-none items-center justify-between rounded-md p-3 text-sm outline-none
17
+ relative flex w-full h-auto min-h-10 gap-2 cursor-default select-none items-center justify-between rounded-md px-3 py-2 text-sm outline-none cursor-pointer
18
18
  focus:bg-neutral focus:text-accent-foreground
19
19
  data-[disabled]:pointer-events-none data-[disabled]:opacity-50
20
20
  data-[highlighted]:bg-neutral data-[highlighted]:text-accent-foreground
@@ -12,6 +12,7 @@
12
12
  class?: string;
13
13
  placeholder?: string;
14
14
  displayValue?: string;
15
+ size?: 'sm' | 'md';
15
16
  }
16
17
 
17
18
  let {
@@ -20,25 +21,32 @@
20
21
  children,
21
22
  placeholder = 'Select an option',
22
23
  displayValue = undefined,
24
+ size = 'md',
23
25
  ...restProps
24
26
  }: Props = $props();
25
27
 
26
- const baseClasses =
27
- 'flex h-10 w-full items-center justify-between !rounded-lg border border-input bg-surface p-2 text-sm placeholder:text-tertiary focus:outline-none hover:border-hover focus:border-accent focus:ring-0 disabled:cursor-not-allowed disabled:border-transparent disabled:bg-neutral disabled:opacity-75 transition-colors shadow-input';
28
+ const sizeClasses = {
29
+ sm: 'h-8 p-1',
30
+ md: 'h-10 p-1.5',
31
+ };
32
+
33
+ const baseClasses = $derived(
34
+ `flex w-full items-center justify-between !rounded-lg border border-input bg-surface text-sm placeholder:text-tertiary focus:outline-none hover:border-hover focus:border-accent focus:ring-0 disabled:cursor-not-allowed disabled:border-transparent disabled:bg-neutral disabled:opacity-75 transition-colors shadow-input ${sizeClasses[size]}`
35
+ );
28
36
  </script>
29
37
 
30
38
  <SelectPrimitive.Trigger
31
39
  {...restProps}
32
- class={baseClasses + ' ' + className}
40
+ class={`${baseClasses} ${className}`}
33
41
  aria-label={placeholder}
34
42
  >
35
43
  <div class="flex-1 truncate text-left">
36
44
  {#if children}
37
45
  {@render children()}
38
46
  {:else if displayValue !== undefined}
39
- {displayValue}
47
+ <span class="pl-1">{displayValue}</span>
40
48
  {:else}
41
- <span class="text-tertiary">{placeholder}</span>
49
+ <span class="text-tertiary pl-1">{placeholder}</span>
42
50
  {/if}
43
51
  </div>
44
52
  {#if TriggerIcon}
@@ -6,6 +6,7 @@ interface Props extends TriggerProps {
6
6
  class?: string;
7
7
  placeholder?: string;
8
8
  displayValue?: string;
9
+ size?: 'sm' | 'md';
9
10
  }
10
11
  declare const SelectTrigger: import("svelte").Component<Props, {}, "">;
11
12
  type SelectTrigger = ReturnType<typeof SelectTrigger>;
@@ -1,6 +1,12 @@
1
1
  <script module>
2
2
  import StatCard from './StatCard.svelte';
3
3
  import { defineMeta } from '@storybook/addon-svelte-csf';
4
+ import Circle from 'phosphor-svelte/lib/Circle';
5
+ import TrendUp from 'phosphor-svelte/lib/TrendUp';
6
+ import TrendDown from 'phosphor-svelte/lib/TrendDown';
7
+ import CheckCircle from 'phosphor-svelte/lib/CheckCircle';
8
+ import WarningCircle from 'phosphor-svelte/lib/WarningCircle';
9
+ import { Icon } from '../icons';
4
10
 
5
11
  const { Story } = defineMeta({
6
12
  component: StatCard,
@@ -9,55 +15,115 @@
9
15
  });
10
16
  </script>
11
17
 
12
- <Story
13
- name="Default"
14
- args={{ title: 'Title', value: 'Value', unit: 'unit', titleTooltip: 'Title tooltip' }}
15
- />
16
-
17
- <Story
18
- name="Germination Rate"
19
- args={{
20
- title: 'Germination Rate',
21
- value: '2',
22
- unit: '%',
23
- titleTooltip: 'Something that explains what this is...',
24
- showTitleTooltip: true,
25
- }}
26
- />
27
-
28
- <Story name="Home Page" args={{ title: 'Current jobs', value: '3', unit: 'of 5' }} />
29
-
30
- <Story
31
- name="Editable"
32
- args={{
33
- title: 'Germination Rate',
34
- value: '2',
35
- unit: '%',
36
- titleTooltip: 'Something that explains what this is...',
37
- showTitleTooltip: true,
38
- editable: true,
39
- }}
40
- />
41
-
42
- <Story
43
- name="Primary Variant"
44
- args={{
45
- title: 'Primary Stat',
46
- value: '42',
47
- unit: 'units',
48
- variant: 'primary',
49
- }}
50
- />
51
-
52
- <Story name="Size Variants" asChild>
18
+ <Story name="Basic" args={{ title: 'Total Users', value: '1,234', unit: 'users' }} />
19
+
20
+ <Story name="Variants" asChild>
53
21
  <div class="flex flex-col gap-4">
54
- <div class="flex flex-col items-center gap-2">
55
- <p class="text-sm text-secondary">Small</p>
56
- <StatCard title="Small Size" value="123" unit="units" size="sm" />
22
+ <div class="flex flex-col gap-2">
23
+ <p class="text-sm font-medium text-secondary">Secondary (default)</p>
24
+ <StatCard title="Total Revenue" value="42,500" unit="$" />
25
+ </div>
26
+ <div class="flex flex-col gap-2">
27
+ <p class="text-sm font-medium text-secondary">Primary</p>
28
+ <StatCard title="Total Revenue" value="42,500" unit="$" variant="primary" />
29
+ </div>
30
+ </div>
31
+ </Story>
32
+
33
+ <Story name="Sizes" asChild>
34
+ <div class="flex flex-col gap-6">
35
+ <div class="flex flex-col gap-2">
36
+ <p class="text-sm font-medium text-secondary">Small (sm)</p>
37
+ <StatCard title="Active Jobs" value="12" unit="jobs" size="sm" />
57
38
  </div>
58
- <div class="flex flex-col items-center gap-2">
59
- <p class="text-sm text-secondary">Medium</p>
60
- <StatCard title="Medium Size" value="123" unit="units" size="md" />
39
+ <div class="flex flex-col gap-2">
40
+ <p class="text-sm font-medium text-secondary">Medium (md)</p>
41
+ <StatCard title="Active Jobs" value="12" unit="jobs" size="md" />
42
+ </div>
43
+ </div>
44
+ </Story>
45
+
46
+
47
+ <Story name="With Icons and Subtitles" asChild>
48
+ <div class="flex flex-col gap-4">
49
+ <StatCard title="Growth" value="23.4" unit="%" variant="primary" size="sm" border>
50
+ {#snippet titleIcon()}
51
+ <Icon icon={Circle} weight="fill" color="icon-accent" size={12} />
52
+ {/snippet}
53
+ {#snippet subtitle()}
54
+ 15% increase from last quarter
55
+ {/snippet}
56
+ </StatCard>
57
+ <StatCard title="Warning" value="3" unit="alerts" size="md">
58
+ {#snippet titleIcon()}
59
+ <Icon icon={WarningCircle} weight="fill" color="icon-warning" size={16} />
60
+ {/snippet}
61
+ {#snippet subtitle()}
62
+ Requires immediate attention
63
+ {/snippet}
64
+ </StatCard>
65
+ </div>
66
+ </Story>
67
+
68
+ <Story name="With Borders" asChild>
69
+ <div class="flex flex-col gap-4">
70
+ <div class="flex flex-col gap-2">
71
+ <p class="text-sm font-medium text-secondary">Secondary with border</p>
72
+ <StatCard title="Total Items" value="456" unit="items" border />
61
73
  </div>
74
+ <div class="flex flex-col gap-2">
75
+ <p class="text-sm font-medium text-secondary">Primary with border</p>
76
+ <StatCard title="Total Items" value="456" unit="items" variant="primary" border />
77
+ </div>
78
+ </div>
79
+ </Story>
80
+
81
+ <Story name="With Tooltips" asChild>
82
+ <div class="flex flex-col gap-4">
83
+ <StatCard
84
+ title="Germination Rate"
85
+ value="87.5"
86
+ unit="%"
87
+ titleTooltip="The percentage of seeds that successfully germinated in the current batch"
88
+ showTitleTooltip={true}
89
+ />
90
+ <StatCard
91
+ title="Success Rate"
92
+ value="92"
93
+ unit="%"
94
+ variant="primary"
95
+ titleTooltip="Overall success rate across all operations"
96
+ showTitleTooltip={true}
97
+ size="sm"
98
+ />
99
+ </div>
100
+ </Story>
101
+
102
+ <Story name="Editable" asChild>
103
+ <div class="flex flex-col gap-4">
104
+ <div class="flex flex-col gap-2">
105
+ <p class="text-sm font-medium text-secondary">Click to edit</p>
106
+ <StatCard title="Target Value" value="1,000" unit="units" editable />
107
+ </div>
108
+ <div class="flex flex-col gap-2">
109
+ <p class="text-sm font-medium text-secondary">Editable with tooltip</p>
110
+ <StatCard
111
+ title="Threshold"
112
+ variant="primary"
113
+ border={true}
114
+ value="500"
115
+ unit="units"
116
+ editable
117
+ titleTooltip="Click to edit the threshold value"
118
+ showTitleTooltip={true}
119
+ />
120
+ </div>
121
+ </div>
122
+ </Story>
123
+
124
+ <Story name="Loading State" asChild>
125
+ <div class="flex flex-col gap-4">
126
+ <StatCard title="Loading Data" value={null} unit="items" />
127
+ <StatCard title="Fetching Results" value={null} variant="primary" size="sm" />
62
128
  </div>
63
129
  </Story>
@@ -3,13 +3,14 @@
3
3
  import PencilSimple from 'phosphor-svelte/lib/PencilSimple';
4
4
  import { Icon } from '../icons/index.js';
5
5
  import IconButton from '../icon-button/IconButton.svelte';
6
- import Spinner from '../spinner/Spinner.svelte';
7
6
  import Tooltip from '../tooltip/Tooltip.svelte';
8
7
  import { Input } from '../input';
9
8
  import { tick } from 'svelte';
10
-
9
+ import type { Snippet } from 'svelte';
10
+ import SkeletonLoader from '../skeleton-loader/SkeletonLoader.svelte';
11
11
  interface Props {
12
12
  title: string;
13
+ titleIcon?: Snippet;
13
14
  value?: string | number | null;
14
15
  unit?: string | null;
15
16
  titleTooltip?: string;
@@ -18,11 +19,14 @@
18
19
  onsubmit?: (value: string | number) => void;
19
20
  inputType?: 'text' | 'number';
20
21
  variant?: 'primary' | 'secondary';
22
+ border?: boolean;
21
23
  size?: 'sm' | 'md';
24
+ subtitle?: Snippet;
22
25
  }
23
26
 
24
27
  let {
25
28
  title,
29
+ titleIcon,
26
30
  value = null,
27
31
  unit = '',
28
32
  titleTooltip = '',
@@ -30,7 +34,9 @@
30
34
  editable = false,
31
35
  inputType = 'text',
32
36
  variant = 'secondary',
37
+ border = false,
33
38
  onsubmit,
39
+ subtitle,
34
40
  size = 'md',
35
41
  }: Props = $props();
36
42
  const formattedValue = $derived(typeof value === 'number' ? value.toLocaleString() : value);
@@ -98,12 +104,15 @@
98
104
  <!-- svelte-ignore a11y_no_static_element_interactions -->
99
105
  <div
100
106
  data-testid="stat-card-body"
101
- class="flex w-full flex-shrink-0 flex-grow basis-0 flex-col items-start gap-2 overflow-clip rounded-lg text-left transition-colors"
107
+ class="flex w-full flex-shrink-0 flex-grow basis-0 flex-col items-start overflow-clip rounded-lg text-left transition-colors"
102
108
  class:bg-neutral={variant === 'secondary'}
103
109
  class:bg-surface={variant === 'primary'}
104
- class:shadow-container={variant === 'primary'}
105
110
  class:p-4={size === 'md'}
106
111
  class:p-3={size === 'sm'}
112
+ class:gap-2={size === 'md'}
113
+ class:gap-1={size === 'sm'}
114
+ class:border={border}
115
+ class:border-static={border}
107
116
  class:hover:bg-neutral-hover={editable && !isEditing && value !== null}
108
117
  class:cursor-pointer={editable && !isEditing && value !== null}
109
118
  onclick={handleCardClick}
@@ -115,11 +124,14 @@
115
124
  class:text-label={size === 'sm'}
116
125
  class:text-sm={size === 'md'}
117
126
  >
127
+ {#if titleIcon}
128
+ {@render titleIcon()}
129
+ {/if}
118
130
  {title}
119
131
  {#if titleTooltip && showTitleTooltip}
120
132
  <Tooltip>
121
133
  {#snippet trigger()}
122
- <Icon color="icon-tertiary" icon={Info} />
134
+ <Icon color="icon-tertiary" icon={Info} class="cursor-help" />
123
135
  {/snippet}
124
136
  {#snippet content()}
125
137
  <span>
@@ -163,7 +175,16 @@
163
175
  {/if}
164
176
  {/if}
165
177
  {:else}
166
- <Spinner />
178
+ <SkeletonLoader class="flex items-center">
179
+ {#snippet children({ Skeleton })}
180
+ <Skeleton class="h-7 w-20 rounded-md" />
181
+ {/snippet}
182
+ </SkeletonLoader>
167
183
  {/if}
168
184
  </div>
185
+ {#if subtitle}
186
+ <p class="text-xs text-tertiary">
187
+ {@render subtitle()}
188
+ </p>
189
+ {/if}
169
190
  </div>
@@ -1,5 +1,7 @@
1
+ import type { Snippet } from 'svelte';
1
2
  interface Props {
2
3
  title: string;
4
+ titleIcon?: Snippet;
3
5
  value?: string | number | null;
4
6
  unit?: string | null;
5
7
  titleTooltip?: string;
@@ -8,7 +10,9 @@ interface Props {
8
10
  onsubmit?: (value: string | number) => void;
9
11
  inputType?: 'text' | 'number';
10
12
  variant?: 'primary' | 'secondary';
13
+ border?: boolean;
11
14
  size?: 'sm' | 'md';
15
+ subtitle?: Snippet;
12
16
  }
13
17
  declare const StatCard: import("svelte").Component<Props, {}, "">;
14
18
  type StatCard = ReturnType<typeof StatCard>;
@@ -64,10 +64,11 @@
64
64
  }
65
65
 
66
66
  .badge-sm {
67
- height: 1.5rem;
68
- gap: 0.25rem;
67
+ height: 1.75rem;
68
+ gap: 0.5rem;
69
69
  padding-left: 0.25rem;
70
- padding-right: 0.5rem
70
+ padding-right: 0.5rem;
71
+ font-size: 13px
71
72
  }
72
73
 
73
74
  .badge-sm .icon {
@@ -37,14 +37,14 @@
37
37
  const stateVariants = {
38
38
  completed: {
39
39
  container: 'bg-transparent text-primary-inverse',
40
- iconContainer: 'bg-primary ',
40
+ iconContainer: 'bg-accent-inverse',
41
41
  iconColor: 'text-surface',
42
42
  labelColor: 'text-primary',
43
43
  showIcon: true,
44
44
  },
45
45
  active: {
46
46
  container: 'bg-accent text-primary-inverse',
47
- iconContainer: 'bg-primary ',
47
+ iconContainer: 'bg-accent-inverse',
48
48
  iconColor: 'text-surface',
49
49
  labelColor: 'text-primary',
50
50
  showIcon: false,
@@ -75,7 +75,7 @@
75
75
  aria-hidden="true"
76
76
  >
77
77
  {#if variant.showIcon}
78
- <Icon class="h-3 w-3 {variant.iconColor}" icon={Check} />
78
+ <Icon class={variant.iconColor} icon={Check} />
79
79
  {:else}
80
80
  <span class="text-xs font-medium leading-none {variant.iconColor}">
81
81
  {stepNumber}