@reshape-biotech/design-system 2.6.3 → 2.6.5

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.
@@ -175,17 +175,24 @@
175
175
 
176
176
  height: 3rem;
177
177
 
178
- padding-left: 1.5rem;
178
+ border-radius: 0.75rem;
179
179
 
180
- padding-right: 1.5rem;
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
- font-size: 1rem;
192
+ font-size: 1rem;
187
193
 
188
- line-height: 1.5rem
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: #12192A26;
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
- <p class="text-primary">Insect Health</p>
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
- <p class="text-primary">Insect Count</p>
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-1/2 bg-accent text-primary-inverse shadow-lg">
191
+ <Card class="w-72 text-accent shadow-lg" headerBorder>
116
192
  {#snippet Header()}
117
- <p class="">Custom Styled Card</p>
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-black/20 px-1">class</code> prop to apply a
122
- different background (<code class="rounded bg-black/20 px-1">bg-accent</code>), text color (<code
123
- class="rounded bg-black/20 px-1">text-primary-inverse</code
124
- >), width (<code class="rounded bg-black/20 px-1">w-1/2</code>), and a larger shadow (<code
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
- variant?: 'full-width' | 'content-width';
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
- variant = 'content-width',
17
+ padding = 4,
18
18
  }: Props = $props();
19
19
  </script>
20
20
 
21
21
  <div
22
- class="overflow-hidden rounded-xl border border-static bg-surface shadow-container {additionalClasses}"
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={variant === 'full-width' ? '' : 'p-4 pb-5'}>
34
+ <div class={`p-${padding}`}>
35
35
  {@render children()}
36
36
  </div>
37
37
  {/if}
@@ -4,7 +4,7 @@ type Props = {
4
4
  children: Snippet;
5
5
  headerBorder?: boolean;
6
6
  class?: string;
7
- variant?: 'full-width' | 'content-width';
7
+ padding?: number;
8
8
  };
9
9
  declare const Card: import("svelte").Component<Props, {}, "">;
10
10
  type Card = ReturnType<typeof Card>;
@@ -20,15 +20,23 @@
20
20
 
21
21
  <div class="wrapper {variant} {additionalClasses}">
22
22
  {#if Header}
23
- <header class="wrapper-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 class="flex w-full !overflow-hidden [&>*]:w-full [&>*]:rounded-xl">
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="wrapper-footer">
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(251 251 251 / var(--tw-bg-opacity, 1))
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
- padding: 0.125rem
63
- }
64
-
65
- .wrapper-header {
66
-
67
- display: flex;
68
-
69
- min-height: 2rem;
70
+ border-radius: 0.75rem;
70
71
 
71
- width: 100%;
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: 2.5rem;
79
+ min-height: 2rem;
87
80
 
88
81
  width: 100%;
89
82
 
@@ -1 +1,2 @@
1
1
  export { default as Card } from './Card.svelte';
2
+ export { default as CardWrapper } from './CardWrapper.svelte';
@@ -1 +1,2 @@
1
1
  export { default as Card } from './Card.svelte';
2
+ export { default as CardWrapper } from './CardWrapper.svelte';
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { backgroundColor } from '../../../tokens';
2
+ import { backgroundColor, textColor } from '../../../tokens';
3
3
  import Icon from '../../icons/Icon.svelte';
4
4
  import Chart, { type GenericChartProps } from '../chart/Chart.svelte';
5
5
  import type {
@@ -11,7 +11,13 @@
11
11
  import Tooltip from '../../tooltip/Tooltip.svelte';
12
12
  import type { Snippet } from 'svelte';
13
13
 
14
- export type DataPoint = { value: [number, number]; metadata: any; error_value?: number };
14
+ export type DataPoint = {
15
+ value: [number, number];
16
+ metadata: any;
17
+ error_value?: number;
18
+ disagreement?: boolean | null;
19
+ highlighted?: boolean | null;
20
+ };
15
21
 
16
22
  export type ScatterPlotEchartsEvent = echarts.ECElementEvent & {
17
23
  data: DataPoint;
@@ -29,6 +35,7 @@
29
35
  showConfidenceBand?: boolean;
30
36
  showLegend?: boolean;
31
37
  legendItems?: LegendItem[];
38
+ highlightIndex?: number | null;
32
39
  children?: Snippet;
33
40
  } & Omit<GenericChartProps, 'timeIndex'>;
34
41
 
@@ -46,6 +53,7 @@
46
53
  { label: 'Count', color: backgroundColor['blue-inverse'], type: 'point' },
47
54
  { label: 'Perfect agreement', color: backgroundColor['lilac-inverse'], type: 'line' },
48
55
  ],
56
+ highlightIndex = null,
49
57
  children,
50
58
  ...props
51
59
  }: ScatterPlotProps = $props();
@@ -85,6 +93,7 @@
85
93
  const xValue = api.value(0) as number;
86
94
  const yValue = api.value(1) as number;
87
95
  const error = api.value(2) as number;
96
+ const disagreement = api.value(3) as number;
88
97
 
89
98
  const highPoint = api.coord([xValue, yValue + error]);
90
99
  const lowPoint = api.coord([xValue, yValue - error]);
@@ -98,8 +107,7 @@
98
107
  const baseWidth = Array.isArray(sizeValue) ? sizeValue[0] : 10;
99
108
  const halfWidth = Math.min((baseWidth ?? 10) * 1.5, 3);
100
109
  const style = {
101
- stroke: api.visual('color') as string,
102
- fill: undefined,
110
+ stroke: disagreement ? textColor['icon-tertiary'] : backgroundColor['blue-inverse'],
103
111
  };
104
112
 
105
113
  return {
@@ -148,7 +156,7 @@
148
156
  const series: SeriesOption[] = [];
149
157
  const errorBarData = data
150
158
  .filter((d) => d.error_value != 0)
151
- .map((d) => [...d.value, d.error_value]);
159
+ .map((d) => [...d.value, d.error_value, d.disagreement ? 1 : 0]);
152
160
 
153
161
  if (props.lineData && props.lineData.length > 0) {
154
162
  const extendedLineData = [
@@ -170,7 +178,6 @@
170
178
  disabled: true,
171
179
  },
172
180
  silent: true,
173
- zlevel: 0,
174
181
  });
175
182
 
176
183
  if (showConfidenceBand && props.lineData && props.lineData.length > 0) {
@@ -205,7 +212,6 @@
205
212
  disabled: true,
206
213
  },
207
214
  silent: true,
208
- zlevel: 0,
209
215
  });
