jedmund-svelte/src/lib/components/admin/DropdownSelectField.svelte
Justin Edmund 314885b704 feat: add utility components and helpers
Add DropdownSelectField and StatusPicker components for form inputs. Add time utility functions.
2025-11-03 23:03:50 -08:00

159 lines
3 KiB
Svelte

<script lang="ts">
import { clickOutside } from '$lib/actions/clickOutside'
import DropdownItem from './DropdownItem.svelte'
import DropdownMenuContainer from './DropdownMenuContainer.svelte'
import FormField from './FormField.svelte'
interface Option {
value: string
label: string
description?: string
}
interface Props {
label: string
value?: string
options: Option[]
required?: boolean
helpText?: string
error?: string
disabled?: boolean
placeholder?: string
}
let {
label,
value = $bindable(),
options,
required = false,
helpText,
error,
disabled = false,
placeholder = 'Select an option'
}: Props = $props()
let isOpen = $state(false)
const selectedOption = $derived(options.find((opt) => opt.value === value))
function handleSelect(optionValue: string) {
value = optionValue
isOpen = false
}
function handleClickOutside() {
isOpen = false
}
</script>
<FormField {label} {required} {helpText} {error}>
{#snippet children()}
<div
class="dropdown-select"
use:clickOutside={{ enabled: isOpen }}
onclickoutside={handleClickOutside}
>
<button
type="button"
class="dropdown-select-trigger"
class:open={isOpen}
class:has-value={!!value}
class:disabled
{disabled}
onclick={() => !disabled && (isOpen = !isOpen)}
>
<span class="dropdown-select-value">
{selectedOption?.label || placeholder}
</span>
<svg
class="chevron"
class:rotate={isOpen}
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
>
<path
d="M3 4.5L6 7.5L9 4.5"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
{#if isOpen}
<DropdownMenuContainer>
{#each options as option}
<DropdownItem
label={option.label}
description={option.description}
onclick={() => handleSelect(option.value)}
/>
{/each}
</DropdownMenuContainer>
{/if}
</div>
{/snippet}
</FormField>
<style lang="scss">
.dropdown-select {
position: relative;
width: 100%;
}
.dropdown-select-trigger {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: $unit-2x $unit-3x;
background: $input-bg;
border: 1px solid $input-border;
border-radius: $corner-radius-full;
font-size: 0.9375rem;
color: $input-text;
cursor: pointer;
transition: all 0.2s ease;
&:hover:not(.disabled) {
background: $input-bg-hover;
border-color: $gray-70;
}
&.open,
&:focus {
background: $input-bg-focus;
border-color: $input-border-focus;
outline: none;
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;
}
&:not(.has-value) {
.dropdown-select-value {
color: $gray-40;
}
}
}
.dropdown-select-value {
flex: 1;
text-align: left;
}
.chevron {
flex-shrink: 0;
color: $gray-40;
transition: transform 0.2s ease;
&.rotate {
transform: rotate(180deg);
}
}
</style>