Monorepo for Tangled
Something went wrong. Try again.
Svelte
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160<script module lang="ts"> import type { ResolvedPathname } from "$app/types";
export interface Label { name: string; color: string; href?: ResolvedPathname; }
const COLLAPSED_LABEL_COUNT = 10;</script>
<script lang="ts"> import Pencil from "$icon/pencil"; import TagPlus from "$icon/tag-plus"; import Plus from "$icon/plus"; import Button from "$lib/components/ui/Button.svelte"; import ColorBall from "$lib/components/ui/ColorBall.svelte"; import Link from "$lib/components/ui/Link.svelte"; import Combobox from "$lib/components/ui/Combobox.svelte"; import type { SelectOption } from "$lib/components/ui/selectField";
interface Props { direction?: "horizontal" | "vertical"; labels?: Label[]; canEdit?: boolean; allLabels?: Label[]; onLabelsChange?: (names: string[]) => void; }
let { direction = "horizontal", labels = [], canEdit = false, allLabels = [], onLabelsChange }: Props = $props();
let expanded = $state(false);
const pickedLabels = $derived(labels.map((label) => label.name));
const hiddenCount = $derived(Math.max(0, labels.length - COLLAPSED_LABEL_COUNT));
const shownLabels = $derived( expanded || hiddenCount === 0 ? labels : labels.slice(0, COLLAPSED_LABEL_COUNT) );
const labelOptions = $derived<SelectOption[]>( allLabels.map((label) => ({ value: label.name, label: label.name })) );
const labelColors = $derived(new Map(allLabels.map((label) => [label.name, label.color])));
const baseId = $props.id(); const labelsHeadingId = `${baseId}-labels`;</script>
{#snippet sectionHeading(id: string, title: string)} <h3 {id} class="pr-xs typography-paragraph-small font-medium text-foreground-muted"> {title} </h3>{/snippet}
{#snippet labelBall(label: Label)} <ColorBall color={label.color} size="small" /> <span class="typography-paragraph-regular">{label.name}</span>{/snippet}
{#if direction === "vertical"} <aside class="flex flex-col gap-xl typography-paragraph-small text-foreground-subtle"> <div>TODO: label panel</div> </aside>{:else} <div class="flex w-full flex-col items-start gap-md"> <section class="flex w-full flex-col items-start gap-xs"> <div class="flex w-full items-center justify-between"> {@render sectionHeading(labelsHeadingId, "Labels")} </div> <ul aria-labelledby={labelsHeadingId} class="group flex w-full flex-wrap items-center gap-xs" > {#each shownLabels as label (label.name)} <li class="flex"> {#if label.href} <Link href={label.href} variant="inherit" class="flex items-center gap-xs rounded-sm border border-border-default bg-background-default px-xs py-[3px] hover:bg-background-subtle hover:no-underline" > {@render labelBall(label)} </Link> {:else} <span class="flex items-center gap-xs rounded-sm border border-border-default bg-background-default px-xs py-[3px]" > {@render labelBall(label)} </span> {/if} </li> {/each} {#if hiddenCount > 0 && !expanded} <li class="flex"> <Button variant="ghost" size="small" onclick={() => (expanded = true)} icon={TagPlus} > Display all {labels.length} labels </Button> </li> {/if} {#if canEdit} <Combobox multiple freezePosition align="right" options={labelOptions} values={pickedLabels} onToggle={(name) => onLabelsChange?.( pickedLabels.includes(name) ? pickedLabels.filter((picked) => picked !== name) : [...pickedLabels, name] )} label="Labels" placeholder="Select labels" searchPlaceholder="Filter labels…" emptyLabel="No labels" class="w-auto" > {#snippet trigger({ props })} <div class="opacity-0 transition group-hover:opacity-100 has-focus-visible:opacity-100 has-aria-expanded:opacity-100" > <Button {...props} variant="ghost" size="small" icon={TagPlus} aria-label="Edit labels" class="aria-expanded:bg-background-inset" /> </div> {/snippet} {#snippet option(choice)} <ColorBall color={labelColors.get(choice.value) ?? ""} size="small" /> <span class="min-w-0 flex-1 truncate" >{choice.label ?? choice.value}</span > {/snippet} </Combobox> {/if} </ul> </section> </div>{/if}