Files
sui/src/lib/RadioGroup.svelte
Elijah Duffy 104d99661c adjust styling
2025-07-04 03:17:43 -07:00

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>