mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
feat(RadioGroup): add card and table variants (#3178)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -133,30 +133,6 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Orientation
|
||||
|
||||
Use the `orientation` prop to change the orientation of the RadioGroup. Defaults to `vertical`.
|
||||
|
||||
::component-code
|
||||
---
|
||||
prettier: true
|
||||
ignore:
|
||||
- defaultValue
|
||||
- items
|
||||
external:
|
||||
- items
|
||||
externalTypes:
|
||||
- RadioGroupItem[]
|
||||
props:
|
||||
orientation: 'horizontal'
|
||||
defaultValue: 'System'
|
||||
items:
|
||||
- 'System'
|
||||
- 'Light'
|
||||
- 'Dark'
|
||||
---
|
||||
::
|
||||
|
||||
### Color
|
||||
|
||||
Use the `color` prop to change the color of the RadioGroup.
|
||||
@@ -181,6 +157,35 @@ props:
|
||||
---
|
||||
::
|
||||
|
||||
### Variant
|
||||
|
||||
Use the `variant` prop to change the variant of the RadioGroup.
|
||||
|
||||
::component-code
|
||||
---
|
||||
prettier: true
|
||||
ignore:
|
||||
- defaultValue
|
||||
- items
|
||||
external:
|
||||
- items
|
||||
props:
|
||||
color: 'primary'
|
||||
variant: 'table'
|
||||
defaultValue: 'pro'
|
||||
items:
|
||||
- label: 'Pro'
|
||||
value: 'pro'
|
||||
description: 'Tailored for indie hackers, freelancers and solo founders.'
|
||||
- label: 'Startup'
|
||||
value: 'startup'
|
||||
description: 'Best suited for small teams, startups and agencies.'
|
||||
- label: 'Enterprise'
|
||||
value: 'enterprise'
|
||||
description: 'Ideal for larger teams and organizations.'
|
||||
---
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the RadioGroup.
|
||||
@@ -197,6 +202,57 @@ externalTypes:
|
||||
- RadioGroupItem[]
|
||||
props:
|
||||
size: 'xl'
|
||||
variant: 'list'
|
||||
defaultValue: 'System'
|
||||
items:
|
||||
- 'System'
|
||||
- 'Light'
|
||||
- 'Dark'
|
||||
---
|
||||
::
|
||||
|
||||
### Orientation
|
||||
|
||||
Use the `orientation` prop to change the orientation of the RadioGroup. Defaults to `vertical`.
|
||||
|
||||
::component-code
|
||||
---
|
||||
prettier: true
|
||||
ignore:
|
||||
- defaultValue
|
||||
- items
|
||||
external:
|
||||
- items
|
||||
externalTypes:
|
||||
- RadioGroupItem[]
|
||||
props:
|
||||
orientation: 'horizontal'
|
||||
variant: 'list'
|
||||
defaultValue: 'System'
|
||||
items:
|
||||
- 'System'
|
||||
- 'Light'
|
||||
- 'Dark'
|
||||
---
|
||||
::
|
||||
|
||||
### Indicator
|
||||
|
||||
Use the `indicator` prop to change the position or hide the indicator. Defaults to `start`.
|
||||
|
||||
::component-code
|
||||
---
|
||||
prettier: true
|
||||
ignore:
|
||||
- defaultValue
|
||||
- items
|
||||
external:
|
||||
- items
|
||||
externalTypes:
|
||||
- RadioGroupItem[]
|
||||
props:
|
||||
indicator: 'end'
|
||||
variant: 'card'
|
||||
defaultValue: 'System'
|
||||
items:
|
||||
- 'System'
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import theme from '#build/ui/radio-group'
|
||||
|
||||
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
||||
const variants = Object.keys(theme.variants.variant)
|
||||
const variant = ref('list' as const)
|
||||
|
||||
const literalOptions = [
|
||||
'Option 1',
|
||||
@@ -23,27 +25,36 @@ const itemsWithDescription = [
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="flex flex-col gap-4 ms-[100px]">
|
||||
<URadioGroup :items="items" default-value="1" />
|
||||
<URadioGroup :items="items" color="neutral" default-value="1" />
|
||||
<URadioGroup :items="items" color="error" default-value="2" />
|
||||
<URadioGroup :items="literalOptions" />
|
||||
<URadioGroup :items="items" label="Disabled" disabled />
|
||||
<URadioGroup :items="items" orientation="horizontal" class="ms-[-91px]" />
|
||||
<USelect v-model="variant" :items="variants" />
|
||||
|
||||
<div class="flex flex-wrap gap-4 ms-[100px]">
|
||||
<URadioGroup :variant="variant" :items="items" default-value="1" />
|
||||
<URadioGroup :variant="variant" :items="items" color="neutral" default-value="1" />
|
||||
<URadioGroup :variant="variant" :items="items" color="error" default-value="2" />
|
||||
<URadioGroup :variant="variant" :items="literalOptions" />
|
||||
<URadioGroup :variant="variant" :items="items" disabled />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-4 ms-[100px]">
|
||||
<URadioGroup :variant="variant" :items="items" default-value="3" indicator="start" />
|
||||
<URadioGroup :variant="variant" :items="items" default-value="3" indicator="end" />
|
||||
<URadioGroup :variant="variant" :items="items" default-value="3" indicator="hidden" />
|
||||
</div>
|
||||
|
||||
<URadioGroup :variant="variant" :items="items" orientation="horizontal" class="ms-[95px]" />
|
||||
|
||||
<div class="flex items-center gap-4 ms-[34px]">
|
||||
<URadioGroup v-for="size in sizes" :key="size" :size="size" :items="items" />
|
||||
<URadioGroup v-for="size in sizes" :key="size" :size="size" :variant="variant" :items="items" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 ms-[74px]">
|
||||
<URadioGroup v-for="size in sizes" :key="size" :size="size" :items="itemsWithDescription" />
|
||||
<URadioGroup v-for="size in sizes" :key="size" :size="size" :variant="variant" :items="itemsWithDescription" />
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<URadioGroup :items="items" legend="Legend" />
|
||||
<URadioGroup :items="items" legend="Legend" required />
|
||||
<URadioGroup :items="items">
|
||||
<URadioGroup :variant="variant" :items="items" legend="Legend" />
|
||||
<URadioGroup :variant="variant" :items="items" legend="Legend" required />
|
||||
<URadioGroup :variant="variant" :items="items">
|
||||
<template #legend>
|
||||
<span class="italic font-bold">
|
||||
With slots
|
||||
@@ -56,6 +67,6 @@ const itemsWithDescription = [
|
||||
</template>
|
||||
</URadioGroup>
|
||||
</div>
|
||||
<URadioGroup :items="items" legend="Legend" orientation="horizontal" required />
|
||||
<URadioGroup :variant="variant" :items="items" legend="Legend" orientation="horizontal" required />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,6 +8,8 @@ import type { FormInputEvents } from '~/src/module'
|
||||
|
||||
describe('RadioGroup', () => {
|
||||
const sizes = Object.keys(theme.variants.size) as any
|
||||
const variants = Object.keys(theme.variants.variant) as any
|
||||
const indicators = Object.keys(theme.variants.indicator) as any
|
||||
|
||||
const items = [
|
||||
{ value: '1', label: 'Option 1' },
|
||||
@@ -28,8 +30,10 @@ describe('RadioGroup', () => {
|
||||
['with description', { props: { items: items.map((opt, count) => ({ ...opt, description: `Description ${count}` })) } }],
|
||||
['with required', { props: { ...props, legend: 'Legend', required: true } }],
|
||||
...sizes.map((size: string) => [`with size ${size}`, { props: { ...props, size } }]),
|
||||
['with color neutral', { props: { color: 'neutral', defaultValue: '1' } }],
|
||||
['with orientation', { props: { ...props, orientation: 'horizontal' } }],
|
||||
...variants.map((variant: string) => [`with primary variant ${variant}`, { props: { ...props, variant, defaultValue: '1' } }]),
|
||||
...variants.map((variant: string) => [`with neutral variant ${variant}`, { props: { ...props, variant, color: 'neutral', defaultValue: '1' } }]),
|
||||
...variants.map((variant: string) => [`with horizontal variant ${variant}`, { props: { ...props, variant, orientation: 'horizontal', defaultValue: '1' } }]),
|
||||
...indicators.map((indicator: string) => [`with indicator ${indicator}`, { props: { ...props, indicator } }]),
|
||||
['with ariaLabel', { props, attrs: { 'aria-label': 'Aria label' } }],
|
||||
['with as', { props: { ...props, as: 'section' } }],
|
||||
['with class', { props: { ...props, class: 'absolute' } }],
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user