diff --git a/frontend/src/components/common/DateInput/DateInput.stories.tsx b/frontend/src/components/common/DateInput/DateInput.stories.tsx index 77e9b9105..73d2b0111 100644 --- a/frontend/src/components/common/DateInput/DateInput.stories.tsx +++ b/frontend/src/components/common/DateInput/DateInput.stories.tsx @@ -4,7 +4,7 @@ import formatDate from '@utils/formatDate'; import DateInput from './index'; const meta: Meta = { - title: 'Common/DateInput', + title: 'Common/Input/DateInput', component: DateInput, parameters: { layout: 'centered', diff --git a/frontend/src/components/common/QuestionBox/QuestionBox.stories.tsx b/frontend/src/components/common/QuestionBox/QuestionBox.stories.tsx index fe1b9f055..e93a6cfbe 100644 --- a/frontend/src/components/common/QuestionBox/QuestionBox.stories.tsx +++ b/frontend/src/components/common/QuestionBox/QuestionBox.stories.tsx @@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import QuestionBox from '.'; const meta: Meta = { - title: 'Common/QuestionBox', + title: 'Components/QuestionBox', component: QuestionBox, parameters: { layout: 'centered', diff --git a/frontend/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx b/frontend/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx new file mode 100644 index 000000000..a91334563 --- /dev/null +++ b/frontend/src/components/common/ToggleSwitch/ToggleSwitch.stories.tsx @@ -0,0 +1,82 @@ +import { useArgs } from '@storybook/preview-api'; +import type { Meta, StoryObj } from '@storybook/react'; +import ToggleSwitch from './index'; + +const meta: Meta = { + title: 'Common/ToggleSwitch', + component: ToggleSwitch, + parameters: { + layout: 'centered', + docs: { + description: { + component: 'ToggleSwitch 컴포넌트는 스위치를 토글할 수 있는 기능을 제공합니다.', + }, + }, + }, + tags: ['autodocs'], + argTypes: { + isChecked: { + description: '스위치의 체크 상태를 나타냅니다.', + control: { type: 'boolean' }, + table: { + type: { summary: 'boolean' }, + }, + }, + isDisabled: { + description: '스위치의 활성화 상태를 나타냅니다.', + control: { type: 'boolean' }, + table: { + type: { summary: 'boolean' }, + }, + }, + onChange: { + description: '스위치 상태 변경 시 호출되는 콜백 함수입니다.', + action: 'changed', + table: { + type: { summary: '() => void' }, + }, + }, + }, + decorators: [ + (Child, context) => { + const [args, updateArgs] = useArgs(); + + const handleChange = () => { + updateArgs({ isChecked: !context.args.isChecked }); + }; + + return ( + + ); + }, + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isChecked: false, + }, +}; + +export const Disabled: Story = { + args: { + isChecked: false, + isDisabled: true, + }, +}; + +Default.parameters = { + docs: { + description: { + story: 'ToggleSwitch 컴포넌트의 기본 상태입니다.', + }, + }, +}; diff --git a/frontend/src/components/common/ToggleSwitch/index.tsx b/frontend/src/components/common/ToggleSwitch/index.tsx new file mode 100644 index 000000000..2b06b896d --- /dev/null +++ b/frontend/src/components/common/ToggleSwitch/index.tsx @@ -0,0 +1,20 @@ +import S, { StyleProps } from './style'; + +interface ToggleSwitchProps extends StyleProps { + onChange: () => void; +} + +export default function ToggleSwitch({ isChecked, isDisabled, onChange }: ToggleSwitchProps) { + return ( + + + + ); +} diff --git a/frontend/src/components/common/ToggleSwitch/style.ts b/frontend/src/components/common/ToggleSwitch/style.ts new file mode 100644 index 000000000..92983df71 --- /dev/null +++ b/frontend/src/components/common/ToggleSwitch/style.ts @@ -0,0 +1,61 @@ +import { css } from '@emotion/react'; +import styled from '@emotion/styled'; + +export interface StyleProps { + isChecked: boolean; + isDisabled: boolean; +} + +const Switch = styled.div` + --parent-width: 5rem; + --parent-padding: 0.3rem; + + width: var(--parent-width); + aspect-ratio: 5/3; + + background-color: ${({ isChecked, theme }) => + isChecked ? theme.baseColors.purplescale[600] : theme.baseColors.grayscale[100]}; + outline: 0.2rem solid + ${({ isChecked, theme }) => (isChecked ? theme.baseColors.purplescale[600] : theme.baseColors.grayscale[700])}; + border-radius: 99rem; + + display: flex; + align-items: center; + + padding: var(--parent-padding); + + cursor: pointer; + transition: background-color 0.3s; + outline-offset: -0.1rem; + ${({ isDisabled, theme }) => + isDisabled && + css({ + backgroundColor: theme.baseColors.grayscale[400], + outline: `0.2rem solid ${theme.baseColors.grayscale[500]}`, + })} +`; + +const Knob = styled.div` + height: 100%; + aspect-ratio: 1/1; + background-color: ${({ isChecked, theme }) => + isChecked ? theme.baseColors.grayscale[200] : theme.baseColors.grayscale[700]}; + border-radius: 50%; + + transform: ${({ isChecked }) => + isChecked ? 'translate(calc(var(--parent-width) - var(--parent-padding)*2 - 100%))' : 'translate(0)'}; + transition: transform 0.2s; + + ${({ isDisabled, theme }) => + isDisabled && + css({ + backgroundColor: theme.baseColors.grayscale[700], + })} +`; + +const S = { + Switch, + Knob, +}; + +export default S;