feat(RadioGroup): add card and table variants (#3178)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Romain Hamel
2025-03-31 16:34:31 +02:00
committed by GitHub
parent 615fcfd73b
commit 4d138ad671
7 changed files with 1541 additions and 623 deletions

View File

@@ -49,6 +49,10 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
* @defaultValue 'md'
*/
size?: RadioGroupVariants['size']
/**
* @defaultValue 'list'
*/
variant?: RadioGroupVariants['variant']
/**
* @defaultValue 'primary'
*/
@@ -58,6 +62,11 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
* @defaultValue 'vertical'
*/
orientation?: RadioGroupRootProps['orientation']
/**
* Position of the indicator.
* @defaultValue 'start'
*/
indicator?: RadioGroupVariants['indicator']
class?: any
ui?: Partial<typeof radioGroup.slots>
}
@@ -101,7 +110,9 @@ const ui = computed(() => radioGroup({
color: color.value,
disabled: disabled.value,
required: props.required,
orientation: props.orientation
orientation: props.orientation,
variant: props.variant,
indicator: props.indicator
}))
function normalizeItem(item: any) {
@@ -167,7 +178,7 @@ function onUpdate(value: any) {
{{ legend }}
</slot>
</legend>
<div v-for="item in normalizedItems" :key="item.value" :class="ui.item({ class: props.ui?.item })">
<component :is="variant === 'list' ? 'div' : Label" v-for="item in normalizedItems" :key="item.value" :class="ui.item({ class: props.ui?.item })">
<div :class="ui.container({ class: props.ui?.container })">
<RadioGroupItem
:id="item.id"
@@ -180,16 +191,18 @@ function onUpdate(value: any) {
</div>
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
<Label :class="ui.label({ class: props.ui?.label })" :for="item.id">
<slot name="label" :item="item" :model-value="(modelValue as RadioGroupValue)">{{ item.label }}</slot>
</Label>
<component :is="variant === 'list' ? Label : 'p'" :class="ui.label({ class: props.ui?.label })" :for="item.id">
<slot name="label" :item="item" :model-value="(modelValue as RadioGroupValue)">
{{ item.label }}
</slot>
</component>
<p v-if="item.description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
<slot name="description" :item="item" :model-value="(modelValue as RadioGroupValue)">
{{ item.description }}
</slot>
</p>
</div>
</div>
</component>
</fieldset>
</RadioGroupRoot>
</template>

View File

@@ -9,7 +9,7 @@ export default (options: Required<ModuleOptions>) => ({
base: 'rounded-full ring ring-inset ring-(--ui-border-accented) focus-visible:outline-2 focus-visible:outline-offset-2',
indicator: 'flex items-center justify-center size-full rounded-full after:bg-(--ui-bg) after:rounded-full',
container: 'flex items-center',
wrapper: 'ms-2',
wrapper: 'w-full',
label: 'block font-medium text-(--ui-text)',
description: 'text-(--ui-text-muted)'
},
@@ -24,6 +24,16 @@ export default (options: Required<ModuleOptions>) => ({
indicator: 'bg-(--ui-bg-inverted)'
}
},
variant: {
list: {
},
card: {
item: 'items-center border border-(--ui-border-muted) rounded-lg'
},
table: {
item: 'border border-(--ui-border-muted)'
}
},
orientation: {
horizontal: {
fieldset: 'flex-row',
@@ -33,6 +43,20 @@ export default (options: Required<ModuleOptions>) => ({
fieldset: 'flex-col'
}
},
indicator: {
start: {
item: 'flex-row',
base: 'me-2'
},
end: {
item: 'flex-row-reverse',
base: 'ms-2'
},
hidden: {
base: 'sr-only',
wrapper: 'text-center'
}
},
size: {
xs: {
fieldset: 'gap-0.5',
@@ -87,8 +111,62 @@ export default (options: Required<ModuleOptions>) => ({
}
}
},
compoundVariants: [
{ size: 'xs', variant: ['card', 'table'], class: { item: 'p-2.5' } },
{ size: 'sm', variant: ['card', 'table'], class: { item: 'p-3' } },
{ size: 'md', variant: ['card', 'table'], class: { item: 'p-3.5' } },
{ size: 'lg', variant: ['card', 'table'], class: { item: 'p-4' } },
{ size: 'xl', variant: ['card', 'table'], class: { item: 'p-4.5' } },
{
orientation: 'horizontal',
variant: 'table',
class: {
item: 'first-of-type:rounded-l-lg last-of-type:rounded-r-lg',
fieldset: 'gap-0 -space-x-px'
}
},
{
orientation: 'vertical',
variant: 'table',
class: {
item: 'first-of-type:rounded-t-lg last-of-type:rounded-b-lg',
fieldset: 'gap-0 -space-y-px'
}
},
...(options.theme.colors || []).map((color: string) => ({
color,
variant: 'card',
class: {
item: `has-data-[state=checked]:border-(--ui-${color})`
}
})),
{
color: 'neutral',
variant: 'card',
class: {
item: 'has-data-[state=checked]:border-(--ui-border-elevated)'
}
},
...(options.theme.colors || []).map((color: string) => ({
color,
variant: 'table',
class: {
item: `has-data-[state=checked]:bg-(--ui-${color})/10 has-data-[state=checked]:border-(--ui-${color})/50 has-data-[state=checked]:z-[1]`
}
})),
{
color: 'neutral',
variant: 'table',
class: {
item: 'has-data-[state=checked]:bg-(--ui-bg-elevated) has-data-[state=checked]:border-(--ui-border-inverted)/25 has-data-[state=checked]:z-[1]'
}
}
],
defaultVariants: {
size: 'md',
color: 'primary'
color: 'primary',
variant: 'list',
orientation: 'vertical',
indicator: 'start'
}
})