Combobox with Tags Input
Combobox 선택값을 TagsInput 칩으로 렌더링해 여러 담당자를 검색, 선택, 제거합니다.
<script lang="ts">
import * as Combobox from '@odbd/svelte/combobox'
import { useListCollection } from '@odbd/svelte/combobox'
import * as TagsInput from '@odbd/svelte/tags-input'
const assignees = ['강민수', '김서연', '박지훈', '오하린', '이준호', '정다은']
const assigneeCollection = useListCollection({
initialItems: assignees,
filter: (item, query) => item.toLowerCase().includes(query.toLowerCase()),
})
let selectedAssignees = $state(['강민수', '오하린'])
let query = $state('')
const updateTags = (details: { value: string[] }) => {
selectedAssignees = details.value
}
const updateComboboxValue = (details: { value: string[] }) => {
selectedAssignees = details.value
query = ''
assigneeCollection.filter('')
}
const updateQuery = (details: { inputValue: string }) => {
query = details.inputValue
assigneeCollection.filter(details.inputValue)
}
</script>
<div class="tag-combobox">
<TagsInput.Root value={selectedAssignees} onValueChange={updateTags}>
<TagsInput.Label>선택된 담당자</TagsInput.Label>
<TagsInput.Control class="tag-combobox__chips">
{#each selectedAssignees as assignee, index}
<TagsInput.Item {index} value={assignee}>
<TagsInput.ItemPreview>
<TagsInput.ItemText>{assignee}</TagsInput.ItemText>
<TagsInput.ItemDeleteTrigger aria-label={`${assignee} 제거`}
>×</TagsInput.ItemDeleteTrigger
>
</TagsInput.ItemPreview>
<TagsInput.ItemInput />
</TagsInput.Item>
{:else}
<span class="tag-combobox__empty">아직 선택한 담당자가 없어요.</span>
{/each}
{#if selectedAssignees.length}
<TagsInput.ClearTrigger>모두 지우기</TagsInput.ClearTrigger>
{/if}
</TagsInput.Control>
<TagsInput.HiddenInput />
</TagsInput.Root>
<Combobox.Root
collection={assigneeCollection.collection}
value={selectedAssignees}
inputValue={query}
multiple
closeOnSelect={false}
onValueChange={updateComboboxValue}
onInputValueChange={updateQuery}
>
<Combobox.Label>담당자 추가</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="이름으로 검색하세요" />
<Combobox.ClearTrigger aria-label="검색어 지우기">×</Combobox.ClearTrigger>
<Combobox.Trigger aria-label="담당자 목록 열기">⌄</Combobox.Trigger>
</Combobox.Control>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.List>
<Combobox.Empty>일치하는 담당자가 없어요.</Combobox.Empty>
{#each assigneeCollection.collection().items as item}
<Combobox.Item {item} class="tag-combobox__option">
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Positioner>
</Combobox.Root>
</div>
<style>
.tag-combobox {
display: grid;
width: min(100%, 28rem);
gap: var(--odbd-space-4);
}
:global(.tag-combobox__chips) {
align-items: center;
min-height: 3rem;
}
.tag-combobox__empty {
color: var(--odbd-color-muted-foreground);
font-size: var(--odbd-font-size-sm);
}
:global(.tag-combobox__option) {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--odbd-space-3);
}
</style>