🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
226 lines
4.1 KiB
Svelte
226 lines
4.1 KiB
Svelte
<script lang="ts">
|
|
import type { HTMLSelectAttributes } from 'svelte/elements'
|
|
import ChevronDownIcon from '$icons/chevron-down.svg?raw'
|
|
|
|
interface Option {
|
|
value: string
|
|
label: string
|
|
}
|
|
|
|
interface Props extends Omit<HTMLSelectAttributes, 'size'> {
|
|
options: Option[]
|
|
value?: string
|
|
size?: 'small' | 'medium' | 'large'
|
|
variant?: 'default' | 'minimal'
|
|
fullWidth?: boolean
|
|
pill?: boolean
|
|
onchange?: (event: Event) => void
|
|
oninput?: (event: Event) => void
|
|
onfocus?: (event: FocusEvent) => void
|
|
onblur?: (event: FocusEvent) => void
|
|
}
|
|
|
|
let {
|
|
options,
|
|
value = $bindable(),
|
|
size = 'medium',
|
|
variant = 'default',
|
|
fullWidth = false,
|
|
pill = true,
|
|
onchange,
|
|
oninput,
|
|
onfocus,
|
|
onblur,
|
|
class: className = '',
|
|
...restProps
|
|
}: Props = $props()
|
|
</script>
|
|
|
|
<div class="select-wrapper">
|
|
<select
|
|
bind:value
|
|
class="select select-{size} select-{variant} {className}"
|
|
class:select-full-width={fullWidth}
|
|
class:select-pill={pill}
|
|
onchange={(e) => onchange?.(e)}
|
|
oninput={(e) => oninput?.(e)}
|
|
onfocus={(e) => onfocus?.(e)}
|
|
onblur={(e) => onblur?.(e)}
|
|
{...restProps}
|
|
>
|
|
{#each options as option}
|
|
<option value={option.value}>{option.label}</option>
|
|
{/each}
|
|
</select>
|
|
<div class="select-icon">
|
|
{@html ChevronDownIcon}
|
|
</div>
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.select-wrapper {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 100%;
|
|
}
|
|
|
|
.select {
|
|
box-sizing: border-box;
|
|
color: $gray-20;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: all 0.2s ease;
|
|
appearance: none;
|
|
padding-right: 36px;
|
|
width: 100%;
|
|
|
|
&:focus {
|
|
outline: none;
|
|
}
|
|
|
|
&:disabled {
|
|
color: $gray-60;
|
|
cursor: not-allowed;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
// Default variant
|
|
&.select-default {
|
|
border: 1px solid $gray-80;
|
|
background: white;
|
|
font-weight: 500;
|
|
|
|
&:focus {
|
|
border-color: $blue-50;
|
|
box-shadow: 0 0 0 3px rgba(20, 130, 193, 0.1);
|
|
}
|
|
|
|
&:disabled {
|
|
background: $gray-90;
|
|
}
|
|
}
|
|
|
|
// Minimal variant
|
|
&.select-minimal {
|
|
border: none;
|
|
background: $gray-90;
|
|
font-weight: 500;
|
|
|
|
&:hover {
|
|
background: $gray-80;
|
|
}
|
|
|
|
&:focus {
|
|
background: $gray-80;
|
|
}
|
|
|
|
&:disabled {
|
|
background: transparent;
|
|
}
|
|
}
|
|
|
|
// Size variants for default variant (accounting for border)
|
|
&.select-default {
|
|
&.select-small {
|
|
padding: calc($unit - 1px) calc($unit * 1.5);
|
|
font-size: 13px;
|
|
min-height: 28px;
|
|
min-width: 120px;
|
|
}
|
|
|
|
&.select-medium {
|
|
padding: calc($unit - 1px) $unit-2x;
|
|
font-size: 14px;
|
|
min-height: 36px;
|
|
min-width: 160px;
|
|
}
|
|
|
|
&.select-large {
|
|
padding: calc($unit * 1.5 - 1px) $unit-3x;
|
|
font-size: 15px;
|
|
min-height: 44px;
|
|
min-width: 180px;
|
|
}
|
|
}
|
|
|
|
// Size variants for minimal variant (no border, card-sized border radius)
|
|
&.select-minimal {
|
|
&.select-small {
|
|
padding: $unit calc($unit * 1.5);
|
|
font-size: 13px;
|
|
min-height: 28px;
|
|
min-width: 120px;
|
|
border-radius: $corner-radius;
|
|
}
|
|
|
|
&.select-medium {
|
|
padding: $unit $unit-2x;
|
|
font-size: 14px;
|
|
min-height: 36px;
|
|
min-width: 160px;
|
|
border-radius: $corner-radius;
|
|
}
|
|
|
|
&.select-large {
|
|
padding: calc($unit * 1.5) $unit-3x;
|
|
font-size: 15px;
|
|
min-height: 44px;
|
|
min-width: 180px;
|
|
border-radius: $card-corner-radius;
|
|
}
|
|
}
|
|
|
|
// Shape variants for default variant only (minimal already has card radius)
|
|
&.select-default.select-pill {
|
|
&.select-small {
|
|
border-radius: 20px;
|
|
}
|
|
&.select-medium {
|
|
border-radius: 24px;
|
|
}
|
|
&.select-large {
|
|
border-radius: 28px;
|
|
}
|
|
}
|
|
|
|
&.select-default:not(.select-pill) {
|
|
&.select-small {
|
|
border-radius: 6px;
|
|
}
|
|
&.select-medium {
|
|
border-radius: 8px;
|
|
}
|
|
&.select-large {
|
|
border-radius: 10px;
|
|
}
|
|
}
|
|
|
|
// Width variants
|
|
&.select-full-width {
|
|
width: 100%;
|
|
min-width: auto;
|
|
}
|
|
}
|
|
|
|
.select-icon {
|
|
position: absolute;
|
|
right: 12px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
pointer-events: none;
|
|
color: $gray-60;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
:global(svg) {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
}
|
|
|
|
// Full width handling for wrapper
|
|
.select-wrapper:has(.select-full-width) {
|
|
width: 100%;
|
|
}
|
|
</style>
|