70 lines
2.0 KiB
Svelte
70 lines
2.0 KiB
Svelte
<script lang="ts">
|
|
import { ChevronDown } from 'lucide-svelte';
|
|
|
|
interface Option {
|
|
value: string;
|
|
label: string;
|
|
}
|
|
|
|
interface Props {
|
|
value: string;
|
|
options: Option[];
|
|
class?: string;
|
|
onchange?: (value: string) => void;
|
|
}
|
|
|
|
let { value = $bindable(), options, class: cls = '', onchange }: Props = $props();
|
|
|
|
let open = $state(false);
|
|
let container: HTMLDivElement;
|
|
let button: HTMLButtonElement;
|
|
let openUp = $state(false);
|
|
|
|
function calcDirection() {
|
|
if (!button) return;
|
|
const rect = button.getBoundingClientRect();
|
|
const spaceBelow = window.innerHeight - rect.bottom;
|
|
openUp = spaceBelow < 260; // less than max-h-60 (240px) + margin
|
|
}
|
|
|
|
const selected = $derived(options.find(o => o.value === value) ?? options[0]);
|
|
|
|
function select(v: string) {
|
|
value = v;
|
|
open = false;
|
|
onchange?.(v);
|
|
}
|
|
|
|
function onKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') open = false;
|
|
}
|
|
</script>
|
|
|
|
<svelte:window onclick={(e) => { if (container && !container.contains(e.target as Node)) open = false; }} onkeydown={onKeydown} />
|
|
|
|
<div bind:this={container} class="relative {cls}">
|
|
<button
|
|
bind:this={button}
|
|
type="button"
|
|
class="flex w-full items-center justify-between gap-2 rounded border border-border bg-white/10 px-3 py-2 text-sm text-white hover:bg-white/15 focus:border-accent focus:outline-none"
|
|
onclick={() => { calcDirection(); open = !open; }}
|
|
>
|
|
<span class="truncate">{selected?.label ?? ''}</span>
|
|
<ChevronDown size={14} class="flex-shrink-0 text-muted transition-transform {open ? 'rotate-180' : ''}" />
|
|
</button>
|
|
|
|
{#if open}
|
|
<div class="absolute left-0 z-50 min-w-full rounded-lg border border-border bg-surface shadow-xl py-1 max-h-60 overflow-y-auto {openUp ? 'bottom-full mb-1' : 'top-full mt-1'}">
|
|
{#each options as opt}
|
|
<button
|
|
type="button"
|
|
class="w-full px-3 py-2 text-left text-sm transition-colors {opt.value === value ? 'bg-accent/20 text-accent' : 'text-white hover:bg-white/10'}"
|
|
onclick={() => select(opt.value)}
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|