Files
shd.build/src/lib/components/SearchableAssignment.svelte
T

61 lines
2.7 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
type Item = { id: string; name: string; pending?: boolean; proposerName?: string };
let {
items,
selected = $bindable<string[]>([]),
excludedIds = [],
fieldName,
emptyLabel = 'NO MATCHING RECORDS',
subjectLabel = 'TALENTS',
searchLabel = `FILTER AVAILABLE ${subjectLabel}`,
searchPlaceholder = `Search ${subjectLabel.toLocaleLowerCase()}…`,
emptySelectionLabel = `NO ${subjectLabel} ASSIGNED`
}: {
items: Item[];
selected?: string[];
excludedIds?: string[];
fieldName: string;
emptyLabel?: string;
subjectLabel?: string;
searchLabel?: string;
searchPlaceholder?: string;
emptySelectionLabel?: string;
} = $props();
let query = $state('');
const normalizedQuery = $derived(query.normalize('NFKC').trim().toLocaleLowerCase());
const available = $derived(items.filter((item) =>
!excludedIds.includes(item.id)
&& (!normalizedQuery || item.name.toLocaleLowerCase().includes(normalizedQuery))
));
const visible = $derived(available.slice(0, 30));
const selectedItems = $derived(selected.map((id) => items.find((item) => item.id === id)).filter((item): item is Item => Boolean(item)));
const toggle = (id: string) => {
selected = selected.includes(id) ? selected.filter((value) => value !== id) : [...selected, id];
};
</script>
<div class="assignment-picker">
{#each selected as id}<input type="hidden" name={fieldName} value={id}/>{/each}
<div class="assignment-selected">
<div class="assignment-summary"><span>ASSIGNED</span><b>{selected.length}</b></div>
<div class="assignment-chips">
{#each selectedItems as item}
<button type="button" class:pending-reference={item.pending} onclick={() => toggle(item.id)} aria-label={`Remove ${item.name}`}><span>{item.name}{#if item.pending}<small>PENDING</small>{/if}</span><b>×</b></button>
{:else}
<small>{emptySelectionLabel}</small>
{/each}
</div>
</div>
<label class="assignment-search"><span>{searchLabel}</span><input type="search" bind:value={query} placeholder={searchPlaceholder} autocomplete="off"/></label>
<div class="assignment-results-head"><span>{available.length} MATCHES</span>{#if available.length > visible.length}<b>SHOWING FIRST {visible.length}</b>{/if}</div>
<div class="assignment-results" role="list">
{#each visible as item}
<button type="button" class:selected={selected.includes(item.id)} class:pending-reference={item.pending} aria-pressed={selected.includes(item.id)} onclick={() => toggle(item.id)}>
<span>{item.name}{#if item.pending}<small>PENDING · {item.proposerName ?? 'CONTRIBUTOR'}</small>{/if}</span><b>{selected.includes(item.id) ? 'ASSIGNED' : 'ADD +'}</b>
</button>
{:else}
<p>{emptyLabel}</p>
{/each}
</div>
</div>