-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'fe-dev' into feature/#974
- Loading branch information
Showing
15 changed files
with
242 additions
and
92 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 73 additions & 31 deletions
104
client/src/components/Design/components/Checkbox/Checkbox.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,99 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import type {Meta, StoryObj} from '@storybook/react'; | ||
|
||
import {useEffect, useState} from 'react'; | ||
import {useState} from 'react'; | ||
|
||
import Text from '../Text/Text'; | ||
|
||
import Checkbox from './Checkbox'; | ||
|
||
const meta = { | ||
title: 'Components/Checkbox', | ||
component: Checkbox, | ||
tags: ['autodocs'], | ||
parameters: { | ||
layout: 'centered', | ||
docs: { | ||
description: { | ||
component: ` | ||
Checkbox 컴포넌트는 사용자가 여러 옵션 중에서 하나 이상을 선택할 수 있게 해주는 컴포넌트입니다. | ||
### 주요 기능 | ||
- **체크 상태 관리**: checked prop으로 체크 상태를 제어할 수 있습니다. | ||
- **우측 컨텐츠**: right prop으로 체크박스 우측에 텍스트나 컴포넌트를 추가할 수 있습니다. | ||
- **접근성**: 키보드 탐색 및 스크린리더 지원 | ||
- **비활성화**: disabled prop으로 체크박스를 비활성화할 수 있습니다. | ||
### 사용 예시 | ||
\`\`\`jsx | ||
// 기본 사용 | ||
<Checkbox checked={checked} onChange={handleChange} /> | ||
// 우측 텍스트 추가 | ||
<Checkbox | ||
checked={checked} | ||
onChange={handleChange} | ||
right={<Text size="bodyBold">체크박스 라벨</Text>} | ||
/> | ||
// 비활성화 상태 | ||
<Checkbox | ||
checked={checked} | ||
onChange={handleChange} | ||
disabled={true} | ||
/> | ||
\`\`\` | ||
`, | ||
}, | ||
}, | ||
}, | ||
tags: ['autodocs'], | ||
argTypes: { | ||
labelText: { | ||
description: '', | ||
control: {type: 'text'}, | ||
checked: { | ||
description: '체크박스의 체크 상태를 제어합니다.', | ||
control: 'boolean', | ||
defaultValue: false, | ||
}, | ||
isChecked: { | ||
description: '', | ||
control: {type: 'boolean'}, | ||
right: { | ||
description: '체크박스 우측에 표시될 element입니다.', | ||
}, | ||
disabled: { | ||
description: '체크박스의 비활성화 상태를 제어합니다.', | ||
control: 'boolean', | ||
defaultValue: false, | ||
}, | ||
onChange: { | ||
description: '', | ||
control: {type: 'object'}, | ||
description: '체크박스 상태가 변경될 때 호출되는 콜백 함수입니다.', | ||
}, | ||
}, | ||
} satisfies Meta<typeof Checkbox>; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const Playground: Story = { | ||
args: { | ||
isChecked: false, | ||
onChange: () => {}, | ||
labelText: '체크박스', | ||
}, | ||
render: ({isChecked, onChange, labelText, ...args}) => { | ||
const [isCheckedState, setIsCheckedState] = useState(isChecked); | ||
const [labelTextState, setLabelTextState] = useState(labelText); | ||
|
||
useEffect(() => { | ||
setIsCheckedState(isChecked); | ||
setLabelTextState(labelText); | ||
}, [isChecked, labelText]); | ||
const ControlledCheckbox = ({ | ||
label, | ||
disabled, | ||
defaultChecked, | ||
}: { | ||
label: string; | ||
disabled?: boolean; | ||
defaultChecked?: boolean; | ||
}) => { | ||
const [checked, setChecked] = useState(defaultChecked); | ||
return ( | ||
<Checkbox | ||
checked={checked} | ||
onChange={e => setChecked(e.target.checked)} | ||
right={<Text size="bodyBold">{label}</Text>} | ||
disabled={disabled} | ||
/> | ||
); | ||
}; | ||
|
||
const handleToggle = () => { | ||
setIsCheckedState(!isCheckedState); | ||
onChange(); | ||
}; | ||
export const Default: Story = { | ||
render: args => <ControlledCheckbox label="기본 체크박스" />, | ||
}; | ||
|
||
return <Checkbox {...args} isChecked={isCheckedState} onChange={handleToggle} labelText={labelTextState} />; | ||
}, | ||
export const DisabledStates: Story = { | ||
render: args => <ControlledCheckbox label="비활성화된 체크박스" disabled defaultChecked={true} />, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 60 additions & 21 deletions
81
client/src/components/Design/components/Checkbox/Checkbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,67 @@ | ||
/** @jsxImportSource @emotion/react */ | ||
import {forwardRef, useState} from 'react'; | ||
|
||
import {useTheme} from '@components/Design/theme/HDesignProvider'; | ||
import {ariaProps, nonAriaProps} from '@components/Design/utils/attribute'; | ||
|
||
import Text from '../Text/Text'; | ||
import {IconCheck} from '../Icons/Icons/IconCheck'; | ||
|
||
import {checkboxStyle, inputGroupStyle} from './Checkbox.style'; | ||
|
||
interface Props { | ||
labelText?: string; | ||
isChecked: boolean; | ||
onChange: () => void; | ||
} | ||
|
||
const Checkbox = ({labelText, isChecked = false, onChange}: Props) => { | ||
const {theme} = useTheme(); | ||
return ( | ||
<label css={checkboxStyle}> | ||
<div css={inputGroupStyle({theme, isChecked})}> | ||
{isChecked ? <IconCheck size={20} color="onPrimary" className="check-icon" /> : null} | ||
<input type="checkbox" checked={isChecked} onChange={onChange} className="checkbox-input" /> | ||
</div> | ||
{labelText && <Text size="bodyBold">{labelText}</Text>} | ||
</label> | ||
); | ||
}; | ||
import {boxStyle, checkboxStyle, invisibleInputStyle} from './Checkbox.style'; | ||
import {CheckboxProps} from './Checkbox.type'; | ||
|
||
const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>( | ||
({right, checked: controlledChecked, onChange, defaultChecked = false, disabled, ...props}, ref) => { | ||
const {theme} = useTheme(); | ||
const [internalChecked, setInternalChecked] = useState(defaultChecked); | ||
|
||
const isControlled = controlledChecked !== undefined; | ||
const checked = isControlled ? controlledChecked : internalChecked; | ||
|
||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
if (!isControlled) { | ||
setInternalChecked(e.target.checked); | ||
} | ||
onChange?.(e); | ||
}; | ||
|
||
const handleKeyDown = (e: React.KeyboardEvent) => { | ||
if (e.key === ' ' || e.key === 'Enter') { | ||
e.preventDefault(); | ||
const input = e.currentTarget.querySelector('input'); | ||
if (input) { | ||
input.click(); | ||
} | ||
} | ||
}; | ||
|
||
return ( | ||
<label | ||
css={checkboxStyle} | ||
role="checkbox" | ||
aria-checked={checked} | ||
onKeyDown={handleKeyDown} | ||
{...ariaProps(props)} | ||
aria-label={props['aria-label'] ?? (right ? `${right} 체크박스` : '체크박스')} | ||
> | ||
<div css={boxStyle({theme, checked, disabled})}> | ||
<div aria-hidden="true" role="presentation"> | ||
{checked && <IconCheck size={20} color="onPrimary" />} | ||
</div> | ||
<input | ||
ref={ref} | ||
type="checkbox" | ||
checked={checked} | ||
onChange={handleChange} | ||
disabled={disabled} | ||
css={invisibleInputStyle} | ||
aria-hidden={true} | ||
{...nonAriaProps(props)} | ||
/> | ||
</div> | ||
{right} | ||
</label> | ||
); | ||
}, | ||
); | ||
|
||
export default Checkbox; |
5 changes: 5 additions & 0 deletions
5
client/src/components/Design/components/Checkbox/Checkbox.type.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import {InputHTMLAttributes, ReactNode} from 'react'; | ||
|
||
export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> { | ||
right?: ReactNode; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.