diff --git a/frontend/src/assets/svg/check.svg b/frontend/src/assets/svg/check.svg new file mode 100644 index 000000000..4fc9efa02 --- /dev/null +++ b/frontend/src/assets/svg/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/svg/index.ts b/frontend/src/assets/svg/index.ts index a478035a9..757c219b7 100644 --- a/frontend/src/assets/svg/index.ts +++ b/frontend/src/assets/svg/index.ts @@ -5,3 +5,4 @@ export { ReactComponent as DeleteIcon } from './delete.svg'; export { ReactComponent as EditIcon } from './edit.svg'; export { ReactComponent as LogoIcon } from './logo.svg'; export { ReactComponent as PlusIcon } from './plus.svg'; +export { ReactComponent as CheckIcon } from './check.svg'; diff --git a/frontend/src/components/common/Checkbox/Checkbox.stories.tsx b/frontend/src/components/common/Checkbox/Checkbox.stories.tsx new file mode 100644 index 000000000..3be2f6766 --- /dev/null +++ b/frontend/src/components/common/Checkbox/Checkbox.stories.tsx @@ -0,0 +1,96 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Checkbox from './Checkbox'; +import { css } from 'styled-components'; + +/** + * `Checkbox`는 대부분의 상황에서 사용할 수 있는 공용 체크박스 컴포넌트입니다. + */ +const meta = { + title: 'common/Checkbox', + component: Checkbox, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Medium: Story = { + args: { + isChecked: true, + onChange: () => + alert( + '체크박스의 onChange 이벤트가 실행되었습니다! 추후 이 이벤트를 핸들링해 state를 변경한다면, 체크박스도 바뀔 것입니다!', + ), + }, +}; + +export const MediumNotChecked: Story = { + args: { + isChecked: false, + }, +}; + +export const Small: Story = { + args: { + size: 'sm', + isChecked: true, + }, +}; + +export const SmallNotChecked: Story = { + args: { + size: 'sm', + isChecked: false, + }, +}; + +export const Large: Story = { + args: { + size: 'lg', + isChecked: true, + }, +}; + +export const LargeNotChecked: Story = { + args: { + size: 'lg', + isChecked: false, + }, +}; + +export const ExtraLarge: Story = { + args: { + size: 'xl', + isChecked: true, + }, +}; + +export const ExtraLargeNotChecked: Story = { + args: { + size: 'xl', + isChecked: false, + }, +}; + +export const CustomColor: Story = { + args: { + size: 'md', + isChecked: true, + color: '#ff8888', + }, +}; + +export const CustomStyle: Story = { + args: { + size: 'xl', + isChecked: true, + css: css` + width: 70px; + height: 50px; + + border: none; + background: linear-gradient(45deg, #00ffe5, #2600ff, #ff0ff7); + `, + }, +}; diff --git a/frontend/src/components/common/Checkbox/Checkbox.styled.ts b/frontend/src/components/common/Checkbox/Checkbox.styled.ts new file mode 100644 index 000000000..70b307d05 --- /dev/null +++ b/frontend/src/components/common/Checkbox/Checkbox.styled.ts @@ -0,0 +1,113 @@ +import { styled, css } from 'styled-components'; +import type { CheckboxSize } from '~/types/size'; +import type { CheckboxProps } from './Checkbox'; +import type { CSSProp } from 'styled-components'; + +type CustomCheckboxProps = Pick; + +type CheckIconWrapperProps = Pick; + +const checkboxSizes: Record = { + sm: css` + width: 20px; + height: 20px; + border-radius: 2px; + `, + + md: css` + width: 26px; + height: 26px; + border-radius: 3px; + `, + + lg: css` + width: 32px; + height: 32px; + border-radius: 4px; + `, + + xl: css` + width: 38px; + height: 38px; + border-radius: 5px; + `, +}; + +const checkIconSizes: Record = { + sm: css` + width: 14px; + height: 14px; + `, + + md: css` + width: 20px; + height: 20px; + `, + + lg: css` + width: 26px; + height: 26px; + `, + + xl: css` + width: 32px; + height: 32px; + `, +}; + +export const RealCheckbox = styled.input` + appearance: none; +`; + +export const CustomCheckbox = styled.span` + display: inline-block; + + ${({ size = 'md' }) => checkboxSizes[size]} + + border: 3px solid + ${({ color, theme }) => { + if (color) { + return color; + } + + return theme.color.PRIMARY; + }}; + background: transparent; + + transition: 0.2s; + cursor: pointer; + + ${RealCheckbox}:checked ~ & { + background-color: ${({ color, theme }) => { + if (color) { + return color; + } + + return theme.color.PRIMARY; + }}; + } + + ${RealCheckbox} ~ & svg { + opacity: 0; + transition: 0.2s; + } + + ${RealCheckbox}:checked ~ & svg { + opacity: 1; + } + + ${({ css }) => css}; +`; + +export const CheckIconWrapper = styled.div` + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + height: 100%; + + & svg { + ${({ size = 'md' }) => checkIconSizes[size]} + } +`; diff --git a/frontend/src/components/common/Checkbox/Checkbox.tsx b/frontend/src/components/common/Checkbox/Checkbox.tsx new file mode 100644 index 000000000..2045754fb --- /dev/null +++ b/frontend/src/components/common/Checkbox/Checkbox.tsx @@ -0,0 +1,29 @@ +import * as S from './Checkbox.styled'; +import type { CSSProp } from 'styled-components'; +import type { CheckboxSize } from '~/types/size'; +import { CheckIcon } from '~/assets/svg'; + +export interface CheckboxProps { + isChecked: boolean; + color?: string; + size?: CheckboxSize; + css?: CSSProp; + onChange?: () => void; +} + +const Checkbox = (props: CheckboxProps) => { + const { isChecked, onChange, color, size = 'md', css } = props; + + return ( + + ); +}; + +export default Checkbox; diff --git a/frontend/src/types/size.ts b/frontend/src/types/size.ts index 919c4e3d9..407683fcc 100644 --- a/frontend/src/types/size.ts +++ b/frontend/src/types/size.ts @@ -10,3 +10,4 @@ export type ThreadSize = Extract; export type NotificationSize = Extract; +export type CheckboxSize = Extract;