@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.
- package/dist/app.css +2 -2
- package/dist/components/button/Button.stories.svelte +27 -7
- package/dist/components/button/Button.svelte +107 -24
- package/dist/components/combobox/Combobox.stories.svelte +483 -89
- package/dist/components/combobox/components/combobox-add.svelte +10 -4
- package/dist/components/combobox/components/combobox-content.svelte +35 -58
- package/dist/components/combobox/components/combobox-indicator.svelte +1 -1
- package/dist/components/combobox/types.d.ts +1 -0
- package/dist/components/dropdown/Dropdown.stories.svelte +36 -16
- package/dist/components/dropdown/components/dropdown-content.svelte +6 -3
- package/dist/components/dropdown/components/dropdown-item.svelte +8 -1
- package/dist/components/dropdown/components/dropdown-separator.svelte +10 -0
- package/dist/components/dropdown/components/dropdown-separator.svelte.d.ts +5 -0
- package/dist/components/dropdown/components/dropdown-sub-content.svelte +4 -2
- package/dist/components/dropdown/components/dropdown-sub-trigger.svelte +10 -3
- package/dist/components/dropdown/index.d.ts +2 -2
- package/dist/components/dropdown/index.js +2 -2
- package/dist/components/dropdown/types.d.ts +1 -0
- package/dist/components/icon-button/IconButton.svelte +1 -1
- package/dist/components/icons/AnalysisIcon.svelte +7 -7
- package/dist/components/modal/Modal.stories.svelte +3 -0
- package/dist/components/modal/components/modal-title.svelte +7 -2
- package/dist/components/modal/types.d.ts +1 -0
- package/dist/components/select/Select.stories.svelte +158 -13
- package/dist/components/select/components/SelectContent.svelte +2 -2
- package/dist/components/select/components/SelectGroupHeading.svelte +1 -1
- package/dist/components/select/components/SelectItem.svelte +1 -1
- package/dist/components/select/components/SelectTrigger.svelte +13 -5
- package/dist/components/select/components/SelectTrigger.svelte.d.ts +1 -0
- package/dist/components/stat-card/StatCard.stories.svelte +113 -47
- package/dist/components/stat-card/StatCard.svelte +27 -6
- package/dist/components/stat-card/StatCard.svelte.d.ts +4 -0
- package/dist/components/status-badge/StatusBadge.svelte +4 -3
- package/dist/components/stepper/components/stepper-step.svelte +3 -3
- package/dist/tokens.d.ts +11 -1
- package/dist/tokens.js +13 -3
- 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="
|
|
300
|
+
<Story name="With Icons (Single Select)" asChild>
|
|
191
301
|
<div class="p-4">
|
|
192
|
-
<Select.Root bind:value={
|
|
193
|
-
<Select.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
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.
|
|
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-
|
|
216
|
-
Selected
|
|
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-
|
|
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
|
|
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
|
|
27
|
-
'
|
|
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
|
|
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}
|
|
@@ -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
|
-
|
|
14
|
-
|
|
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
|
|
55
|
-
<p class="text-sm text-secondary">
|
|
56
|
-
<StatCard title="
|
|
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
|
|
59
|
-
<p class="text-sm text-secondary">Medium</p>
|
|
60
|
-
<StatCard title="
|
|
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
|
|
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
|
-
<
|
|
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>;
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
const stateVariants = {
|
|
38
38
|
completed: {
|
|
39
39
|
container: 'bg-transparent text-primary-inverse',
|
|
40
|
-
iconContainer: 'bg-
|
|
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-
|
|
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=
|
|
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}
|