@reshape-biotech/design-system 2.6.3 → 2.6.4
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/components/button/Button.svelte +13 -6
- package/dist/components/card/Card.stories.svelte +96 -21
- package/dist/components/card/Card.svelte +5 -5
- package/dist/components/card/Card.svelte.d.ts +1 -1
- package/dist/components/card/CardWrapper.svelte +17 -24
- package/dist/components/card/index.d.ts +1 -0
- package/dist/components/card/index.js +1 -0
- package/dist/components/input/Input.svelte +58 -45
- package/dist/components/input/Input.svelte.d.ts +1 -0
- package/dist/components/legend/components/legend-item.svelte +1 -1
- package/dist/components/legend/components/legend-root.svelte +2 -2
- package/dist/components/stat-card/StatCard.stories.svelte +23 -0
- package/dist/components/stat-card/StatCard.svelte +27 -10
- package/dist/components/stat-card/StatCard.svelte.d.ts +2 -1
- package/dist/components/textarea/Textarea.svelte +39 -24
- package/dist/components/textarea/Textarea.svelte.d.ts +1 -0
- package/dist/tokens/colors.js +1 -1
- package/package.json +1 -1
|
@@ -175,17 +175,24 @@
|
|
|
175
175
|
|
|
176
176
|
height: 3rem;
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
border-radius: 0.75rem;
|
|
179
179
|
|
|
180
|
-
padding-
|
|
180
|
+
padding-left: 1.25rem;
|
|
181
|
+
|
|
182
|
+
padding-right: 1.25rem;
|
|
181
183
|
|
|
182
184
|
padding-top: 1rem;
|
|
183
185
|
|
|
184
|
-
padding-bottom: 1rem
|
|
186
|
+
padding-bottom: 1rem
|
|
187
|
+
}
|
|
188
|
+
@media (min-width: 1280px) {
|
|
189
|
+
|
|
190
|
+
.btn-size-lg {
|
|
185
191
|
|
|
186
|
-
|
|
192
|
+
font-size: 1rem;
|
|
187
193
|
|
|
188
|
-
|
|
194
|
+
line-height: 1.5rem
|
|
195
|
+
}
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
/* Button variants */
|
|
@@ -272,7 +279,7 @@
|
|
|
272
279
|
}
|
|
273
280
|
.btn-outline:hover {
|
|
274
281
|
|
|
275
|
-
border-color: #
|
|
282
|
+
border-color: #12192A1A;
|
|
276
283
|
|
|
277
284
|
background-color: #12192a0A;
|
|
278
285
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
3
|
import Card from './Card.svelte';
|
|
4
|
+
import CardWrapper from './CardWrapper.svelte';
|
|
4
5
|
import RequiredStatusIndicator from '../required-status-indicator/RequiredStatusIndicator.svelte';
|
|
5
6
|
|
|
6
7
|
const { Story } = defineMeta({
|
|
@@ -13,6 +14,9 @@
|
|
|
13
14
|
headerBorder: { control: 'boolean', description: 'Show a border below the header' },
|
|
14
15
|
class: { control: 'text', description: 'Additional CSS classes for the card' },
|
|
15
16
|
},
|
|
17
|
+
parameters: {
|
|
18
|
+
layout: 'centered',
|
|
19
|
+
},
|
|
16
20
|
});
|
|
17
21
|
</script>
|
|
18
22
|
|
|
@@ -30,21 +34,11 @@
|
|
|
30
34
|
</Card>
|
|
31
35
|
</Story>
|
|
32
36
|
|
|
33
|
-
<Story name="Header Only" args={{ class: 'w-96' }} asChild>
|
|
34
|
-
<Card class="w-96">
|
|
35
|
-
{#snippet Header()}
|
|
36
|
-
<p class="text-primary">Card with Header Only</p>
|
|
37
|
-
<p class="text-sm text-tertiary">No main content here, just a header.</p>
|
|
38
|
-
{/snippet}
|
|
39
|
-
{#snippet children()}{/snippet}
|
|
40
|
-
</Card>
|
|
41
|
-
</Story>
|
|
42
|
-
|
|
43
37
|
<Story name="Header and Content" args={{ class: 'w-96' }} asChild>
|
|
44
38
|
<Card class="w-96">
|
|
45
39
|
{#snippet Header()}
|
|
46
|
-
<div class="flex items-center justify-between">
|
|
47
|
-
<
|
|
40
|
+
<div class="flex w-full items-center justify-between">
|
|
41
|
+
<h5>Insect Health</h5>
|
|
48
42
|
<span class="text-sm text-success">Target</span>
|
|
49
43
|
</div>
|
|
50
44
|
{/snippet}
|
|
@@ -78,8 +72,8 @@
|
|
|
78
72
|
<Story name="Header, Content, and Border" args={{ headerBorder: true, class: 'w-96' }} asChild>
|
|
79
73
|
<Card headerBorder={true} class="w-96">
|
|
80
74
|
{#snippet Header()}
|
|
81
|
-
<div class="flex items-center justify-between">
|
|
82
|
-
<
|
|
75
|
+
<div class="flex w-full items-center justify-between">
|
|
76
|
+
<h6>Insect Count</h6>
|
|
83
77
|
<RequiredStatusIndicator requiredCount={2} currentCount={2} />
|
|
84
78
|
</div>
|
|
85
79
|
{/snippet}
|
|
@@ -111,18 +105,99 @@
|
|
|
111
105
|
</Card>
|
|
112
106
|
</Story>
|
|
113
107
|
|
|
108
|
+
<Story name="Card nested in CardWrapper" args={{ class: 'w-96' }} asChild>
|
|
109
|
+
<CardWrapper class="w-96" variant="compact">
|
|
110
|
+
{#snippet Header()}
|
|
111
|
+
<h5>Overview</h5>
|
|
112
|
+
<span class="text-sm text-success">Active</span>
|
|
113
|
+
{/snippet}
|
|
114
|
+
{#snippet children()}
|
|
115
|
+
<Card>
|
|
116
|
+
{#snippet children()}
|
|
117
|
+
<h3 class="mb-2 text-lg font-semibold text-primary">Nested Card Content</h3>
|
|
118
|
+
<p class="text-secondary">
|
|
119
|
+
This demonstrates a Card nested in a compact CardWrapper. The wrapper provides the outer
|
|
120
|
+
container with header and footer, while the Card provides the inner styling with
|
|
121
|
+
content.
|
|
122
|
+
</p>
|
|
123
|
+
{/snippet}
|
|
124
|
+
</Card>
|
|
125
|
+
{/snippet}
|
|
126
|
+
{#snippet Footer()}
|
|
127
|
+
<p class="py-2 text-xs text-secondary">Last updated 2 hours ago</p>
|
|
128
|
+
{/snippet}
|
|
129
|
+
</CardWrapper>
|
|
130
|
+
</Story>
|
|
131
|
+
|
|
132
|
+
<Story name="CardWrapper with Multiple Cards" args={{ class: 'w-[600px]' }} asChild>
|
|
133
|
+
<CardWrapper class="w-[600px]">
|
|
134
|
+
{#snippet Header()}
|
|
135
|
+
<h6>Project Dashboard</h6>
|
|
136
|
+
<span class="text-sm text-success">3 Active</span>
|
|
137
|
+
{/snippet}
|
|
138
|
+
{#snippet children()}
|
|
139
|
+
<Card headerBorder>
|
|
140
|
+
{#snippet Header()}
|
|
141
|
+
<div class="flex items-center gap-2">
|
|
142
|
+
<div class="h-3 w-3 rounded-full bg-success-inverse"></div>
|
|
143
|
+
<h6>Analysis Model</h6>
|
|
144
|
+
</div>
|
|
145
|
+
{/snippet}
|
|
146
|
+
{#snippet children()}
|
|
147
|
+
<p class="mb-2 text-sm text-secondary">Cell Count Analysis</p>
|
|
148
|
+
<div class="flex justify-between text-xs text-tertiary">
|
|
149
|
+
<span>Status: Active</span>
|
|
150
|
+
<span>Last run: 2h ago</span>
|
|
151
|
+
</div>
|
|
152
|
+
{/snippet}
|
|
153
|
+
</Card>
|
|
154
|
+
|
|
155
|
+
<Card headerBorder>
|
|
156
|
+
{#snippet Header()}
|
|
157
|
+
<div class="flex items-center gap-2">
|
|
158
|
+
<div class="h-3 w-3 rounded-full bg-warning-inverse"></div>
|
|
159
|
+
<h6>Validation</h6>
|
|
160
|
+
</div>
|
|
161
|
+
{/snippet}
|
|
162
|
+
{#snippet children()}
|
|
163
|
+
<p class="mb-2 text-sm text-secondary">Quality Check</p>
|
|
164
|
+
<div class="flex justify-between text-xs text-tertiary">
|
|
165
|
+
<span>Status: Pending</span>
|
|
166
|
+
<span>Next run: 1d</span>
|
|
167
|
+
</div>
|
|
168
|
+
{/snippet}
|
|
169
|
+
</Card>
|
|
170
|
+
|
|
171
|
+
<Card headerBorder>
|
|
172
|
+
{#snippet Header()}
|
|
173
|
+
<div class="flex items-center gap-2">
|
|
174
|
+
<div class="h-3 w-3 rounded-full bg-danger-inverse"></div>
|
|
175
|
+
<h6>Export</h6>
|
|
176
|
+
</div>
|
|
177
|
+
{/snippet}
|
|
178
|
+
{#snippet children()}
|
|
179
|
+
<p class="mb-2 text-sm text-secondary">Data Export</p>
|
|
180
|
+
<div class="flex justify-between text-xs text-tertiary">
|
|
181
|
+
<span>Status: Failed</span>
|
|
182
|
+
<span>Last run: 5h ago</span>
|
|
183
|
+
</div>
|
|
184
|
+
{/snippet}
|
|
185
|
+
</Card>
|
|
186
|
+
{/snippet}
|
|
187
|
+
</CardWrapper>
|
|
188
|
+
</Story>
|
|
189
|
+
|
|
114
190
|
<Story name="With Custom Styling" args={{}} asChild>
|
|
115
|
-
<Card class="w-
|
|
191
|
+
<Card class="w-72 text-accent shadow-lg" headerBorder>
|
|
116
192
|
{#snippet Header()}
|
|
117
|
-
<
|
|
193
|
+
<h6>Custom Styled Card</h6>
|
|
118
194
|
{/snippet}
|
|
119
195
|
{#snippet children()}
|
|
120
196
|
<p>
|
|
121
|
-
This card uses the <code class="rounded bg-
|
|
122
|
-
different background (<code class="rounded bg-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
class="rounded bg-black/20 px-1">shadow-lg</code
|
|
197
|
+
This card uses the <code class="rounded bg-accent px-1">class</code> prop to apply a
|
|
198
|
+
different background text color (<code class="rounded bg-accent px-1">text-accent</code>),
|
|
199
|
+
width (<code class="rounded bg-accent px-1">w-72</code>), and a larger shadow (<code
|
|
200
|
+
class="rounded bg-accent px-1">shadow-lg</code
|
|
126
201
|
>).
|
|
127
202
|
</p>
|
|
128
203
|
{/snippet}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
children: Snippet;
|
|
7
7
|
headerBorder?: boolean;
|
|
8
8
|
class?: string;
|
|
9
|
-
|
|
9
|
+
padding?: number;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
const {
|
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
children,
|
|
15
15
|
headerBorder = false,
|
|
16
16
|
class: additionalClasses = '',
|
|
17
|
-
|
|
17
|
+
padding = 4,
|
|
18
18
|
}: Props = $props();
|
|
19
19
|
</script>
|
|
20
20
|
|
|
21
21
|
<div
|
|
22
|
-
class="overflow-hidden rounded-
|
|
22
|
+
class="overflow-hidden rounded-lg border border-static bg-surface shadow-container {additionalClasses}"
|
|
23
23
|
>
|
|
24
24
|
{#if Header}
|
|
25
25
|
<header
|
|
26
|
-
class="flex h-12 items-center px-4"
|
|
26
|
+
class="flex min-h-12 items-center px-4"
|
|
27
27
|
class:border-b={headerBorder}
|
|
28
28
|
class:border-static={headerBorder}
|
|
29
29
|
>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
</header>
|
|
32
32
|
{/if}
|
|
33
33
|
{#if children}
|
|
34
|
-
<div class={
|
|
34
|
+
<div class={`p-${padding}`}>
|
|
35
35
|
{@render children()}
|
|
36
36
|
</div>
|
|
37
37
|
{/if}
|
|
@@ -20,15 +20,23 @@
|
|
|
20
20
|
|
|
21
21
|
<div class="wrapper {variant} {additionalClasses}">
|
|
22
22
|
{#if Header}
|
|
23
|
-
<header
|
|
23
|
+
<header
|
|
24
|
+
class="flex w-full items-center justify-between {variant === 'default'
|
|
25
|
+
? 'min-h-10 px-4'
|
|
26
|
+
: 'min-h-8 px-3'}"
|
|
27
|
+
>
|
|
24
28
|
{@render Header()}
|
|
25
29
|
</header>
|
|
26
30
|
{/if}
|
|
27
|
-
<div
|
|
31
|
+
<div
|
|
32
|
+
class="flex w-full !overflow-hidden [&>*]:w-full {variant === 'default'
|
|
33
|
+
? '[&>*]:rounded-xl'
|
|
34
|
+
: '[&>*]:rounded-[10px]'}"
|
|
35
|
+
>
|
|
28
36
|
{@render children()}
|
|
29
37
|
</div>
|
|
30
38
|
{#if Footer}
|
|
31
|
-
<footer class="
|
|
39
|
+
<footer class="flex min-h-8 w-full items-center {variant === 'default' ? 'px-4' : 'px-3'}">
|
|
32
40
|
{@render Footer()}
|
|
33
41
|
</footer>
|
|
34
42
|
{/if}
|
|
@@ -41,17 +49,17 @@
|
|
|
41
49
|
|
|
42
50
|
flex-direction: column;
|
|
43
51
|
|
|
44
|
-
border-radius: 1rem;
|
|
45
|
-
|
|
46
52
|
--tw-bg-opacity: 1;
|
|
47
53
|
|
|
48
|
-
background-color: rgb(
|
|
54
|
+
background-color: rgb(250 250 250 / var(--tw-bg-opacity, 1))
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
.wrapper.default {
|
|
52
58
|
|
|
53
59
|
gap: 0.25rem;
|
|
54
60
|
|
|
61
|
+
border-radius: 1rem;
|
|
62
|
+
|
|
55
63
|
padding: 0.25rem
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -59,31 +67,16 @@
|
|
|
59
67
|
|
|
60
68
|
gap: 0.125rem;
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.wrapper-header {
|
|
66
|
-
|
|
67
|
-
display: flex;
|
|
68
|
-
|
|
69
|
-
min-height: 2rem;
|
|
70
|
+
border-radius: 0.75rem;
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
align-items: center;
|
|
74
|
-
|
|
75
|
-
justify-content: space-between;
|
|
76
|
-
|
|
77
|
-
padding-left: 1rem;
|
|
78
|
-
|
|
79
|
-
padding-right: 1rem
|
|
72
|
+
padding: 0.125rem
|
|
80
73
|
}
|
|
81
74
|
|
|
82
75
|
.wrapper-footer {
|
|
83
76
|
|
|
84
77
|
display: flex;
|
|
85
78
|
|
|
86
|
-
min-height:
|
|
79
|
+
min-height: 2rem;
|
|
87
80
|
|
|
88
81
|
width: 100%;
|
|
89
82
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
interface InputProps extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'suffix'> {
|
|
8
8
|
label?: string | null;
|
|
9
|
+
labelPlacement?: 'top' | 'left';
|
|
9
10
|
id?: string | undefined;
|
|
10
11
|
variant?: 'primary' | 'secondary' | 'transparent' | 'borderless';
|
|
11
12
|
validator?: (a: string | number) => boolean;
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
|
|
24
25
|
let {
|
|
25
26
|
label = null,
|
|
27
|
+
labelPlacement = 'top',
|
|
26
28
|
variant = 'primary',
|
|
27
29
|
value = $bindable(),
|
|
28
30
|
type = 'text',
|
|
@@ -67,9 +69,18 @@
|
|
|
67
69
|
export type InputSize = 'xs' | 'sm' | 'md' | 'lg' | 'dynamic';
|
|
68
70
|
</script>
|
|
69
71
|
|
|
70
|
-
<div
|
|
72
|
+
<div
|
|
73
|
+
class="flex w-full gap-2"
|
|
74
|
+
class:flex-col={labelPlacement === 'top'}
|
|
75
|
+
class:items-center={labelPlacement === 'left'}
|
|
76
|
+
>
|
|
71
77
|
{#if label}
|
|
72
|
-
<label
|
|
78
|
+
<label
|
|
79
|
+
for={id ?? inputId}
|
|
80
|
+
class="flex text-sm capitalize text-secondary {labelPlacement === 'left'
|
|
81
|
+
? 'w-1/4'
|
|
82
|
+
: 'w-full'}"
|
|
83
|
+
>
|
|
73
84
|
{label}
|
|
74
85
|
{#if rest.required}
|
|
75
86
|
<span class="ml-0.5 text-danger">*</span>
|
|
@@ -77,51 +88,53 @@
|
|
|
77
88
|
</label>
|
|
78
89
|
{/if}
|
|
79
90
|
|
|
80
|
-
<div
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
<div class="flex-1">
|
|
92
|
+
<div
|
|
93
|
+
class="flex w-full items-center gap-1 size-{size} transition-colors"
|
|
94
|
+
class:!border-error={!valid}
|
|
95
|
+
class:primary={variant === 'primary'}
|
|
96
|
+
class:secondary={variant === 'secondary'}
|
|
97
|
+
class:transparent={variant === 'transparent'}
|
|
98
|
+
class:borderless={variant === 'borderless'}
|
|
99
|
+
>
|
|
100
|
+
<div class="whitespace-nowrap text-secondary">
|
|
101
|
+
{@render prefix?.({ valid })}
|
|
102
|
+
</div>
|
|
103
|
+
<input
|
|
104
|
+
{id}
|
|
105
|
+
class="inline leading-none"
|
|
106
|
+
class:has-text={value}
|
|
107
|
+
class:has-placeholder={rest.placeholder}
|
|
108
|
+
aria-label={label}
|
|
109
|
+
{type}
|
|
110
|
+
oninput={handleInput}
|
|
111
|
+
onblur={handleBlur}
|
|
112
|
+
bind:this={input}
|
|
113
|
+
bind:value
|
|
114
|
+
{...rest}
|
|
115
|
+
/>
|
|
116
|
+
<div class="whitespace-nowrap text-secondary">
|
|
117
|
+
{@render suffix?.()}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{#if clearable && value}
|
|
121
|
+
<IconButton
|
|
122
|
+
variant="transparent"
|
|
123
|
+
rounded={false}
|
|
124
|
+
size="xs"
|
|
125
|
+
onclick={() => {
|
|
126
|
+
value = '';
|
|
127
|
+
input?.focus();
|
|
128
|
+
onclear();
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<Icon iconName="X" />
|
|
132
|
+
</IconButton>
|
|
133
|
+
{/if}
|
|
106
134
|
</div>
|
|
107
135
|
|
|
108
|
-
{
|
|
109
|
-
<IconButton
|
|
110
|
-
variant="transparent"
|
|
111
|
-
rounded={false}
|
|
112
|
-
size="xs"
|
|
113
|
-
onclick={() => {
|
|
114
|
-
value = '';
|
|
115
|
-
input?.focus();
|
|
116
|
-
onclear();
|
|
117
|
-
}}
|
|
118
|
-
>
|
|
119
|
-
<Icon iconName="X" />
|
|
120
|
-
</IconButton>
|
|
121
|
-
{/if}
|
|
136
|
+
{@render error?.()}
|
|
122
137
|
</div>
|
|
123
|
-
|
|
124
|
-
{@render error?.()}
|
|
125
138
|
</div>
|
|
126
139
|
|
|
127
140
|
<style>
|
|
@@ -191,7 +204,7 @@ input::placeholder {
|
|
|
191
204
|
|
|
192
205
|
--tw-bg-opacity: 1;
|
|
193
206
|
|
|
194
|
-
background-color: rgb(
|
|
207
|
+
background-color: rgb(250 250 250 / var(--tw-bg-opacity, 1))
|
|
195
208
|
}
|
|
196
209
|
|
|
197
210
|
.transparent {
|
|
@@ -2,6 +2,7 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
interface InputProps extends Omit<HTMLInputAttributes, 'size' | 'prefix' | 'suffix'> {
|
|
4
4
|
label?: string | null;
|
|
5
|
+
labelPlacement?: 'top' | 'left';
|
|
5
6
|
id?: string | undefined;
|
|
6
7
|
variant?: 'primary' | 'secondary' | 'transparent' | 'borderless';
|
|
7
8
|
validator?: (a: string | number) => boolean;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
>
|
|
20
20
|
<div class="flex h-2 w-2 flex-shrink-0 rounded-sm" style="background-color: {color}"></div>
|
|
21
21
|
<div
|
|
22
|
-
class="flex max-w-
|
|
22
|
+
class="flex max-w-40 select-none truncate text-xs"
|
|
23
23
|
class:text-secondary={!selected}
|
|
24
24
|
class:text-primary={selected}
|
|
25
25
|
>
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
let { children, items }: Props = $props();
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
|
-
<div class="inline-flex w-full flex-col items-center rounded-[10px] bg-
|
|
12
|
+
<div class="inline-flex w-full flex-col items-center rounded-[10px] bg-base">
|
|
13
13
|
<div
|
|
14
14
|
class="flex w-full px-0.5 pt-0.5 [&>*]:rounded-lg [&>*]:border [&>*]:border-static [&>*]:p-1"
|
|
15
15
|
>
|
|
16
16
|
{@render children()}
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
<div class="flex flex-wrap items-center justify-center gap-
|
|
19
|
+
<div class="flex flex-wrap items-center justify-center gap-1 overflow-clip p-1">
|
|
20
20
|
{#if items}
|
|
21
21
|
{@render items()}
|
|
22
22
|
{/if}
|
|
@@ -38,3 +38,26 @@
|
|
|
38
38
|
editable: true,
|
|
39
39
|
}}
|
|
40
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>
|
|
53
|
+
<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" />
|
|
57
|
+
</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" />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</Story>
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
editable?: boolean;
|
|
16
16
|
onsubmit?: (value: string | number) => void;
|
|
17
17
|
inputType?: 'text' | 'number';
|
|
18
|
-
variant?: '
|
|
18
|
+
variant?: 'primary' | 'secondary';
|
|
19
|
+
size?: 'sm' | 'md';
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
let {
|
|
@@ -26,8 +27,9 @@
|
|
|
26
27
|
showTitleTooltip = false,
|
|
27
28
|
editable = false,
|
|
28
29
|
inputType = 'text',
|
|
29
|
-
variant = '
|
|
30
|
+
variant = 'secondary',
|
|
30
31
|
onsubmit,
|
|
32
|
+
size = 'md',
|
|
31
33
|
}: Props = $props();
|
|
32
34
|
const formattedValue = $derived(typeof value === 'number' ? value.toLocaleString() : value);
|
|
33
35
|
let isEditing = $state(false);
|
|
@@ -94,16 +96,23 @@
|
|
|
94
96
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
95
97
|
<div
|
|
96
98
|
data-testid="stat-card-body"
|
|
97
|
-
class="flex w-full flex-shrink-0 flex-grow basis-0 flex-col items-start gap-2 overflow-clip rounded-lg
|
|
98
|
-
class:bg-neutral={variant === '
|
|
99
|
-
class:bg-surface={variant === '
|
|
99
|
+
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"
|
|
100
|
+
class:bg-neutral={variant === 'secondary'}
|
|
101
|
+
class:bg-surface={variant === 'primary'}
|
|
102
|
+
class:shadow-container={variant === 'primary'}
|
|
103
|
+
class:p-4={size === 'md'}
|
|
104
|
+
class:p-3={size === 'sm'}
|
|
100
105
|
class:hover:bg-neutral-hover={editable && !isEditing && value !== null}
|
|
101
106
|
class:cursor-pointer={editable && !isEditing && value !== null}
|
|
102
107
|
onclick={handleCardClick}
|
|
103
108
|
onkeydown={editable && !isEditing && value !== null ? handleInputKeydown : undefined}
|
|
104
109
|
aria-label={editable && !isEditing && value !== null ? `Edit ${title}` : undefined}
|
|
105
110
|
>
|
|
106
|
-
<p
|
|
111
|
+
<p
|
|
112
|
+
class="flex items-center justify-start gap-2 truncate font-medium text-secondary"
|
|
113
|
+
class:text-label={size === 'sm'}
|
|
114
|
+
class:text-sm={size === 'md'}
|
|
115
|
+
>
|
|
107
116
|
{title}
|
|
108
117
|
{#if titleTooltip && showTitleTooltip}
|
|
109
118
|
<Tooltip>
|
|
@@ -118,7 +127,7 @@
|
|
|
118
127
|
</Tooltip>
|
|
119
128
|
{/if}
|
|
120
129
|
</p>
|
|
121
|
-
<div class="flex
|
|
130
|
+
<div class="flex w-full items-center gap-1" class:h-8={size === 'md'} class:h-7={size === 'sm'}>
|
|
122
131
|
{#if value !== null}
|
|
123
132
|
{#if isEditing}
|
|
124
133
|
<div class="flex-1">
|
|
@@ -132,13 +141,21 @@
|
|
|
132
141
|
</div>
|
|
133
142
|
{:else}
|
|
134
143
|
<div class="flex flex-1 flex-nowrap items-baseline gap-1">
|
|
135
|
-
<p class="text-2xl
|
|
144
|
+
<p class="font-medium" class:text-2xl={size === 'md'} class:text-xl={size === 'sm'}>
|
|
145
|
+
{formattedValue}
|
|
146
|
+
</p>
|
|
136
147
|
{#if unit}
|
|
137
|
-
<p
|
|
148
|
+
<p
|
|
149
|
+
class="flex-1 truncate font-medium text-tertiary"
|
|
150
|
+
class:text-2xl={size === 'md'}
|
|
151
|
+
class:text-xl={size === 'sm'}
|
|
152
|
+
>
|
|
153
|
+
{unit}
|
|
154
|
+
</p>
|
|
138
155
|
{/if}
|
|
139
156
|
</div>
|
|
140
157
|
{#if editable}
|
|
141
|
-
<IconButton onclick={(e) => startEditing(e)}>
|
|
158
|
+
<IconButton onclick={(e) => startEditing(e)} rounded={false}>
|
|
142
159
|
<Icon iconName="PencilSimple" />
|
|
143
160
|
</IconButton>
|
|
144
161
|
{/if}
|
|
@@ -7,7 +7,8 @@ interface Props {
|
|
|
7
7
|
editable?: boolean;
|
|
8
8
|
onsubmit?: (value: string | number) => void;
|
|
9
9
|
inputType?: 'text' | 'number';
|
|
10
|
-
variant?: '
|
|
10
|
+
variant?: 'primary' | 'secondary';
|
|
11
|
+
size?: 'sm' | 'md';
|
|
11
12
|
}
|
|
12
13
|
declare const StatCard: import("svelte").Component<Props, {}, "">;
|
|
13
14
|
type StatCard = ReturnType<typeof StatCard>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
input?: HTMLTextAreaElement;
|
|
10
10
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'dynamic';
|
|
11
11
|
label?: string;
|
|
12
|
+
labelPlacement?: 'top' | 'left';
|
|
12
13
|
error?: Snippet;
|
|
13
14
|
placeholder?: string;
|
|
14
15
|
}
|
|
@@ -20,6 +21,7 @@
|
|
|
20
21
|
onkeydown = () => {},
|
|
21
22
|
input = $bindable(),
|
|
22
23
|
label,
|
|
24
|
+
labelPlacement = 'top',
|
|
23
25
|
placeholder,
|
|
24
26
|
error,
|
|
25
27
|
id,
|
|
@@ -66,29 +68,42 @@
|
|
|
66
68
|
});
|
|
67
69
|
</script>
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{
|
|
71
|
+
<div
|
|
72
|
+
class="flex w-full gap-2"
|
|
73
|
+
class:flex-col={labelPlacement === 'top'}
|
|
74
|
+
class:items-start={labelPlacement === 'left'}
|
|
75
|
+
>
|
|
76
|
+
{#if label}
|
|
77
|
+
<label
|
|
78
|
+
for={inputId}
|
|
79
|
+
class="flex text-sm capitalize text-secondary {labelPlacement === 'left'
|
|
80
|
+
? 'w-1/4'
|
|
81
|
+
: 'w-full'}"
|
|
82
|
+
>
|
|
83
|
+
{label}
|
|
84
|
+
{#if rest.required}
|
|
85
|
+
<span class="ml-0.5 text-danger">*</span>
|
|
86
|
+
{/if}
|
|
87
|
+
</label>
|
|
88
|
+
{/if}
|
|
77
89
|
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
<div class="flex-1">
|
|
91
|
+
<textarea
|
|
92
|
+
id={inputId}
|
|
93
|
+
class="flex min-h-10 w-full items-center gap-1 rounded-lg border border-input bg-surface px-3 py-2 shadow-input transition-colors hover:border-hover focus:border-focus focus:outline-none {className ??
|
|
94
|
+
''}"
|
|
95
|
+
class:!border-error={error}
|
|
96
|
+
class:has-text={value}
|
|
97
|
+
class:has-placeholder={placeholder}
|
|
98
|
+
{placeholder}
|
|
99
|
+
oninput={handleInputInternal}
|
|
100
|
+
onblur={handleBlurInternal}
|
|
101
|
+
bind:this={input}
|
|
102
|
+
{value}
|
|
103
|
+
{...rest}
|
|
104
|
+
{onkeydown}
|
|
105
|
+
></textarea>
|
|
93
106
|
|
|
94
|
-
{@render error?.()}
|
|
107
|
+
{@render error?.()}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
package/dist/tokens/colors.js
CHANGED