83 lines
2.4 KiB
Svelte
83 lines
2.4 KiB
Svelte
<script lang="ts">
|
|
import { RadioGroup, type RadioGroupProps } from 'melt/builders';
|
|
import type { ClassValue } from 'svelte/elements';
|
|
import { scale } from 'svelte/transition';
|
|
import Label from './Label.svelte';
|
|
import { validate } from '@svelte-toolkit/validate';
|
|
import { getLabel, getValue, type Option } from './util';
|
|
|
|
interface Props extends RadioGroupProps {
|
|
options: Option[];
|
|
label?: string;
|
|
required?: boolean;
|
|
invalidMessage?: string;
|
|
class?: ClassValue | null | undefined;
|
|
}
|
|
|
|
let {
|
|
options,
|
|
label,
|
|
class: classValue,
|
|
required = false,
|
|
invalidMessage = 'Field is required',
|
|
...props
|
|
}: Props = $props();
|
|
let valid = $state(true);
|
|
|
|
const group = new RadioGroup(props);
|
|
console.log('group', group.root.id);
|
|
const isVert = $derived(group.orientation === 'vertical');
|
|
</script>
|
|
|
|
<div class={[classValue]} {...group.root}>
|
|
{#if label}
|
|
<Label {...group.label}>{label}</Label>
|
|
{/if}
|
|
<div class="flex {isVert ? 'flex-col gap-1' : 'flex-row justify-center gap-3'}">
|
|
{#each options as opt}
|
|
{@const item = group.getItem(getValue(opt))}
|
|
<div
|
|
class="ring-sui-accent-500 group -ml-1 flex items-center gap-3 rounded p-1 outline-hidden focus-visible:ring-3
|
|
data-disabled:cursor-not-allowed data-disabled:opacity-50"
|
|
{...item.attrs}
|
|
>
|
|
<div
|
|
class="group-aria-[checked]:border-sui-accent-500 dark:hover:bg-sui-text-800 relative size-6
|
|
rounded-full border-2 border-neutral-400 bg-neutral-100 shadow-sm
|
|
group-aria-[checke]:bg-transparent hover:bg-gray-100 data-[disabled=true]:bg-gray-400
|
|
dark:border-white dark:bg-transparent dark:sm:hover:bg-slate-800"
|
|
>
|
|
{#if item.checked}
|
|
<div
|
|
class="group-aria-[checked]:bg-sui-accent-500 absolute top-1/2 left-1/2 size-3 -translate-x-1/2 -translate-y-1/2
|
|
rounded-full"
|
|
aria-hidden="true"
|
|
transition:scale={{ duration: 75 }}
|
|
></div>
|
|
{/if}
|
|
</div>
|
|
|
|
<span
|
|
class={[
|
|
'cursor-default leading-none font-medium select-none',
|
|
typeof opt === 'string' && 'capitalize'
|
|
]}
|
|
>
|
|
{getLabel(opt)}
|
|
</span>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
<input
|
|
{...group.hiddenInput}
|
|
use:validate={{ required }}
|
|
onvalidate={(e) => (valid = e.detail.valid)}
|
|
/>
|
|
|
|
{#if required}
|
|
<div class={['opacity-0 transition-opacity', !valid && 'opacity-100']}>
|
|
<Label for={group.label.for} error>{invalidMessage}</Label>
|
|
</div>
|
|
{/if}
|
|
</div>
|