Skip to content

Commit ddda0b8

Browse files
committed
Feat radio components
1 parent 44b5aea commit ddda0b8

File tree

9 files changed

+1481
-0
lines changed

9 files changed

+1481
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@types/node": "^24.3.0",
2222
"happy-dom": "^18.0.1",
2323
"@testing-library/react": "^16.3.0",
24+
"@testing-library/user-event": "14.6.1",
2425
"@testing-library/jest-dom": "^6.7.0",
2526
"@devup-ui/vite-plugin": "workspace:*"
2627
},
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Radio } from '.'
2+
3+
export default {
4+
title: 'Devfive/Radio',
5+
component: Radio,
6+
}
7+
8+
export const Default = {
9+
args: {
10+
checked: undefined,
11+
colors: {
12+
primary: 'var(--primary)',
13+
border: 'var(--border)',
14+
text: 'var(--text)',
15+
bg: 'var(--bg)',
16+
hoverBg: 'var(--hoverBg)',
17+
hoverBorder: 'var(--hoverBorder)',
18+
hoverColor: 'var(--hoverColor)',
19+
checkedBg: 'var(--checkedBg)',
20+
checkedBorder: 'var(--checkedBorder)',
21+
checkedColor: 'var(--checkedColor)',
22+
disabledBg: 'var(--disabledBg)',
23+
disabledColor: 'var(--disabledColor)',
24+
},
25+
name: 'radio',
26+
children: '옵션1',
27+
variant: 'default',
28+
},
29+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { render } from '@testing-library/react'
2+
3+
import { Radio } from '../index'
4+
5+
vi.mock('react', async (originImport: any) => {
6+
const origin = await originImport()
7+
return {
8+
...origin,
9+
cache: vi.fn((arg) => arg),
10+
}
11+
})
12+
describe('Radio', () => {
13+
it('should Radio snapshot', () => {
14+
expect(render(<Radio />).container).toMatchSnapshot()
15+
expect(render(<Radio variant="button" />).container).toMatchSnapshot()
16+
expect(render(<Radio disabled />).container).toMatchSnapshot()
17+
expect(
18+
render(<Radio disabled variant="button" />).container,
19+
).toMatchSnapshot()
20+
expect(
21+
render(
22+
<Radio
23+
disabled
24+
style={{
25+
width: '500px',
26+
}}
27+
/>,
28+
).container,
29+
).toMatchSnapshot()
30+
expect(
31+
render(
32+
<Radio
33+
disabled
34+
style={{
35+
width: '500px',
36+
}}
37+
styles={{
38+
label: {
39+
width: '500px',
40+
},
41+
}}
42+
variant="button"
43+
/>,
44+
).container,
45+
).toMatchSnapshot()
46+
expect(
47+
render(
48+
<Radio
49+
disabled
50+
style={{
51+
width: '500px',
52+
}}
53+
styles={{
54+
label: {
55+
width: '500px',
56+
},
57+
}}
58+
/>,
59+
).container,
60+
).toMatchSnapshot()
61+
62+
expect(
63+
render(
64+
<Radio
65+
className="className"
66+
classNames={{ label: 'classNameLabel' }}
67+
disabled
68+
variant="button"
69+
/>,
70+
).container,
71+
).toMatchSnapshot()
72+
expect(
73+
render(
74+
<Radio
75+
className="className"
76+
classNames={{ label: 'classNameLabel' }}
77+
disabled
78+
/>,
79+
).container,
80+
).toMatchSnapshot()
81+
82+
expect(
83+
render(<Radio firstButton variant="button" />).container,
84+
).toMatchSnapshot()
85+
expect(
86+
render(<Radio lastButton variant="button" />).container,
87+
).toMatchSnapshot()
88+
})
89+
})
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import { Box, Input, Text } from '@devup-ui/react'
2+
3+
type RadioProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type'> & {
4+
checked?: boolean
5+
classNames?: {
6+
label?: string
7+
}
8+
color?: string
9+
hoverColor?: string
10+
styles?: {
11+
label?: React.CSSProperties
12+
}
13+
colors?: {
14+
primary?: string
15+
border?: string
16+
text?: string
17+
bg?: string
18+
hoverBg?: string
19+
hoverBorder?: string
20+
hoverColor?: string
21+
checkedBg?: string
22+
checkedBorder?: string
23+
checkedColor?: string
24+
disabledBg?: string
25+
disabledColor?: string
26+
}
27+
variant?: 'default' | 'button'
28+
} & (
29+
| {
30+
variant?: 'default'
31+
firstButton?: undefined
32+
lastButton?: undefined
33+
}
34+
| {
35+
variant: 'button'
36+
firstButton?: boolean
37+
lastButton?: boolean
38+
}
39+
)
40+
41+
export function Radio({
42+
className,
43+
disabled,
44+
children,
45+
variant = 'default',
46+
checked,
47+
classNames,
48+
styles,
49+
style,
50+
firstButton,
51+
lastButton,
52+
colors,
53+
...props
54+
}: RadioProps) {
55+
const isButton = variant === 'button'
56+
return (
57+
<Box
58+
alignItems={isButton ? undefined : 'center'}
59+
aria-disabled={disabled}
60+
as="label"
61+
cursor={isButton ? undefined : 'pointer'}
62+
display={isButton ? undefined : 'inline-flex'}
63+
gap={isButton ? undefined : 2}
64+
selectors={{
65+
'&[aria-disabled=true]': {
66+
cursor: 'default',
67+
},
68+
}}
69+
>
70+
{isButton ? (
71+
<Input
72+
checked={checked}
73+
className={className}
74+
data-radio-input
75+
disabled={disabled}
76+
display="none"
77+
opacity={0}
78+
styleOrder={1}
79+
type="radio"
80+
{...props}
81+
/>
82+
) : (
83+
<Input
84+
_focus={{
85+
outline: '1px sold var(--border, var(--primary))',
86+
}}
87+
appearance="none"
88+
bg="light-dark(#fff, #2E2E2E)"
89+
border="1px solid"
90+
borderColor="$border"
91+
borderRadius="100%"
92+
checked={checked}
93+
className={className}
94+
data-radio-input
95+
disabled={disabled}
96+
height="18px"
97+
m={0}
98+
selectors={{
99+
// checked
100+
'&:checked:not(:disabled)': {
101+
bg: 'var(--checkedBg, var(--primary, light-dark(#fff, #2E2E2E)))',
102+
border: '3px solid',
103+
borderColor: 'var(--checkedBg, light-dark(#fff, #2E2E2E))',
104+
boxShadow: '0 0 0 1px var(--checkedBorder, var(--primary))',
105+
},
106+
// hover
107+
'&:hover:not(:disabled,:checked)': {
108+
border: '1px solid var(--hoverBorder, var(--primary))',
109+
bg: 'var(--hoverBg, light-dark(color-mix(in srgb, var(--primary) 10%, white 90%), color-mix(in srgb, var(--primary) 10%, black 90%)))',
110+
},
111+
// disabled
112+
'&:is(:disabled, [aria-disabled=true])': {
113+
bgColor: 'var(--disabledBg, light-dark(#F0F0F3, #47474A))',
114+
},
115+
}}
116+
styleOrder={1}
117+
styleVars={{
118+
primary: colors?.primary,
119+
border: colors?.border,
120+
text: colors?.text,
121+
bg: colors?.bg,
122+
hoverBg: colors?.hoverBg,
123+
hoverBorder: colors?.hoverBorder,
124+
hoverColor: colors?.hoverColor,
125+
checkedBg: colors?.checkedBg,
126+
checkedBorder: colors?.checkedBorder,
127+
checkedColor: colors?.checkedColor,
128+
disabledBg: colors?.disabledBg,
129+
disabledColor: colors?.disabledColor,
130+
}}
131+
transition=".25s"
132+
type="radio"
133+
width="18px"
134+
{...props}
135+
/>
136+
)}
137+
{variant === 'button' ? (
138+
<Box
139+
_disabled={{
140+
cursor: 'not-allowed',
141+
}}
142+
aria-disabled={disabled}
143+
bg="var(--bg, light-dark(#fff, #2E2E2E))"
144+
border="1px solid"
145+
borderColor="$border"
146+
borderRadius={
147+
firstButton ? '6px 0 0 6px' : lastButton ? '0 6px 6px 0' : undefined
148+
}
149+
className={className}
150+
color="var(--text, light-dark(#000, #fff))"
151+
cursor="pointer"
152+
data-radio-button
153+
display="flex"
154+
px={8}
155+
py={4}
156+
selectors={{
157+
// checked
158+
'[data-radio-input]:checked + &:not([aria-disabled=true])': {
159+
fontWeight: 600,
160+
bg: `var(--checkedBg, light-dark(color-mix(in srgb, var(--primary) 10%, white 80%), color-mix(in srgb, var(--primary) 10%, black 80%)))`,
161+
borderColor: 'var(--checkedBorder, var(--primary))',
162+
color: 'var(--checkedColor, var(--primary))',
163+
},
164+
// hover
165+
'&:hover:not([aria-disabled=true])': {
166+
bg: `var(--hoverBg, light-dark(color-mix(in srgb, var(--primary) 10%, white 90%), color-mix(in srgb, var(--primary) 10%, black 90%)))`,
167+
borderColor: 'var(--hoverBorder, var(--primary))',
168+
},
169+
// disabled
170+
'[data-radio-input]:disabled + &': {
171+
bg: 'var(--disabledBg, light-dark(#F0F0F3, #47474A))',
172+
color: 'var(--disabledColor, light-dark(#D6D7DE, #373737))',
173+
},
174+
}}
175+
style={style}
176+
styleOrder={1}
177+
styleVars={{
178+
primary: colors?.primary,
179+
border: colors?.border,
180+
text: colors?.text,
181+
bg: colors?.bg,
182+
hoverBg: colors?.hoverBg,
183+
hoverBorder: colors?.hoverBorder,
184+
hoverColor: colors?.hoverColor,
185+
checkedBg: colors?.checkedBg,
186+
checkedBorder: colors?.checkedBorder,
187+
checkedColor: colors?.checkedColor,
188+
disabledBg: colors?.disabledBg,
189+
disabledColor: colors?.disabledColor,
190+
}}
191+
transition="background-color 0.25s, border-color 0.25s"
192+
w="fit-content"
193+
>
194+
{children}
195+
</Box>
196+
) : (
197+
<Text
198+
aria-disabled={disabled}
199+
className={classNames?.label}
200+
color="var(--text, light-dark(#000, #fff))"
201+
selectors={{
202+
'&[aria-disabled=true]': {
203+
color: 'var(--disabledColor, light-dark(#D6D7DE, #373737))',
204+
},
205+
}}
206+
style={style}
207+
styleOrder={1}
208+
styleVars={{
209+
primary: colors?.primary,
210+
border: colors?.border,
211+
text: colors?.text,
212+
bg: colors?.bg,
213+
hoverBg: colors?.hoverBg,
214+
hoverBorder: colors?.hoverBorder,
215+
hoverColor: colors?.hoverColor,
216+
checkedBg: colors?.checkedBg,
217+
checkedBorder: colors?.checkedBorder,
218+
checkedColor: colors?.checkedColor,
219+
disabledBg: colors?.disabledBg,
220+
disabledColor: colors?.disabledColor,
221+
}}
222+
>
223+
{children}
224+
</Text>
225+
)}
226+
</Box>
227+
)
228+
}

0 commit comments

Comments
 (0)