210
216
  }
211
217
  }
@@ -215,17 +221,43 @@
215
221
  data,
216
222
  type: 'scatter',
217
223
  itemStyle: {
218
- color: backgroundColor['blue-inverse'],
219
- opacity: 0.8,
224
+ color: (params: any) =>
225
+ params?.data?.disagreement ? textColor['icon-tertiary'] : backgroundColor['blue-inverse'],
226
+ opacity: 1,
220
227
  },
221
228
  emphasis: {
222
229
  itemStyle: {
223
- color: backgroundColor['blue-inverse'],
230
+ color: (params: any) => {
231
+ const point = params?.data as DataPoint;
232
+ return point?.disagreement
233
+ ? textColor['icon-tertiary']
234
+ : backgroundColor['blue-inverse'];
235
+ },
224
236
  opacity: 1,
225
237
  },
226
238
  },
227
239
  animation: false,
228
- zlevel: 1,
240
+ });
241
+
242
+ const p =
243
+ highlightIndex !== null && highlightIndex >= 0 && highlightIndex < data.length
244
+ ? data[highlightIndex]
245
+ : null;
246
+ series.push({
247
+ id: 'highlight-overlay',
248
+ type: 'scatter',
249
+ data: [p],
250
+ symbolSize: 16,
251
+ itemStyle: {
252
+ color: (params: any) => {
253
+ const point = params?.data as DataPoint;
254
+ return point?.disagreement
255
+ ? backgroundColor['neutral-hover']
256
+ : backgroundColor['blue-hover'];
257
+ },
258
+ },
259
+ z: -1,
260
+ silent: true,
229
261
  });
230
262
 
231
263
  if (errorBarData.length > 0) {
@@ -233,16 +265,11 @@
233
265
  type: 'custom',
234
266
  name: 'error',
235
267
  silent: true,
236
- itemStyle: {
237
- borderWidth: 1.5,
238
- color: backgroundColor['blue-inverse'],
239
- },
268
+ itemStyle: { borderWidth: 1.5 },
240
269
  renderItem: renderErrorBarItem,
241
270
  data: errorBarData,
242
- zlevel: 0,
243
271
  });
