radio group: custom class support, improved options prop

This commit is contained in:
Elijah Duffy
2025-07-03 14:11:09 -07:00
parent 37c3594d1a
commit 07b7102a78
2 changed files with 74 additions and 15 deletions

View File

@@ -1,26 +1,67 @@
<script lang="ts" module>
export type RadioGroupOption =
| {
value: string;
label?: string;
}
| string;
/** getLabel returns the option label if it exists*/
const getLabel = (option: RadioGroupOption): string => {
if (typeof option === 'string') {
return option;
} else {
return option.label ?? option.value;
}
};
/** getValue returns the option value */
const getValue = (option: RadioGroupOption): string => {
if (typeof option === 'string') {
return option;
} else {
return option.value;
}
};
</script>
<script lang="ts"> <script lang="ts">
import { RadioGroup } from 'melt/builders'; import { RadioGroup, type RadioGroupProps } from 'melt/builders';
import type { RadioGroupProps } from 'melt/builders'; import type { ClassValue } from 'svelte/elements';
import { scale } from 'svelte/transition'; import { scale } from 'svelte/transition';
import Label from './Label.svelte';
import { validate } from '@svelte-toolkit/validate';
interface Props extends RadioGroupProps {
options: RadioGroupOption[];
label?: string;
required?: boolean;
invalidMessage?: string;
class?: ClassValue | null | undefined;
}
let { let {
items, options,
label, label,
class: classValue,
required = false,
invalidMessage = 'Field is required',
...props ...props
}: RadioGroupProps & { }: Props = $props();
items: string[]; let valid = $state(true);
label: string;
} = $props();
const group = new RadioGroup(props); const group = new RadioGroup(props);
console.log('group', group.root.id);
const isVert = $derived(group.orientation === 'vertical'); const isVert = $derived(group.orientation === 'vertical');
</script> </script>
<div class="flex w-fit flex-col gap-2" {...group.root}> <div class={['flex w-fit flex-col', classValue]} {...group.root}>
<label {...group.label} class="font-medium">{label}</label> {#if label}
<Label {...group.label}>{label}</Label>
{/if}
<div class="flex {isVert ? 'flex-col gap-1' : 'flex-row gap-3'}"> <div class="flex {isVert ? 'flex-col gap-1' : 'flex-row gap-3'}">
{#each items as i} {#each options as opt}
{@const item = group.getItem(i)} {@const item = group.getItem(getValue(opt))}
<div <div
class="ring-accent-500 group -ml-1 flex items-center gap-3 rounded p-1 outline-hidden focus-visible:ring-3 class="ring-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" data-disabled:cursor-not-allowed data-disabled:opacity-50"
@@ -42,11 +83,24 @@
{/if} {/if}
</div> </div>
<span class="cursor-default leading-none font-semibold capitalize select-none"> <span
{i} class={[
'cursor-default leading-none font-medium select-none',
typeof opt === 'string' && 'capitalize'
]}
>
{getLabel(opt)}
</span> </span>
</div> </div>
{/each} {/each}
</div> </div>
<input {...group.hiddenInput} /> <input
{...group.hiddenInput}
use:validate={{ required }}
onvalidate={(e) => (valid = e.detail.valid)}
/>
<div class={['opacity-0 transition-opacity', !valid && 'opacity-100']}>
<Label for={group.label.for} error>{invalidMessage}</Label>
</div>
</div> </div>

View File

@@ -106,7 +106,12 @@
<RadioGroup <RadioGroup
name="example-radio-group" name="example-radio-group"
label="Choose an option" label="Choose an option"
items={['male', 'female', 'other', 'prefer not to say']} options={[
'male',
'female',
'other',
{ value: 'prefer_not_to_say', label: 'Prefer not to say' }
]}
/> />
</div> </div>