️ feat: create radio component

This commit is contained in:
Wahyu Kurniawan 2024-02-22 01:01:49 +07:00
parent ac260dd59c
commit cbc22fe16f
No known key found for this signature in database
GPG Key ID: 040A1549143A8E33
4 changed files with 193 additions and 0 deletions

View File

@ -0,0 +1,54 @@
import { VariantProps, tv } from 'tailwind-variants';
export const radioTheme = tv({
slots: {
root: ['flex', 'gap-3', 'flex-wrap'],
wrapper: ['flex', 'items-center', 'gap-2', 'group'],
label: ['text-sm', 'tracking-[-0.006em]', 'text-elements-high-em'],
radio: [
'w-5',
'h-5',
'rounded-full',
'border',
'group',
'border-border-interactive/10',
'shadow-button',
'group-hover:border-border-interactive/[0.14]',
'focus-ring',
// Checked
'data-[state=checked]:bg-controls-primary',
'data-[state=checked]:group-hover:bg-controls-primary-hovered',
],
indicator: [
'flex',
'items-center',
'justify-center',
'relative',
'w-full',
'h-full',
'after:content-[""]',
'after:block',
'after:w-2.5',
'after:h-2.5',
'after:rounded-full',
'after:bg-transparent',
'after:group-hover:bg-controls-disabled',
'after:group-focus-visible:bg-controls-disabled',
// Checked
'after:data-[state=checked]:bg-elements-on-primary',
'after:data-[state=checked]:group-hover:bg-elements-on-primary',
'after:data-[state=checked]:group-focus-visible:bg-elements-on-primary',
],
},
variants: {
orientation: {
vertical: { root: ['flex-col'] },
horizontal: { root: ['flex-row'] },
},
},
defaultVariants: {
orientation: 'vertical',
},
});
export type RadioTheme = VariantProps<typeof radioTheme>;

View File

@ -0,0 +1,63 @@
import React from 'react';
import {
Root as RadixRoot,
RadioGroupProps,
} from '@radix-ui/react-radio-group';
import { RadioTheme, radioTheme } from './Radio.theme';
import { RadioItem, RadioItemProps } from './RadioItem';
export interface RadioOption extends RadioItemProps {
/**
* The label of the radio option.
*/
label: string;
/**
* The value of the radio option.
*/
value: string;
}
export interface RadioProps extends RadioGroupProps, RadioTheme {
/**
* The options of the radio.
* @default []
* @example
* ```tsx
* const options = [
* {
* label: 'Label 1',
* value: '1',
* },
* {
* label: 'Label 2',
* value: '2',
* },
* {
* label: 'Label 3',
* value: '3',
* },
* ];
* ```
*/
options: RadioOption[];
}
/**
* The Radio component is used to select one option from a list of options.
*/
export const Radio = ({
className,
options,
orientation,
...props
}: RadioProps) => {
const { root } = radioTheme({ orientation });
return (
<RadixRoot {...props} className={root({ className })}>
{options.map((option) => (
<RadioItem key={option.value} {...option} />
))}
</RadixRoot>
);
};

View File

@ -0,0 +1,74 @@
import React, { ComponentPropsWithoutRef } from 'react';
import {
Item as RadixRadio,
Indicator as RadixIndicator,
RadioGroupItemProps,
RadioGroupIndicatorProps,
} from '@radix-ui/react-radio-group';
import { radioTheme } from './Radio.theme';
export interface RadioItemProps extends RadioGroupItemProps {
/**
* The wrapper props of the radio item.
* You can use this prop to customize the wrapper props.
*/
wrapperProps?: ComponentPropsWithoutRef<'div'>;
/**
* The label props of the radio item.
* You can use this prop to customize the label props.
*/
labelProps?: ComponentPropsWithoutRef<'label'>;
/**
* The indicator props of the radio item.
* You can use this prop to customize the indicator props.
*/
indicatorProps?: RadioGroupIndicatorProps;
/**
* The id of the radio item.
*/
id?: string;
/**
* The label of the radio item.
*/
label?: string;
}
/**
* The RadioItem component is used to render a radio item.
*/
export const RadioItem = ({
className,
wrapperProps,
labelProps,
indicatorProps,
label,
id,
...props
}: RadioItemProps) => {
const { wrapper, label: labelClass, radio, indicator } = radioTheme();
// Generate a unique id for the radio item from the label if the id is not provided
const kebabCaseLabel = label?.toLowerCase().replace(/\s+/g, '-');
const componentId = id ?? kebabCaseLabel;
return (
<div className={wrapper({ className: wrapperProps?.className })}>
<RadixRadio {...props} className={radio({ className })} id={componentId}>
<RadixIndicator
forceMount
{...indicatorProps}
className={indicator({ className: indicatorProps?.className })}
/>
</RadixRadio>
{label && (
<label
{...labelProps}
className={labelClass({ className: labelProps?.className })}
htmlFor={componentId}
>
{label}
</label>
)}
</div>
);
};

View File

@ -0,0 +1,2 @@
export * from './Radio';
export * from './RadioItem';