mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-02-05 22:53:52 +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
|
### Color
|
||||||
|
|
||||||
Use the `color` prop to change the color of the RadioGroup.
|
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
|
### Size
|
||||||
|
|
||||||
Use the `size` prop to change the size of the RadioGroup.
|
Use the `size` prop to change the size of the RadioGroup.
|
||||||
@@ -197,6 +202,57 @@ externalTypes:
|
|||||||
- RadioGroupItem[]
|
- RadioGroupItem[]
|
||||||
props:
|
props:
|
||||||
size: 'xl'
|
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'
|
defaultValue: 'System'
|
||||||
items:
|
items:
|
||||||
- 'System'
|
- 'System'
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import theme from '#build/ui/radio-group'
|
import theme from '#build/ui/radio-group'
|
||||||
|
|
||||||
const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.variants.size>
|
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 = [
|
const literalOptions = [
|
||||||
'Option 1',
|
'Option 1',
|
||||||
@@ -23,27 +25,36 @@ const itemsWithDescription = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center gap-4">
|
<div class="flex flex-col items-center gap-4">
|
||||||
<div class="flex flex-col gap-4 ms-[100px]">
|
<USelect v-model="variant" :items="variants" />
|
||||||
<URadioGroup :items="items" default-value="1" />
|
|
||||||
<URadioGroup :items="items" color="neutral" default-value="1" />
|
<div class="flex flex-wrap gap-4 ms-[100px]">
|
||||||
<URadioGroup :items="items" color="error" default-value="2" />
|
<URadioGroup :variant="variant" :items="items" default-value="1" />
|
||||||
<URadioGroup :items="literalOptions" />
|
<URadioGroup :variant="variant" :items="items" color="neutral" default-value="1" />
|
||||||
<URadioGroup :items="items" label="Disabled" disabled />
|
<URadioGroup :variant="variant" :items="items" color="error" default-value="2" />
|
||||||
<URadioGroup :items="items" orientation="horizontal" class="ms-[-91px]" />
|
<URadioGroup :variant="variant" :items="literalOptions" />
|
||||||
|
<URadioGroup :variant="variant" :items="items" disabled />
|
||||||
</div>
|
</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]">
|
<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>
|
||||||
|
|
||||||
<div class="flex items-center gap-4 ms-[74px]">
|
<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>
|
||||||
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<URadioGroup :items="items" legend="Legend" />
|
<URadioGroup :variant="variant" :items="items" legend="Legend" />
|
||||||
<URadioGroup :items="items" legend="Legend" required />
|
<URadioGroup :variant="variant" :items="items" legend="Legend" required />
|
||||||
<URadioGroup :items="items">
|
<URadioGroup :variant="variant" :items="items">
|
||||||
<template #legend>
|
<template #legend>
|
||||||
<span class="italic font-bold">
|
<span class="italic font-bold">
|
||||||
With slots
|
With slots
|
||||||
@@ -56,6 +67,6 @@ const itemsWithDescription = [
|
|||||||
</template>
|
</template>
|
||||||
</URadioGroup>
|
</URadioGroup>
|
||||||
</div>
|
</div>
|
||||||
<URadioGroup :items="items" legend="Legend" orientation="horizontal" required />
|
<URadioGroup :variant="variant" :items="items" legend="Legend" orientation="horizontal" required />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
|
|||||||
* @defaultValue 'md'
|
* @defaultValue 'md'
|
||||||
*/
|
*/
|
||||||
size?: RadioGroupVariants['size']
|
size?: RadioGroupVariants['size']
|
||||||
|
/**
|
||||||
|
* @defaultValue 'list'
|
||||||
|
*/
|
||||||
|
variant?: RadioGroupVariants['variant']
|
||||||
/**
|
/**
|
||||||
* @defaultValue 'primary'
|
* @defaultValue 'primary'
|
||||||
*/
|
*/
|
||||||
@@ -58,6 +62,11 @@ export interface RadioGroupProps<T extends RadioGroupItem = RadioGroupItem> exte
|
|||||||
* @defaultValue 'vertical'
|
* @defaultValue 'vertical'
|
||||||
*/
|
*/
|
||||||
orientation?: RadioGroupRootProps['orientation']
|
orientation?: RadioGroupRootProps['orientation']
|
||||||
|
/**
|
||||||
|
* Position of the indicator.
|
||||||
|
* @defaultValue 'start'
|
||||||
|
*/
|
||||||
|
indicator?: RadioGroupVariants['indicator']
|
||||||
class?: any
|
class?: any
|
||||||
ui?: Partial<typeof radioGroup.slots>
|
ui?: Partial<typeof radioGroup.slots>
|
||||||
}
|
}
|
||||||
@@ -101,7 +110,9 @@ const ui = computed(() => radioGroup({
|
|||||||
color: color.value,
|
color: color.value,
|
||||||
disabled: disabled.value,
|
disabled: disabled.value,
|
||||||
required: props.required,
|
required: props.required,
|
||||||
orientation: props.orientation
|
orientation: props.orientation,
|
||||||
|
variant: props.variant,
|
||||||
|
indicator: props.indicator
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function normalizeItem(item: any) {
|
function normalizeItem(item: any) {
|
||||||
@@ -167,7 +178,7 @@ function onUpdate(value: any) {
|
|||||||
{{ legend }}
|
{{ legend }}
|
||||||
</slot>
|
</slot>
|
||||||
</legend>
|
</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 })">
|
<div :class="ui.container({ class: props.ui?.container })">
|
||||||
<RadioGroupItem
|
<RadioGroupItem
|
||||||
:id="item.id"
|
:id="item.id"
|
||||||
@@ -180,16 +191,18 @@ function onUpdate(value: any) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
<div :class="ui.wrapper({ class: props.ui?.wrapper })">
|
||||||
<Label :class="ui.label({ class: props.ui?.label })" :for="item.id">
|
<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>
|
<slot name="label" :item="item" :model-value="(modelValue as RadioGroupValue)">
|
||||||
</Label>
|
{{ item.label }}
|
||||||
|
</slot>
|
||||||
|
</component>
|
||||||
<p v-if="item.description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
|
<p v-if="item.description || !!slots.description" :class="ui.description({ class: props.ui?.description })">
|
||||||
<slot name="description" :item="item" :model-value="(modelValue as RadioGroupValue)">
|
<slot name="description" :item="item" :model-value="(modelValue as RadioGroupValue)">
|
||||||
{{ item.description }}
|
{{ item.description }}
|
||||||
</slot>
|
</slot>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</component>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</RadioGroupRoot>
|
</RadioGroupRoot>
|
||||||
</template>
|
</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',
|
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',
|
indicator: 'flex items-center justify-center size-full rounded-full after:bg-(--ui-bg) after:rounded-full',
|
||||||
container: 'flex items-center',
|
container: 'flex items-center',
|
||||||
wrapper: 'ms-2',
|
wrapper: 'w-full',
|
||||||
label: 'block font-medium text-(--ui-text)',
|
label: 'block font-medium text-(--ui-text)',
|
||||||
description: 'text-(--ui-text-muted)'
|
description: 'text-(--ui-text-muted)'
|
||||||
},
|
},
|
||||||
@@ -24,6 +24,16 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
indicator: 'bg-(--ui-bg-inverted)'
|
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: {
|
orientation: {
|
||||||
horizontal: {
|
horizontal: {
|
||||||
fieldset: 'flex-row',
|
fieldset: 'flex-row',
|
||||||
@@ -33,6 +43,20 @@ export default (options: Required<ModuleOptions>) => ({
|
|||||||
fieldset: 'flex-col'
|
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: {
|
size: {
|
||||||
xs: {
|
xs: {
|
||||||
fieldset: 'gap-0.5',
|
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: {
|
defaultVariants: {
|
||||||
size: 'md',
|
size: 'md',
|
||||||
color: 'primary'
|
color: 'primary',
|
||||||
|
variant: 'list',
|
||||||
|
orientation: 'vertical',
|
||||||
|
indicator: 'start'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import type { FormInputEvents } from '~/src/module'
|
|||||||
|
|
||||||
describe('RadioGroup', () => {
|
describe('RadioGroup', () => {
|
||||||
const sizes = Object.keys(theme.variants.size) as any
|
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 = [
|
const items = [
|
||||||
{ value: '1', label: 'Option 1' },
|
{ value: '1', label: 'Option 1' },
|
||||||
@@ -28,8 +30,10 @@ describe('RadioGroup', () => {
|
|||||||
['with description', { props: { items: items.map((opt, count) => ({ ...opt, description: `Description ${count}` })) } }],
|
['with description', { props: { items: items.map((opt, count) => ({ ...opt, description: `Description ${count}` })) } }],
|
||||||
['with required', { props: { ...props, legend: 'Legend', required: true } }],
|
['with required', { props: { ...props, legend: 'Legend', required: true } }],
|
||||||
...sizes.map((size: string) => [`with size ${size}`, { props: { ...props, size } }]),
|
...sizes.map((size: string) => [`with size ${size}`, { props: { ...props, size } }]),
|
||||||
['with color neutral', { props: { color: 'neutral', defaultValue: '1' } }],
|
...variants.map((variant: string) => [`with primary variant ${variant}`, { props: { ...props, variant, defaultValue: '1' } }]),
|
||||||
['with orientation', { props: { ...props, orientation: 'horizontal' } }],
|
...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 ariaLabel', { props, attrs: { 'aria-label': 'Aria label' } }],
|
||||||
['with as', { props: { ...props, as: 'section' } }],
|
['with as', { props: { ...props, as: 'section' } }],
|
||||||
['with class', { props: { ...props, class: 'absolute' } }],
|
['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