244
272
  }
245
-
246
273
  return {
247
274
  xAxis: {
248
275
  type: 'value',
@@ -4,6 +4,8 @@ export type DataPoint = {
4
4
  value: [number, number];
5
5
  metadata: any;
6
6
  error_value?: number;
7
+ disagreement?: boolean | null;
8
+ highlighted?: boolean | null;
7
9
  };
8
10
  export type ScatterPlotEchartsEvent = echarts.ECElementEvent & {
9
11
  data: DataPoint;
@@ -19,6 +21,7 @@ type ScatterPlotProps = {
19
21
  showConfidenceBand?: boolean;
20
22
  showLegend?: boolean;
21
23
  legendItems?: LegendItem[];
24
+ highlightIndex?: number | null;
22
25
  children?: Snippet;
23
26
  } & Omit<GenericChartProps, 'timeIndex'>;
24
27
  declare const Scatterplot: import("svelte").Component<ScatterPlotProps, {}, "">;
@@ -8,6 +8,7 @@
8
8
  | 'secondary'
9
9
  | 'transparent'
10
10
  | 'outline'
11
+ | 'accent'
11
12
  | 'danger'
12
13
  | 'secondary-inverse'
13
14
  | 'transparent-inverse';
@@ -164,6 +165,14 @@
164
165
  }
165
166
  .color-secondary:hover {
166
167
  background-color: #12192A1A
168
+ }
169
+ .color-accent {
170
+ background-color: #5750ee1A;
171
+ --tw-text-opacity: 1;
172
+ color: rgb(255 255 255 / var(--tw-text-opacity, 1))
173
+ }
174
+ .color-accent:hover {
175
+ background-color: #5750ee40
167
176
  }
168
177
  .color-transparent {
169
178
  background-color: transparent;
@@ -1,6 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLButtonAttributes } from 'svelte/elements';
3
- type Variant = 'primary' | 'secondary' | 'transparent' | 'outline' | 'danger' | 'secondary-inverse' | 'transparent-inverse';
3
+ type Variant = 'primary' | 'secondary' | 'transparent' | 'outline' | 'accent' | 'danger' | 'secondary-inverse' | 'transparent-inverse';
4
4
  interface Props extends HTMLButtonAttributes {
5
5
  variant?: Variant;
6
6
  type?: 'button' | 'submit' | 'reset' | null | undefined;
@@ -4,7 +4,7 @@ import type { textColor, backgroundColor } from './../../tokens';
4
4
  export * from './custom';
5
5
  import type { CustomIconName } from './custom';
6
6
  export type PhosphorIconProps = Component<IconComponentProps, object, ''>;
7
- export type IconName = 'Aperture' | 'Archive' | 'ArrowFatLineRight' | 'ArrowCounterClockwise' | 'ArrowRight' | 'ArrowUpRight' | 'ArrowUpLeft' | 'ArrowUUpLeft' | 'Barcode' | 'Bell' | 'BookOpen' | 'BookOpenText' | 'BowlingBall' | 'BugBeetle' | 'Calendar' | 'CalendarBlank' | 'Camera' | 'CameraSlash' | 'CaretDown' | 'CaretLeft' | 'CaretRight' | 'CaretUp' | 'CaretUpDown' | 'ChatTeardropDots' | 'Check' | 'CheckCircle' | 'CheckFat' | 'Circle' | 'CircleDashed' | 'CircleHalf' | 'CirclesFour' | 'CirclesThreePlus' | 'Clock' | 'ClockCountdown' | 'Copy' | 'Crop' | 'Cube' | 'CursorClick' | 'CraneTower' | 'Database' | 'Dna' | 'DotsThree' | 'DotsThreeOutlineVertical' | 'DownloadSimple' | 'Drop' | 'EnvelopeSimple' | 'Eye' | 'Eyedropper' | 'EyeSlash' | 'FileCsv' | 'Flag' | 'Flask' | 'Folder' | 'FolderDashed' | 'FolderSimplePlus' | 'FunnelSimple' | 'Gear' | 'GlobeSimple' | 'GlobeSimpleX' | 'GridFour' | 'Hash' | 'House' | 'ImageSquare' | 'ImagesSquare' | 'Info' | 'IntersectThree' | 'Lock' | 'LineSegments' | 'List' | 'Link' | 'ListMagnifyingGlass' | 'MagnifyingGlass' | 'MegaphoneSimple' | 'MicrosoftExcelLogo' | 'Moon' | 'Minus' | 'Palette' | 'Pause' | 'Pencil' | 'PencilSimple' | 'Percent' | 'Phone' | 'Plant' | 'Play' | 'Plus' | 'PlusMinus' | 'Ruler' | 'Question' | 'SealCheck' | 'RadioButton' | 'SealQuestion' | 'SealWarning' | 'SelectionAll' | 'Share' | 'SidebarSimple' | 'SkipBack' | 'SkipForward' | 'SignIn' | 'SignOut' | 'SortAscending' | 'Sparkle' | 'SpinnerGap' | 'SquaresFour' | 'Star' | 'Stop' | 'StopCircle' | 'Sun' | 'Table' | 'Tag' | 'Target' | 'TestTube' | 'Trash' | 'TrashSimple' | 'TreeStructure' | 'UserCircle' | 'UserPlus' | 'Video' | 'Warning' | 'WarningCircle' | 'WifiSlash' | 'X' | 'XCircle';
7
+ export type IconName = 'Aperture' | 'Archive' | 'ArrowFatLineRight' | 'ArrowCounterClockwise' | 'ArrowRight' | 'ArrowUpRight' | 'ArrowUpLeft' | 'ArrowUUpLeft' | 'Barcode' | 'Bell' | 'BookOpen' | 'BookOpenText' | 'BowlingBall' | 'BugBeetle' | 'Calendar' | 'CalendarBlank' | 'Camera' | 'CameraSlash' | 'CaretDown' | 'CaretLeft' | 'CaretRight' | 'CaretUp' | 'CaretUpDown' | 'ChatTeardropDots' | 'Check' | 'CheckCircle' | 'CheckFat' | 'Circle' | 'CircleDashed' | 'CircleHalf' | 'CirclesFour' | 'CirclesThreePlus' | 'Clock' | 'ClockCountdown' | 'Copy' | 'Crop' | 'Cube' | 'CursorClick' | 'CraneTower' | 'Database' | 'Dna' | 'DotsThree' | 'DotsThreeOutlineVertical' | 'DownloadSimple' | 'Drop' | 'EnvelopeSimple' | 'Eye' | 'Eyedropper' | 'EyeSlash' | 'FileCsv' | 'Flag' | 'Flask' | 'Folder' | 'FolderDashed' | 'FolderSimplePlus' | 'FunnelSimple' | 'Gear' | 'GlobeSimple' | 'GlobeSimpleX' | 'GridFour' | 'Hash' | 'House' | 'ImageSquare' | 'ImagesSquare' | 'Info' | 'IntersectThree' | 'Lock' | 'LineSegments' | 'List' | 'Link' | 'ListMagnifyingGlass' | 'MagnifyingGlass' | 'MegaphoneSimple' | 'MicrosoftExcelLogo' | 'Moon' | 'Minus' | 'Palette' | 'Pause' | 'Pencil' | 'PencilSimple' | 'Percent' | 'Phone' | 'Plant' | 'Play' | 'Plus' | 'PlusMinus' | 'Ruler' | 'Question' | 'SealCheck' | 'RadioButton' | 'SealQuestion' | 'SealWarning' | 'SelectionAll' | 'Share' | 'SidebarSimple' | 'SkipBack' | 'SkipForward' | 'SignIn' | 'SignOut' | 'SortAscending' | 'Sparkle' | 'SpinnerGap' | 'SquaresFour' | 'Star' | 'Stop' | 'StopCircle' | 'Sun' | 'Table' | 'Tag' | 'Target' | 'TestTube' | 'Trash' | 'TrashSimple' | 'TreeStructure' | 'UserCircle' | 'UserPlus' | 'Users' | 'Video' | 'Warning' | 'WarningCircle' | 'WifiSlash' | 'X' | 'XCircle';
8
8
  export type AllIconName = IconName | CustomIconName;
9
9
  export declare const iconMap: Record<IconName, PhosphorIconProps>;
10
10
  export declare function renderIcon(iconName: keyof typeof iconMap): PhosphorIconProps;
@@ -115,6 +115,7 @@ import TrashSimple from 'phosphor-svelte/lib/TrashSimple';
115
115
  import TreeStructure from 'phosphor-svelte/lib/TreeStructure';
116
116
  import UserCircle from 'phosphor-svelte/lib/UserCircle';
117
117
  import UserPlus from 'phosphor-svelte/lib/UserPlus';
118
+ import Users from 'phosphor-svelte/lib/Users';
118
119
  import Video from 'phosphor-svelte/lib/Video';
119
120
  import Warning from 'phosphor-svelte/lib/Warning';
120
121
  import WarningCircle from 'phosphor-svelte/lib/WarningCircle';
@@ -240,6 +241,7 @@ export const iconMap = {
240
241
  TreeStructure,
241
242
  UserCircle,
242
243
  UserPlus,
244
+ Users,
243
245
  Video,
244
246
  Warning,
245
247
  WarningCircle,
@@ -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 class="flex w-full flex-col gap-2">
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 for={id ?? inputId} class="block px-1 text-sm capitalize text-secondary">
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
- class="flex w-full items-center gap-1 size-{size} transition-colors"
82
- class:!border-error={!valid}
83
- class:primary={variant === 'primary'}
84
- class:secondary={variant === 'secondary'}
85
- class:transparent={variant === 'transparent'}
86
- class:borderless={variant === 'borderless'}
87
- >
88
- <div class="whitespace-nowrap text-secondary">
89
- {@render prefix?.({ valid })}
90
- </div>
91
- <input
92
- {id}
93
- class="inline leading-none"
94
- class:has-text={value}
95
- class:has-placeholder={rest.placeholder}
96
- aria-label={label}
97
- {type}
98
- oninput={handleInput}
99
- onblur={handleBlur}
100
- bind:this={input}
101
- bind:value
102
- {...rest}
103
- />
104
- <div class="whitespace-nowrap text-secondary">
105
- {@render suffix?.()}
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
- {#if clearable && value}
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(251 251 251 / var(--tw-bg-opacity, 1))
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-24 select-none truncate text-xs"
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-neutral">
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-x-4 gap-y-1 overflow-clip py-1">
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?: 'default' | 'light';
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 = 'default',
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 p-4 text-left transition-colors"
98
- class:bg-neutral={variant === 'default'}
99
- class:bg-surface={variant === 'light'}
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 class="flex items-center justify-start gap-2 truncate font-medium text-secondary">
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 h-8 w-full items-center gap-1">
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 font-medium">{formattedValue}</p>
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 class="flex-1 truncate text-2xl font-medium text-tertiary">{unit}</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?: 'default' | 'light';
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
- {#if label}
70
- <label for={inputId} class="block px-1 py-2 text-sm text-secondary">
71
- {label}
72
- {#if rest.required}
73
- <span class="ml-0.5 text-danger">*</span>
74
- {/if}
75
- </label>
76
- {/if}
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
- <textarea
79
- id={inputId}
80
- class="flex w-full items-center gap-1 rounded-lg border border-input bg-surface px-3 py-2 shadow-input focus-within:border-focus hover:border-hover {className ??
81
- ''}"
82
- class:!border-error={error}
83
- class:has-text={value}
84
- class:has-placeholder={placeholder}
85
- {placeholder}
86
- oninput={handleInputInternal}
87
- onblur={handleBlurInternal}
88
- bind:this={input}
89
- {value}
90
- {...rest}
91
- {onkeydown}
92
- ></textarea>
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>
@@ -7,6 +7,7 @@ interface TextareaProps extends HTMLTextareaAttributes {
7
7
  input?: HTMLTextAreaElement;
8
8
  size?: 'xs' | 'sm' | 'md' | 'lg' | 'dynamic';
9
9
  label?: string;
10
+ labelPlacement?: 'top' | 'left';
10
11
  error?: Snippet;
11
12
  placeholder?: string;
12
13
  }
@@ -1,6 +1,6 @@
1
1
  export const colors = {
2
2
  base: {
3
- mist: '#fbfbfb',
3
+ mist: '#fafafa',
4
4
  snow: '#f6f7f7',
5
5
  white: {
6
6
  default: '#FFFFFF',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reshape-biotech/design-system",
3
- "version": "2.6.3",
3
+ "version": "2.6.5",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build",