Skip to content

Commit 405f15b

Browse files
committed
feat: add component radio
1 parent e879744 commit 405f15b

File tree

17 files changed

+491
-1
lines changed

17 files changed

+491
-1
lines changed

packages/ui/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"main": "./dist/index.js",
1212
"module": "./dist/index.js",
1313
"types": "./dist/index.d.ts",
14-
"files": ["dist"],
14+
"files": [
15+
"dist"
16+
],
1517
"scripts": {
1618
"build": "pnpm run pgk:prod && pnpm run build:components && pnpm run registry",
1719
"build:components": "tsdown",
@@ -37,6 +39,7 @@
3739
"@radix-ui/react-menu": "^2.1.15",
3840
"@radix-ui/react-menubar": "^1.1.15",
3941
"@radix-ui/react-popover": "1.1.14",
42+
"@radix-ui/react-radio-group": "^1.3.7",
4043
"@radix-ui/react-scroll-area": "1.2.9",
4144
"@radix-ui/react-separator": "1.1.7",
4245
"@radix-ui/react-slot": "1.2.3",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import RadioLabel from '../label/Label';
2+
3+
import RadioGroupItem from './RadioGroupItem';
4+
import RadioIndicator from './RadioIndicator';
5+
import RadioRoot from './RadioRoot';
6+
import type { RadioProps } from './types';
7+
8+
const Radio = (props: RadioProps) => {
9+
const { className, classNames, color, id, label, size, value, ...rest } = props;
10+
11+
return (
12+
<RadioRoot
13+
className={className || classNames?.root}
14+
size={size}
15+
>
16+
<RadioGroupItem
17+
color={color}
18+
value={value}
19+
id={id || value}
20+
{...rest}
21+
size={size}
22+
>
23+
<RadioIndicator
24+
className={classNames?.indicator}
25+
color={color}
26+
/>
27+
</RadioGroupItem>
28+
29+
{label && (
30+
<RadioLabel
31+
className={classNames?.label}
32+
htmlFor={id || value}
33+
size={size}
34+
>
35+
{label}
36+
</RadioLabel>
37+
)}
38+
</RadioRoot>
39+
);
40+
};
41+
42+
export default Radio;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Root } from '@radix-ui/react-radio-group';
2+
3+
import { cn } from '@/lib/utils';
4+
5+
import Radio from './Radio';
6+
import { radioVariants } from './radio-variants';
7+
import type { RadioGroupProps } from './types';
8+
9+
const RadioGroup = (props: RadioGroupProps) => {
10+
const { className, classNames, color, items, orientation, size, ...rest } = props;
11+
12+
const { group } = radioVariants({ orientation, size });
13+
14+
const mergedCls = cn(group(), className || classNames?.group);
15+
16+
return (
17+
<Root
18+
className={mergedCls}
19+
{...rest}
20+
>
21+
{items.map(item => (
22+
<Radio
23+
classNames={classNames}
24+
color={color}
25+
key={item.value}
26+
size={size}
27+
{...item}
28+
/>
29+
))}
30+
</Root>
31+
);
32+
};
33+
34+
export default RadioGroup;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Item } from '@radix-ui/react-radio-group';
2+
3+
import { cn } from '@/lib/utils';
4+
5+
import { radioVariants } from './radio-variants';
6+
import type { RadioGroupItemProps } from './types';
7+
8+
const RadioGroupItem = (props: RadioGroupItemProps) => {
9+
const { className, color, size, ...rest } = props;
10+
11+
const { control } = radioVariants({ color, size });
12+
13+
const mergedCls = cn(control(), className);
14+
15+
return (
16+
<Item
17+
className={mergedCls}
18+
data-color={color}
19+
data-size={size}
20+
data-slot="radio-group-item"
21+
{...rest}
22+
/>
23+
);
24+
};
25+
26+
export default RadioGroupItem;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Indicator } from '@radix-ui/react-radio-group';
2+
3+
import { cn } from '@/lib/utils';
4+
5+
import { radioVariants } from './radio-variants';
6+
import type { RadioIndicatorProps } from './types';
7+
8+
const RadioIndicator = (props: RadioIndicatorProps) => {
9+
const { className, color, ...rest } = props;
10+
11+
const { indicator } = radioVariants({ color });
12+
13+
const mergedCls = cn(indicator(), className);
14+
15+
return (
16+
<Indicator
17+
className={mergedCls}
18+
data-color={color}
19+
data-slot="radio-indicator"
20+
{...rest}
21+
/>
22+
);
23+
};
24+
25+
export default RadioIndicator;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { cn } from '@/lib/utils';
2+
3+
import { radioVariants } from './radio-variants';
4+
import type { RadioRootProps } from './types';
5+
6+
const RadioRoot = (props: RadioRootProps) => {
7+
const { className, size, ...rest } = props;
8+
9+
const { root } = radioVariants({ size });
10+
11+
const mergedCls = cn(root(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
data-size={size}
17+
data-slot="radio-root"
18+
{...rest}
19+
/>
20+
);
21+
};
22+
23+
export default RadioRoot;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export { default as Radio } from './Radio';
2+
3+
export { default as RadioGroup } from './RadioGroup';
4+
export { default as RadioGroupItem } from './RadioGroupItem';
5+
export { default as RadioIndicator } from './RadioIndicator';
6+
export { default as RadioRoot } from './RadioRoot';
7+
8+
export * from './types';
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { tv } from 'tailwind-variants';
2+
3+
export const radioVariants = tv({
4+
defaultVariants: {
5+
color: 'primary',
6+
orientation: 'horizontal',
7+
size: 'md'
8+
},
9+
slots: {
10+
control: [
11+
'peer relative shrink-0 rounded-full border shadow',
12+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background',
13+
'disabled:cursor-not-allowed disabled:opacity-50'
14+
],
15+
group: 'flex',
16+
indicator: 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-1/2 rounded-full ',
17+
label: '',
18+
root: 'flex items-center'
19+
},
20+
variants: {
21+
color: {
22+
accent: {
23+
control: `border-accent-foreground focus-visible:ring-accent-foreground/20`,
24+
indicator: `bg-accent-foreground/60`
25+
},
26+
carbon: {
27+
control: `border-carbon focus-visible:ring-carbon`,
28+
indicator: `bg-carbon`
29+
},
30+
destructive: {
31+
control: `border-destructive focus-visible:ring-destructive`,
32+
indicator: `bg-destructive`
33+
},
34+
info: {
35+
control: `border-info focus-visible:ring-info`,
36+
indicator: `bg-info`
37+
},
38+
primary: {
39+
control: `border-primary focus-visible:ring-primary`,
40+
indicator: `bg-primary`
41+
},
42+
secondary: {
43+
control: `border-secondary-foreground focus-visible:ring-secondary-foreground/20`,
44+
indicator: `bg-secondary-foreground/60`
45+
},
46+
success: {
47+
control: `border-success focus-visible:ring-success`,
48+
indicator: `bg-success`
49+
},
50+
warning: {
51+
control: `border-warning focus-visible:ring-warning`,
52+
indicator: `bg-warning`
53+
}
54+
},
55+
orientation: {
56+
horizontal: {
57+
group: 'items-center'
58+
},
59+
vertical: {
60+
group: 'flex-col'
61+
}
62+
},
63+
size: {
64+
'2xl': {
65+
control: 'size-6',
66+
group: 'gap-x-4.5 gap-y-3.5',
67+
root: 'gap-3.5'
68+
},
69+
lg: {
70+
control: 'size-4.5',
71+
group: 'gap-x-3.5 gap-y-2.5',
72+
root: 'gap-2.5'
73+
},
74+
md: {
75+
control: 'size-4',
76+
group: 'gap-x-3 gap-y-2',
77+
root: 'gap-2'
78+
},
79+
sm: {
80+
control: 'size-3.5',
81+
group: 'gap-x-2.5 gap-y-1.75',
82+
root: 'gap-1.75'
83+
},
84+
xl: {
85+
control: 'size-5',
86+
group: 'gap-x-4 gap-y-3',
87+
root: 'gap-3'
88+
},
89+
xs: {
90+
control: 'size-3',
91+
group: 'gap-x-2 gap-y-1.5',
92+
root: 'gap-1.5'
93+
}
94+
}
95+
}
96+
});
97+
98+
export type RadioSlots = keyof typeof radioVariants.slots;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type {
2+
RadioGroupIndicatorProps as _RadioGroupIndicatorProps,
3+
RadioGroupItemProps as _RadioGroupItemProps,
4+
RadioGroupProps as _RadioGroupProps
5+
} from '@radix-ui/react-radio-group';
6+
import type { ReactNode } from 'react';
7+
8+
import type { BaseComponentProps, BaseNodeProps, ClassValue, ThemeColor } from '@/types/other';
9+
10+
import type { RadioSlots } from './radio-variants';
11+
12+
export type RadioClassNames = Partial<Record<RadioSlots, ClassValue>>;
13+
14+
export interface RadioProps extends RadioGroupItemProps {
15+
classNames?: Pick<RadioClassNames, 'control' | 'indicator' | 'label' | 'root'>;
16+
label?: ReactNode;
17+
}
18+
19+
export interface RadioGroupProps extends BaseNodeProps<_RadioGroupProps> {
20+
classNames?: RadioClassNames;
21+
color?: ThemeColor;
22+
items: Omit<RadioProps, 'classNames' | 'color' | 'size'>[];
23+
}
24+
25+
export interface RadioGroupItemProps extends BaseNodeProps<_RadioGroupItemProps> {
26+
color?: ThemeColor;
27+
}
28+
29+
export interface RadioIndicatorProps extends BaseNodeProps<_RadioGroupIndicatorProps> {
30+
color?: ThemeColor;
31+
}
32+
33+
export interface RadioRootProps extends BaseComponentProps<'div'> {}

packages/ui/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export * from './components/label';
5252

5353
export * from './components/popover';
5454

55+
export * from './components/radio';
56+
5557
export * from './components/scroll-area';
5658

5759
export * from './components/skeleton';

0 commit comments

Comments
 (0)