feat: 공통 컴포넌트 추가 생성
This commit is contained in:
102
web-app/app/shared/components/radio/Radio.tsx
Normal file
102
web-app/app/shared/components/radio/Radio.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { type ReactNode, useId } from 'react';
|
||||
|
||||
import { tv } from 'tailwind-variants';
|
||||
|
||||
export interface RadioProps {
|
||||
name: string;
|
||||
value: string;
|
||||
checked?: boolean;
|
||||
defaultChecked?: boolean;
|
||||
onChange?: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const radioStyles = tv({
|
||||
base: [
|
||||
'shrink-0 w-4 h-4 min-w-4 min-h-4 box-border rounded-full',
|
||||
'border-2 border-dabeeo-gray-be bg-white',
|
||||
'flex items-center justify-center',
|
||||
'transition-all duration-200',
|
||||
'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-2 peer-focus-visible:ring-primary',
|
||||
],
|
||||
variants: {
|
||||
isSelected: {
|
||||
true: 'border-primary bg-primary',
|
||||
},
|
||||
isDisabled: {
|
||||
true: 'border-dabeeo-gray-be bg-dabeeo-gray-eb',
|
||||
},
|
||||
isReadOnly: {
|
||||
true: '',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
isSelected: true,
|
||||
isDisabled: true,
|
||||
className: 'border-dabeeo-gray-be bg-dabeeo-gray-eb',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const innerCircleStyles = tv({
|
||||
base: 'w-2 h-2 rounded-full transition-all duration-200',
|
||||
variants: {
|
||||
isSelected: {
|
||||
true: 'bg-white',
|
||||
false: 'bg-transparent',
|
||||
},
|
||||
isDisabled: {
|
||||
true: 'bg-dabeeo-gray-eb',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
isSelected: true,
|
||||
isDisabled: true,
|
||||
className: 'bg-dabeeo-gray-eb',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const labelStyles = tv({
|
||||
base: 'text-dabeeo-black-22 text-sm font-medium inline-flex items-center',
|
||||
variants: {
|
||||
isDisabled: {
|
||||
true: 'text-dabeeo-gray-be',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const Radio = (props: RadioProps) => {
|
||||
const { name, value, checked, defaultChecked, onChange, disabled, readOnly, children, className } = props;
|
||||
const id = useId();
|
||||
const isInteractive = !disabled && !readOnly;
|
||||
|
||||
return (
|
||||
<label
|
||||
htmlFor={id}
|
||||
className={`inline-flex h-9 items-center gap-2.5 ${isInteractive ? 'cursor-pointer' : 'cursor-not-allowed'} ${className ?? ''}`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
checked={checked}
|
||||
defaultChecked={defaultChecked}
|
||||
onChange={(e) => isInteractive && onChange?.(e.target.value)}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<span className={radioStyles({ isSelected: checked, isDisabled: disabled, isReadOnly: readOnly })}>
|
||||
<span className={innerCircleStyles({ isSelected: checked, isDisabled: disabled })} />
|
||||
</span>
|
||||
{children && <span className={labelStyles({ isDisabled: disabled })}>{children}</span>}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user