⚡️ feat: create radio component
This commit is contained in:
parent
ac260dd59c
commit
cbc22fe16f
54
packages/frontend/src/components/shared/Radio/Radio.theme.ts
Normal file
54
packages/frontend/src/components/shared/Radio/Radio.theme.ts
Normal 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>;
|
63
packages/frontend/src/components/shared/Radio/Radio.tsx
Normal file
63
packages/frontend/src/components/shared/Radio/Radio.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
74
packages/frontend/src/components/shared/Radio/RadioItem.tsx
Normal file
74
packages/frontend/src/components/shared/Radio/RadioItem.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
2
packages/frontend/src/components/shared/Radio/index.ts
Normal file
2
packages/frontend/src/components/shared/Radio/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './Radio';
|
||||||
|
export * from './RadioItem';
|
Loading…
Reference in New Issue
Block a user