Skip to content

Commit 178306d

Browse files
authored
[#44] ✨ RadioInput 컴포넌트 구현 (#94)
* [#44] 💄 Add custom radio input class styling * [#44] ✨ Implement RadioInput component * [#44] ♻️ Add JSX.Element type * [#44] ✨ Create utility func for handling keydown events * [#44] ♻️ Refactor RadioInput for improved accessibility * [#44] ✅ Add Storybook for RadioInput component
1 parent 362963f commit 178306d

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import clsx from 'clsx'
2+
3+
import { handleKeyDown } from '@/utils/handleKeyDown'
4+
5+
interface RadioInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
6+
label: string
7+
}
8+
9+
export const RadioInput = ({
10+
label,
11+
className = '',
12+
checked,
13+
disabled,
14+
onChange,
15+
...props
16+
}: RadioInputProps): JSX.Element => {
17+
const labelClass = clsx(
18+
'flex cursor-pointer items-center',
19+
disabled && 'cursor-not-allowed opacity-50',
20+
className
21+
)
22+
23+
return (
24+
<label className={labelClass}>
25+
<input type='radio' checked={checked} disabled={disabled} {...props} />
26+
<span
27+
role='radio'
28+
tabIndex={0}
29+
aria-checked={checked}
30+
aria-label={'radio button'}
31+
onKeyDown={e =>
32+
handleKeyDown(
33+
e,
34+
() =>
35+
onChange?.({
36+
target: { checked: true, value: props.value },
37+
} as any),
38+
disabled
39+
)
40+
}
41+
className='custom-radio'
42+
/>
43+
<span className='text-body-2 ml-4 h-22 font-normal text-gray-800'>
44+
{label}
45+
</span>
46+
</label>
47+
)
48+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { fn } from '@storybook/test'
2+
3+
import { RadioInput } from '@/components/common/input/RadioInput'
4+
5+
export default {
6+
title: 'Common/Input/RadioInput',
7+
component: RadioInput,
8+
parameters: {
9+
layout: 'centered',
10+
},
11+
tags: ['common', 'radio'],
12+
args: {
13+
onChange: fn(),
14+
},
15+
}
16+
17+
export const Default = {
18+
args: {
19+
label: '회사 ‧ 학교',
20+
},
21+
}

src/styles/globals.css

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,25 @@ select {
1414
outline: 0;
1515
cursor: pointer;
1616
}
17+
18+
input[type='radio'] {
19+
display: none;
20+
}
21+
.custom-radio {
22+
@apply relative inline-block h-20 w-20 cursor-pointer rounded-full border-[1.4px] border-solid border-gray-300;
23+
}
24+
.custom-radio::before {
25+
content: '';
26+
@apply absolute left-1/2 top-1/2 block h-12 w-12 -translate-x-1/2 -translate-y-1/2 rounded-full;
27+
}
28+
input[type='radio']:checked + .custom-radio {
29+
@apply border-primary-normal;
30+
}
31+
32+
input[type='radio']:checked + .custom-radio::before {
33+
@apply bg-primary-normal;
34+
}
35+
36+
label:focus-within .custom-radio {
37+
@apply ring-1 ring-primary-normal;
38+
}

src/utils/handleKeyDown.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const handleKeyDown = (
2+
e: React.KeyboardEvent,
3+
callback: () => void,
4+
disabled?: boolean
5+
): void => {
6+
if (e.key === 'Enter' && !disabled) {
7+
callback()
8+
}
9+
}

0 commit comments

Comments
 (0)