@reshape-biotech/design-system 2.7.35 → 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 +386 -115
- package/dist/components/combobox/components/combobox-add.svelte +10 -4
- package/dist/components/combobox/components/combobox-content.svelte +35 -60
- 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 +17 -17
- 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 +89 -14
- package/dist/components/select/components/SelectContent.svelte +1 -1
- 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/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script module lang="ts">
|
|
2
2
|
import Plus from 'phosphor-svelte/lib/Plus';
|
|
3
3
|
import List from 'phosphor-svelte/lib/List';
|
|
4
|
+
import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
|
|
4
5
|
import { Icon } from '../icons/index.js';
|
|
5
6
|
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
6
7
|
import { userEvent, within } from '@storybook/test';
|
|
@@ -10,6 +11,8 @@
|
|
|
10
11
|
import Divider from '../divider/Divider.svelte';
|
|
11
12
|
import Tag from '../tag/Tag.svelte';
|
|
12
13
|
import Button from '../button/Button.svelte';
|
|
14
|
+
import Input from '../input/Input.svelte';
|
|
15
|
+
import Checkbox from '../checkbox/Checkbox.svelte';
|
|
13
16
|
|
|
14
17
|
const { Story } = defineMeta({
|
|
15
18
|
component: ComboboxRootForMeta,
|
|
@@ -60,11 +63,47 @@
|
|
|
60
63
|
let searchValueSingle = $state('');
|
|
61
64
|
let searchValueGrouped = $state('');
|
|
62
65
|
let searchValueCustom = $state('');
|
|
66
|
+
let searchValueWithoutWidth = $state('');
|
|
67
|
+
let searchValueWithWidth = $state('');
|
|
63
68
|
|
|
64
69
|
let selected = $state<string[]>([]);
|
|
65
70
|
let selectedSingle = $state<string>('');
|
|
66
71
|
let selectedGrouped = $state<string[]>([]);
|
|
67
72
|
let selectedCustom = $state<string[]>([]);
|
|
73
|
+
let selectedWithoutWidth = $state<string>('');
|
|
74
|
+
let selectedWithWidth = $state<string>('');
|
|
75
|
+
|
|
76
|
+
// Organism pattern story state
|
|
77
|
+
let selectedOrganisms = $state<string[]>([]);
|
|
78
|
+
let organismSearchValue = $state('');
|
|
79
|
+
let organismAnchor = $state<HTMLElement>(null!);
|
|
80
|
+
|
|
81
|
+
const organismItems = [
|
|
82
|
+
{ value: 'e-coli', label: 'E. coli', level: 'species' },
|
|
83
|
+
{ value: 's-aureus', label: 'S. aureus', level: 'species' },
|
|
84
|
+
{ value: 'b-subtilis', label: 'B. subtilis', level: 'species' },
|
|
85
|
+
{ value: 'p-aeruginosa', label: 'P. aeruginosa', level: 'species' },
|
|
86
|
+
];
|
|
87
|
+
const recentItems = [
|
|
88
|
+
{ value: 'e-coli', label: 'E. coli', level: 'species' },
|
|
89
|
+
{ value: 's-aureus', label: 'S. aureus', level: 'species' },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const filteredOrganismItems = $derived(
|
|
93
|
+
organismSearchValue === ''
|
|
94
|
+
? organismItems
|
|
95
|
+
: organismItems.filter((item) =>
|
|
96
|
+
item.label.toLowerCase().includes(organismSearchValue.toLowerCase())
|
|
97
|
+
)
|
|
98
|
+
);
|
|
99
|
+
const isSearchingOrganisms = $derived(organismSearchValue.trim() !== '');
|
|
100
|
+
const hasExactOrganismMatch = $derived(
|
|
101
|
+
isSearchingOrganisms &&
|
|
102
|
+
organismItems.some(
|
|
103
|
+
(o) => o.label.toLowerCase() === organismSearchValue.trim().toLowerCase()
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
const shouldShowCreateNewOrganism = $derived(isSearchingOrganisms && !hasExactOrganismMatch);
|
|
68
107
|
|
|
69
108
|
const filteredFruits = $derived(
|
|
70
109
|
searchValue === ''
|
|
@@ -96,6 +135,22 @@
|
|
|
96
135
|
)
|
|
97
136
|
);
|
|
98
137
|
|
|
138
|
+
const filteredFruitsWithoutWidth = $derived(
|
|
139
|
+
searchValueWithoutWidth === ''
|
|
140
|
+
? fruits
|
|
141
|
+
: fruits.filter((fruit) =>
|
|
142
|
+
fruit.label.toLowerCase().includes(searchValueWithoutWidth.toLowerCase())
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const filteredFruitsWithWidth = $derived(
|
|
147
|
+
searchValueWithWidth === ''
|
|
148
|
+
? fruits
|
|
149
|
+
: fruits.filter((fruit) =>
|
|
150
|
+
fruit.label.toLowerCase().includes(searchValueWithWidth.toLowerCase())
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
|
|
99
154
|
const exactMatch = $derived(
|
|
100
155
|
filteredFruits.find((fruit) => fruit.value.toLowerCase() === searchValue.toLowerCase())
|
|
101
156
|
);
|
|
@@ -111,6 +166,8 @@
|
|
|
111
166
|
let customAnchorSingle = $state<HTMLElement>(null!);
|
|
112
167
|
let customAnchorGrouped = $state<HTMLElement>(null!);
|
|
113
168
|
let customAnchorCustom = $state<HTMLElement>(null!);
|
|
169
|
+
let customAnchorWithoutWidth = $state<HTMLElement>(null!);
|
|
170
|
+
let customAnchorWithWidth = $state<HTMLElement>(null!);
|
|
114
171
|
|
|
115
172
|
// Generate a long list of countries for scrolling demo
|
|
116
173
|
const countries = [
|
|
@@ -186,58 +243,58 @@
|
|
|
186
243
|
</Tag>
|
|
187
244
|
{/each}
|
|
188
245
|
</div>
|
|
189
|
-
<
|
|
190
|
-
<
|
|
191
|
-
<
|
|
246
|
+
<div class="flex items-center justify-center">
|
|
247
|
+
<Combobox.Trigger>
|
|
248
|
+
<div bind:this={customAnchor}>
|
|
249
|
+
<IconButton rounded={false}>
|
|
192
250
|
<Icon>
|
|
193
251
|
{#snippet children(props)}
|
|
194
252
|
<Plus {...props} />
|
|
195
253
|
{/snippet}
|
|
196
254
|
</Icon>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
255
|
+
</IconButton>
|
|
256
|
+
</div>
|
|
257
|
+
</Combobox.Trigger>
|
|
258
|
+
</div>
|
|
200
259
|
<Combobox.Content {customAnchor} class="flex flex-col justify-between">
|
|
201
|
-
|
|
260
|
+
{#snippet header()}
|
|
202
261
|
<Combobox.Input
|
|
203
262
|
placeholder="Search a fruit"
|
|
204
263
|
oninput={(e: Event) => (searchValue = (e.target as HTMLInputElement).value)}
|
|
205
264
|
autofocus
|
|
206
265
|
/>
|
|
207
266
|
<Divider />
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<Combobox.
|
|
212
|
-
|
|
213
|
-
{
|
|
214
|
-
|
|
215
|
-
{
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
</Combobox.Group>
|
|
226
|
-
{/if}
|
|
227
|
-
</div>
|
|
228
|
-
{#if !exactMatch && searchValue !== ''}
|
|
229
|
-
<Divider />
|
|
230
|
-
|
|
231
|
-
<Combobox.Add
|
|
232
|
-
onclick={() => {
|
|
233
|
-
selected.push(searchValue);
|
|
234
|
-
fruits.push({ value: searchValue, label: searchValue });
|
|
235
|
-
searchValue = '';
|
|
236
|
-
}}
|
|
237
|
-
>
|
|
238
|
-
<p>Add new fruit</p>
|
|
239
|
-
</Combobox.Add>
|
|
267
|
+
{/snippet}
|
|
268
|
+
{#if filteredFruits.length > 0}
|
|
269
|
+
<Combobox.Group>
|
|
270
|
+
<Combobox.GroupHeading>Fruits</Combobox.GroupHeading>
|
|
271
|
+
{#each filteredFruits as fruit (fruit.value)}
|
|
272
|
+
<Combobox.Item value={fruit.value} label={fruit.label}>
|
|
273
|
+
{#snippet children({ selected })}
|
|
274
|
+
{fruit.label}
|
|
275
|
+
{#if selected}
|
|
276
|
+
<Combobox.Indicator />
|
|
277
|
+
{/if}
|
|
278
|
+
{/snippet}
|
|
279
|
+
</Combobox.Item>
|
|
280
|
+
{:else}
|
|
281
|
+
<span class="block px-5 py-2 text-sm text-secondary text-center"> No results found </span>
|
|
282
|
+
{/each}
|
|
283
|
+
</Combobox.Group>
|
|
240
284
|
{/if}
|
|
285
|
+
{#snippet footer()}
|
|
286
|
+
{#if !exactMatch && searchValue !== ''}
|
|
287
|
+
<Combobox.Add
|
|
288
|
+
onclick={() => {
|
|
289
|
+
selected.push(searchValue);
|
|
290
|
+
fruits.push({ value: searchValue, label: searchValue });
|
|
291
|
+
searchValue = '';
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
<p>Add new fruit</p>
|
|
295
|
+
</Combobox.Add>
|
|
296
|
+
{/if}
|
|
297
|
+
{/snippet}
|
|
241
298
|
</Combobox.Content>
|
|
242
299
|
</Combobox.Root>
|
|
243
300
|
</Story>
|
|
@@ -252,13 +309,15 @@
|
|
|
252
309
|
items={filteredFruitsSingle}
|
|
253
310
|
bind:value={selectedSingle}
|
|
254
311
|
>
|
|
255
|
-
<
|
|
256
|
-
<
|
|
257
|
-
<
|
|
312
|
+
<div class="flex items-center justify-center">
|
|
313
|
+
<Combobox.Trigger>
|
|
314
|
+
<div bind:this={customAnchorSingle}>
|
|
315
|
+
<Button variant="secondary" size="sm">
|
|
258
316
|
{selectedSingle ? selectedSingle : 'Select a fruit'}
|
|
259
317
|
</Button>
|
|
260
|
-
|
|
261
|
-
|
|
318
|
+
</div>
|
|
319
|
+
</Combobox.Trigger>
|
|
320
|
+
</div>
|
|
262
321
|
<Combobox.Content class="flex flex-col justify-between" customAnchor={customAnchorSingle}>
|
|
263
322
|
<div class="flex flex-grow flex-col">
|
|
264
323
|
{#if filteredFruitsSingle.length > 0}
|
|
@@ -274,7 +333,7 @@
|
|
|
274
333
|
{/snippet}
|
|
275
334
|
</Combobox.Item>
|
|
276
335
|
{:else}
|
|
277
|
-
<span class="block px-5 py-2 text-sm text-
|
|
336
|
+
<span class="block px-5 py-2 text-sm text-secondary text-center"> No results found </span>
|
|
278
337
|
{/each}
|
|
279
338
|
</Combobox.Group>
|
|
280
339
|
{/if}
|
|
@@ -300,9 +359,10 @@
|
|
|
300
359
|
</Tag>
|
|
301
360
|
{/each}
|
|
302
361
|
</div>
|
|
303
|
-
<
|
|
304
|
-
<
|
|
305
|
-
<
|
|
362
|
+
<div class="flex items-center justify-center">
|
|
363
|
+
<Combobox.Trigger>
|
|
364
|
+
<div bind:this={customAnchorGrouped}>
|
|
365
|
+
<Button variant="primary" size="sm">
|
|
306
366
|
<Icon>
|
|
307
367
|
{#snippet children(props)}
|
|
308
368
|
<List {...props} />
|
|
@@ -310,18 +370,18 @@
|
|
|
310
370
|
</Icon>
|
|
311
371
|
Select fruits by category
|
|
312
372
|
</Button>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
373
|
+
</div>
|
|
374
|
+
</Combobox.Trigger>
|
|
375
|
+
</div>
|
|
376
|
+
<Combobox.Content class="flex flex-col justify-between" customAnchor={customAnchorGrouped} matchTriggerWidth>
|
|
377
|
+
{#snippet header()}
|
|
317
378
|
<Combobox.Input
|
|
318
379
|
placeholder="Search across categories"
|
|
319
380
|
oninput={(e: Event) => (searchValueGrouped = (e.target as HTMLInputElement).value)}
|
|
320
381
|
autofocus
|
|
321
382
|
/>
|
|
322
383
|
<Divider />
|
|
323
|
-
|
|
324
|
-
<div class="flex flex-grow flex-col">
|
|
384
|
+
{/snippet}
|
|
325
385
|
{#if searchValueGrouped === ''}
|
|
326
386
|
{#each categories as category}
|
|
327
387
|
<Combobox.Group>
|
|
@@ -358,7 +418,6 @@
|
|
|
358
418
|
{:else}
|
|
359
419
|
<span class="text-muted-foreground block px-5 py-2 text-sm"> No results found </span>
|
|
360
420
|
{/if}
|
|
361
|
-
</div>
|
|
362
421
|
</Combobox.Content>
|
|
363
422
|
</Combobox.Root>
|
|
364
423
|
</Story>
|
|
@@ -380,9 +439,10 @@
|
|
|
380
439
|
</Tag>
|
|
381
440
|
{/each}
|
|
382
441
|
</div>
|
|
383
|
-
<
|
|
384
|
-
<
|
|
385
|
-
<
|
|
442
|
+
<div class="flex items-center justify-center">
|
|
443
|
+
<Combobox.Trigger>
|
|
444
|
+
<div bind:this={customAnchorCountries}>
|
|
445
|
+
<Button variant="primary" size="sm">
|
|
386
446
|
<Icon>
|
|
387
447
|
{#snippet children(props)}
|
|
388
448
|
<List {...props} />
|
|
@@ -390,39 +450,38 @@
|
|
|
390
450
|
</Icon>
|
|
391
451
|
Select countries
|
|
392
452
|
</Button>
|
|
393
|
-
|
|
394
|
-
|
|
453
|
+
</div>
|
|
454
|
+
</Combobox.Trigger>
|
|
455
|
+
</div>
|
|
395
456
|
<Combobox.Content
|
|
396
457
|
class="flex flex-col justify-between"
|
|
397
458
|
customAnchor={customAnchorCountries}
|
|
398
459
|
>
|
|
399
|
-
|
|
460
|
+
{#snippet header()}
|
|
400
461
|
<Combobox.Input
|
|
401
462
|
placeholder="Search for a country"
|
|
402
463
|
oninput={(e: Event) => (searchValueCountries = (e.target as HTMLInputElement).value)}
|
|
403
464
|
autofocus
|
|
404
465
|
/>
|
|
405
466
|
<Divider />
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
<Combobox.
|
|
410
|
-
|
|
411
|
-
{
|
|
412
|
-
|
|
413
|
-
{
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
{/if}
|
|
425
|
-
</div>
|
|
467
|
+
{/snippet}
|
|
468
|
+
{#if filteredCountries.length > 0}
|
|
469
|
+
<Combobox.Group>
|
|
470
|
+
<Combobox.GroupHeading>Countries</Combobox.GroupHeading>
|
|
471
|
+
{#each filteredCountries as country (country.value)}
|
|
472
|
+
<Combobox.Item value={country.value} label={country.label}>
|
|
473
|
+
{#snippet children({ selected })}
|
|
474
|
+
{country.label}
|
|
475
|
+
{#if selected}
|
|
476
|
+
<Combobox.Indicator />
|
|
477
|
+
{/if}
|
|
478
|
+
{/snippet}
|
|
479
|
+
</Combobox.Item>
|
|
480
|
+
{/each}
|
|
481
|
+
</Combobox.Group>
|
|
482
|
+
{:else}
|
|
483
|
+
<span class="block px-5 py-2 text-sm text-secondary text-center"> No results found </span>
|
|
484
|
+
{/if}
|
|
426
485
|
</Combobox.Content>
|
|
427
486
|
<div class="mt-4 rounded bg-blue-50 p-2 text-xs text-blue-700">
|
|
428
487
|
This story demonstrates the 80vh max-height with overflow scrolling for long lists (40 countries).
|
|
@@ -509,23 +568,25 @@
|
|
|
509
568
|
</Tag>
|
|
510
569
|
{/each}
|
|
511
570
|
</div>
|
|
512
|
-
<
|
|
513
|
-
<
|
|
514
|
-
<
|
|
571
|
+
<div class="flex items-center justify-center">
|
|
572
|
+
<Combobox.Trigger data-testid="combobox-trigger">
|
|
573
|
+
<div bind:this={customAnchor}>
|
|
574
|
+
<IconButton rounded={false}>
|
|
515
575
|
<Icon>
|
|
516
576
|
{#snippet children(props)}
|
|
517
577
|
<Plus {...props} />
|
|
518
578
|
{/snippet}
|
|
519
579
|
</Icon>
|
|
520
580
|
</IconButton>
|
|
521
|
-
|
|
522
|
-
|
|
581
|
+
</div>
|
|
582
|
+
</Combobox.Trigger>
|
|
583
|
+
</div>
|
|
523
584
|
<Combobox.Content
|
|
524
585
|
{customAnchor}
|
|
525
586
|
class="flex flex-col justify-between"
|
|
526
587
|
data-testid="combobox-content"
|
|
527
588
|
>
|
|
528
|
-
|
|
589
|
+
{#snippet header()}
|
|
529
590
|
<Combobox.Input
|
|
530
591
|
placeholder="Search a fruit"
|
|
531
592
|
oninput={(e: Event) => (searchValue = (e.target as HTMLInputElement).value)}
|
|
@@ -533,17 +594,85 @@
|
|
|
533
594
|
data-testid="combobox-input"
|
|
534
595
|
/>
|
|
535
596
|
<Divider />
|
|
597
|
+
{/snippet}
|
|
598
|
+
{#if filteredFruits.length > 0}
|
|
599
|
+
<Combobox.Group>
|
|
600
|
+
<Combobox.GroupHeading>Fruits</Combobox.GroupHeading>
|
|
601
|
+
{#each filteredFruits as fruit (fruit.value)}
|
|
602
|
+
<Combobox.Item
|
|
603
|
+
value={fruit.value}
|
|
604
|
+
label={fruit.label}
|
|
605
|
+
data-testid={`fruit-option-${fruit.value}`}
|
|
606
|
+
>
|
|
607
|
+
{#snippet children({ selected })}
|
|
608
|
+
{fruit.label}
|
|
609
|
+
{#if selected}
|
|
610
|
+
<Combobox.Indicator />
|
|
611
|
+
{/if}
|
|
612
|
+
{/snippet}
|
|
613
|
+
</Combobox.Item>
|
|
614
|
+
{:else}
|
|
615
|
+
<span class="block px-5 py-2 text-sm text-secondary text-center">
|
|
616
|
+
No results found
|
|
617
|
+
</span>
|
|
618
|
+
{/each}
|
|
619
|
+
</Combobox.Group>
|
|
620
|
+
{/if}
|
|
621
|
+
{#snippet footer()}
|
|
622
|
+
{#if !exactMatch && searchValue !== ''}
|
|
623
|
+
<Combobox.Add
|
|
624
|
+
data-testid="add-new-fruit"
|
|
625
|
+
onclick={() => {
|
|
626
|
+
selected.push(searchValue);
|
|
627
|
+
fruits.push({ value: searchValue, label: searchValue });
|
|
628
|
+
searchValue = '';
|
|
629
|
+
}}
|
|
630
|
+
>
|
|
631
|
+
<p>Add new fruit</p>
|
|
632
|
+
</Combobox.Add>
|
|
633
|
+
{/if}
|
|
634
|
+
{/snippet}
|
|
635
|
+
</Combobox.Content>
|
|
636
|
+
</Combobox.Root>
|
|
637
|
+
</div>
|
|
638
|
+
</Story>
|
|
639
|
+
|
|
640
|
+
<Story name="Width Comparison" asChild>
|
|
641
|
+
<div class="flex gap-8 p-8">
|
|
642
|
+
<div class="flex flex-1 flex-col gap-4">
|
|
643
|
+
<p class="font-medium text-center text-secondary">matchTriggerWidth: false</p>
|
|
644
|
+
<Combobox.Root
|
|
645
|
+
onOpenChange={(o) => {
|
|
646
|
+
if (!o) searchValueWithoutWidth = '';
|
|
647
|
+
}}
|
|
648
|
+
type="single"
|
|
649
|
+
name="withoutWidth"
|
|
650
|
+
items={filteredFruitsWithoutWidth}
|
|
651
|
+
bind:value={selectedWithoutWidth}
|
|
652
|
+
>
|
|
653
|
+
<div class="flex w-full items-center justify-center">
|
|
654
|
+
<Combobox.Trigger class="w-full">
|
|
655
|
+
<div bind:this={customAnchorWithoutWidth} class="w-full">
|
|
656
|
+
<Button variant="secondary" size="sm" class="w-full">
|
|
657
|
+
Select a fruit
|
|
658
|
+
</Button>
|
|
659
|
+
</div>
|
|
660
|
+
</Combobox.Trigger>
|
|
536
661
|
</div>
|
|
537
|
-
<
|
|
538
|
-
{#
|
|
662
|
+
<Combobox.Content customAnchor={customAnchorWithoutWidth}>
|
|
663
|
+
{#snippet header()}
|
|
664
|
+
<Combobox.Input
|
|
665
|
+
placeholder="Search a fruit"
|
|
666
|
+
oninput={(e: Event) => (searchValueWithoutWidth = (e.target as HTMLInputElement).value)}
|
|
667
|
+
autofocus
|
|
668
|
+
/>
|
|
669
|
+
<Divider />
|
|
670
|
+
{/snippet}
|
|
671
|
+
{#if filteredFruitsWithoutWidth.length > 0}
|
|
539
672
|
<Combobox.Group>
|
|
540
673
|
<Combobox.GroupHeading>Fruits</Combobox.GroupHeading>
|
|
541
|
-
{#each
|
|
542
|
-
<Combobox.Item
|
|
543
|
-
value={fruit.value}
|
|
544
|
-
label={fruit.label}
|
|
545
|
-
data-testid={`fruit-option-${fruit.value}`}
|
|
546
|
-
>
|
|
674
|
+
{#each filteredFruitsWithoutWidth as fruit (fruit.value)}
|
|
675
|
+
<Combobox.Item value={fruit.value} label={fruit.label}>
|
|
547
676
|
{#snippet children({ selected })}
|
|
548
677
|
{fruit.label}
|
|
549
678
|
{#if selected}
|
|
@@ -551,29 +680,171 @@
|
|
|
551
680
|
{/if}
|
|
552
681
|
{/snippet}
|
|
553
682
|
</Combobox.Item>
|
|
554
|
-
{:else}
|
|
555
|
-
<span class="block px-5 py-2 text-sm text-muted-foreground">
|
|
556
|
-
No results found
|
|
557
|
-
</span>
|
|
558
683
|
{/each}
|
|
559
684
|
</Combobox.Group>
|
|
560
685
|
{/if}
|
|
561
|
-
</
|
|
562
|
-
|
|
563
|
-
|
|
686
|
+
</Combobox.Content>
|
|
687
|
+
</Combobox.Root>
|
|
688
|
+
</div>
|
|
564
689
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
690
|
+
<div class="flex flex-1 flex-col gap-4">
|
|
691
|
+
<p class="font-medium text-center text-secondary">matchTriggerWidth: true</p>
|
|
692
|
+
<Combobox.Root
|
|
693
|
+
onOpenChange={(o) => {
|
|
694
|
+
if (!o) searchValueWithWidth = '';
|
|
695
|
+
}}
|
|
696
|
+
type="single"
|
|
697
|
+
name="withWidth"
|
|
698
|
+
items={filteredFruitsWithWidth}
|
|
699
|
+
bind:value={selectedWithWidth}
|
|
700
|
+
>
|
|
701
|
+
<div class="flex w-full items-center justify-center">
|
|
702
|
+
<Combobox.Trigger class="w-full">
|
|
703
|
+
<div bind:this={customAnchorWithWidth} class="w-full">
|
|
704
|
+
<Button variant="secondary" size="sm" class="w-full">
|
|
705
|
+
Select a fruit
|
|
706
|
+
</Button>
|
|
707
|
+
</div>
|
|
708
|
+
</Combobox.Trigger>
|
|
709
|
+
</div>
|
|
710
|
+
<Combobox.Content matchTriggerWidth={true} customAnchor={customAnchorWithWidth}>
|
|
711
|
+
{#snippet header()}
|
|
712
|
+
<Combobox.Input
|
|
713
|
+
placeholder="Search a fruit"
|
|
714
|
+
oninput={(e: Event) => (searchValueWithWidth = (e.target as HTMLInputElement).value)}
|
|
715
|
+
autofocus
|
|
716
|
+
/>
|
|
717
|
+
<Divider />
|
|
718
|
+
{/snippet}
|
|
719
|
+
{#if filteredFruitsWithWidth.length > 0}
|
|
720
|
+
<Combobox.Group>
|
|
721
|
+
<Combobox.GroupHeading>Fruits</Combobox.GroupHeading>
|
|
722
|
+
{#each filteredFruitsWithWidth as fruit (fruit.value)}
|
|
723
|
+
<Combobox.Item value={fruit.value} label={fruit.label}>
|
|
724
|
+
{#snippet children({ selected })}
|
|
725
|
+
{fruit.label}
|
|
726
|
+
{#if selected}
|
|
727
|
+
<Combobox.Indicator />
|
|
728
|
+
{/if}
|
|
729
|
+
{/snippet}
|
|
730
|
+
</Combobox.Item>
|
|
731
|
+
{/each}
|
|
732
|
+
</Combobox.Group>
|
|
733
|
+
{/if}
|
|
734
|
+
</Combobox.Content>
|
|
735
|
+
</Combobox.Root>
|
|
736
|
+
</div>
|
|
578
737
|
</div>
|
|
579
738
|
</Story>
|
|
739
|
+
|
|
740
|
+
<Story name="Organism Pattern (Simplified)" asChild>
|
|
741
|
+
|
|
742
|
+
<div class="flex items-center justify-center p-8">
|
|
743
|
+
<div class="w-full max-w-md">
|
|
744
|
+
|
|
745
|
+
<Combobox.Root
|
|
746
|
+
type="multiple"
|
|
747
|
+
value={selectedOrganisms}
|
|
748
|
+
name="organism"
|
|
749
|
+
onOpenChange={(open) => {
|
|
750
|
+
if (!open) {
|
|
751
|
+
organismSearchValue = '';
|
|
752
|
+
}
|
|
753
|
+
}}
|
|
754
|
+
onValueChange={(value) => {
|
|
755
|
+
selectedOrganisms = value;
|
|
756
|
+
}}
|
|
757
|
+
allowDeselect
|
|
758
|
+
>
|
|
759
|
+
<Combobox.Trigger
|
|
760
|
+
class="w-full rounded-lg border border-dashed border-input bg-transparent text-secondary hover:border-interactive hover:bg-neutral hover:text-primary"
|
|
761
|
+
>
|
|
762
|
+
<div bind:this={organismAnchor} class="flex w-full flex-row items-center justify-center gap-2 py-2">
|
|
763
|
+
<Icon icon={Plus} color="secondary" />
|
|
764
|
+
<span>Add organism</span>
|
|
765
|
+
</div>
|
|
766
|
+
</Combobox.Trigger>
|
|
767
|
+
<Combobox.Content
|
|
768
|
+
customAnchor={organismAnchor}
|
|
769
|
+
class="flex max-h-[400px] min-w-[var(--bits-combobox-anchor-width)] w-[var(--bits-combobox-anchor-width)] flex-col justify-between"
|
|
770
|
+
portalled={false}
|
|
771
|
+
align="start"
|
|
772
|
+
matchTriggerWidth={true}
|
|
773
|
+
>
|
|
774
|
+
{#snippet header()}
|
|
775
|
+
<Combobox.Input>
|
|
776
|
+
{#snippet child()}
|
|
777
|
+
<Input
|
|
778
|
+
autofocus
|
|
779
|
+
size="md"
|
|
780
|
+
variant="borderless"
|
|
781
|
+
clearable
|
|
782
|
+
placeholder="Search for organism..."
|
|
783
|
+
bind:value={organismSearchValue}
|
|
784
|
+
>
|
|
785
|
+
{#snippet prefix()}
|
|
786
|
+
<Icon icon={MagnifyingGlass} class="flex pl-2"/>
|
|
787
|
+
{/snippet}
|
|
788
|
+
</Input>
|
|
789
|
+
{/snippet}
|
|
790
|
+
</Combobox.Input>
|
|
791
|
+
<Divider />
|
|
792
|
+
{/snippet}
|
|
793
|
+
|
|
794
|
+
{#if isSearchingOrganisms}
|
|
795
|
+
{#if filteredOrganismItems.length > 0}
|
|
796
|
+
<Combobox.Group>
|
|
797
|
+
{#each filteredOrganismItems as organism}
|
|
798
|
+
<Combobox.Item value={organism.value} label={organism.label}>
|
|
799
|
+
{#snippet children({ selected })}
|
|
800
|
+
<div class="flex w-full flex-row items-center justify-between">
|
|
801
|
+
<span class="flex flex-row items-center gap-3">
|
|
802
|
+
<Checkbox checked={selected} />
|
|
803
|
+
<span>{organism.label}</span>
|
|
804
|
+
</span>
|
|
805
|
+
<Tag size="xs" variant="default">{organism.level}</Tag>
|
|
806
|
+
</div>
|
|
807
|
+
{/snippet}
|
|
808
|
+
</Combobox.Item>
|
|
809
|
+
{/each}
|
|
810
|
+
</Combobox.Group>
|
|
811
|
+
{/if}
|
|
812
|
+
{:else}
|
|
813
|
+
<Combobox.Group>
|
|
814
|
+
<Combobox.GroupHeading>Recently used</Combobox.GroupHeading>
|
|
815
|
+
{#each recentItems as organism}
|
|
816
|
+
<Combobox.Item value={organism.value} label={organism.label}>
|
|
817
|
+
{#snippet children({ selected })}
|
|
818
|
+
<div class="flex w-full flex-row items-center justify-between gap-2">
|
|
819
|
+
<span class="flex flex-row items-center gap-3">
|
|
820
|
+
<Checkbox checked={selected} />
|
|
821
|
+
{organism.label}
|
|
822
|
+
</span>
|
|
823
|
+
<Tag size="xs">{organism.level}</Tag>
|
|
824
|
+
</div>
|
|
825
|
+
{/snippet}
|
|
826
|
+
</Combobox.Item>
|
|
827
|
+
{/each}
|
|
828
|
+
</Combobox.Group>
|
|
829
|
+
{/if}
|
|
830
|
+
|
|
831
|
+
{#snippet footer()}
|
|
832
|
+
{#if shouldShowCreateNewOrganism}
|
|
833
|
+
<Combobox.Add>
|
|
834
|
+
<p>Create new organism: <span class="font-medium">"{organismSearchValue}"</span></p>
|
|
835
|
+
</Combobox.Add>
|
|
836
|
+
{/if}
|
|
837
|
+
{/snippet}
|
|
838
|
+
</Combobox.Content>
|
|
839
|
+
</Combobox.Root>
|
|
840
|
+
|
|
841
|
+
{#if selectedOrganisms.length > 0}
|
|
842
|
+
<div class="mt-4 flex flex-wrap gap-2">
|
|
843
|
+
{#each selectedOrganisms as value}
|
|
844
|
+
<Tag>{organismItems.find((o) => o.value === value)?.label || value}</Tag>
|
|
845
|
+
{/each}
|
|
846
|
+
</div>
|
|
847
|
+
{/if}
|
|
848
|
+
</div>
|
|
849
|
+
</div>
|
|
850
|
+
</Story>
|