Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import Button from '../Button/Button';
import ResponsiveSheet from './ResponsiveSheet';

const meta = {
title: 'Components/Common/ResponsiveSheet',
component: ResponsiveSheet,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
argTypes: {
isOpen: {
control: 'boolean',
description: '시트의 열림/닫힘 상태입니다.',
},
onClose: {
action: 'closed',
description: '닫기(배경 클릭/ESC) 시 호출됩니다.',
},
children: {
control: false,
description: '시트 내부에 렌더링될 컨텐츠입니다.',
},
sheetBackground: {
control: 'color',
description: '시트의 배경색입니다. 기본값은 흰색입니다.',
},
stacked: {
control: 'boolean',
description:
'위에 다른 시트를 겹쳐 띄울 때 뒤에서 보이도록 들어올립니다. 바텀시트일 때만 적용됩니다.',
},
},
} satisfies Meta<typeof ResponsiveSheet>;

export default meta;
type Story = StoryObj<typeof meta>;

// 화면 너비가 700px 이하면 바텀시트, 그보다 넓으면 가운데 모달로 열린다.
// 툴바의 뷰포트를 700px 이하로 바꾸면 바텀시트 모양을 확인할 수 있다.
// 닫힌 채로 시작한다. 처음부터 열면 docs 페이지 전체를 오버레이가 덮는다.
const SheetTemplate = (args: Story['args']) => {
const [isOpen, setIsOpen] = useState(args?.isOpen ?? false);

const handleClose = () => {
setIsOpen(false);
args?.onClose?.();
};

return (
<div style={{ height: '100dvh', padding: 20 }}>
<Button onClick={() => setIsOpen(true)}>시트 열기</Button>
<ResponsiveSheet
isOpen={isOpen}
onClose={handleClose}
sheetBackground={args?.sheetBackground}
stacked={args?.stacked}
>
<div style={{ padding: '8px 0 40px' }}>
<h4 style={{ margin: '0 0 12px' }}>시트 콘텐츠</h4>
<p style={{ margin: 0, color: '#787878' }}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#787878은 colors.gray[700]과 같은 값이에요. 스토리라도 Toast.stories.tsx처럼 @/styles/theme/colors 토큰을 쓰는 편이 좋아요. 토큰 값이 바뀌면 카탈로그도 같이 따라가니까요.

Suggested change
<p style={{ margin: 0, color: '#787878' }}>
<p style={{ margin: 0, color: colors.gray[700] }}>

(상단에 import { colors } from '@/styles/theme/colors'; 추가가 필요해요.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했어요. colors.gray[700]으로 바꿨어요(006c962).

작은 화면에서는 하단 시트로, 큰 화면에서는 모달로 열립니다.
</p>
</div>
</ResponsiveSheet>
</div>
);
};

export const Default: Story = {
args: {
isOpen: false,
onClose: () => {},
children: null,
},
render: (args) => <SheetTemplate {...args} />,
};

// 배경색을 바꾼 경우. 바텀시트·모달 카드 모두에 적용된다.
export const CustomBackground: Story = {
args: {
isOpen: false,
onClose: () => {},
children: null,
sheetBackground: '#f5f5f5',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 #f5f5f5는 colors.gray[100]이에요. 위와 같이 토큰으로 맞춰 주세요.

Suggested change
sheetBackground: '#f5f5f5',
sheetBackground: colors.gray[100],

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했어요. colors.gray[100]으로 바꿨어요(006c962).

},
render: (args) => <SheetTemplate {...args} />,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import ToggleButton from './ToggleButton';

const meta = {
title: 'Components/Common/ToggleButton',
component: ToggleButton,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
active: {
control: 'boolean',
description: '선택된 상태인지 여부입니다. 배경색과 글자색이 바뀝니다.',
},
children: {
control: 'text',
description: '버튼 내부에 들어갈 텍스트를 지정합니다.',
},
onClick: {
description: '버튼 클릭 시 실행될 함수입니다.',
},
},
} satisfies Meta<typeof ToggleButton>;

export default meta;
type Story = StoryObj<typeof meta>;

// 여러 개를 나란히 두고 하나만 고르는 필터 칩 형태로 쓰는 경우
const ToggleGroup = () => {
const options = ['전체', '모집 중', '모집 예정', '모집 마감'];
const [selected, setSelected] = useState(options[0]);

return (
<div style={{ display: 'flex', gap: 8 }}>
{options.map((option) => (
<ToggleButton
key={option}
active={selected === option}
onClick={() => setSelected(option)}
>
{option}
</ToggleButton>
))}
</div>
);
};

// 선택되지 않은 기본 상태
export const Inactive: Story = {
args: {
active: false,
children: '모집 중',
},
};

// 선택된 상태
export const Active: Story = {
args: {
active: true,
children: '모집 중',
},
};

// 문구가 길어졌을 때의 모양
export const LongText: Story = {
args: {
active: true,
children: '이번 학기 신입 부원을 모집하는 동아리만 보기',
},
};

// 클릭할 때마다 선택이 바뀌는 그룹
export const Group: Story = {
args: {
active: false,
children: null,
},
render: () => <ToggleGroup />,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { MemoryRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react';
import WebviewTopBar from './WebviewTopBar';

const meta = {
title: 'Components/Common/WebviewTopBar',
component: WebviewTopBar,
parameters: {
layout: 'fullscreen',
},
tags: ['autodocs'],
decorators: [
(Story) => (
<MemoryRouter>
<Story />
</MemoryRouter>
),
],
argTypes: {
title: {
control: 'text',
description: '상단바 가운데에 표시할 제목입니다.',
},
onBack: {
action: 'back',
description:
'뒤로가기 버튼 클릭 시 실행될 함수입니다. 없으면 앱 브릿지 → 브라우저 history → fallbackPath 순으로 이동합니다.',
},
fallbackPath: {
control: 'text',
description:
'앱이 처리하지 않고 이전 화면도 없을 때 이동할 경로입니다. 기본값은 `/`입니다.',
},
},
} satisfies Meta<typeof WebviewTopBar>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
title: '동아리 상세',
onBack: () => {},
},
};

// 제목이 길어도 뒤로가기 버튼과 오른쪽 여백 사이에 가운데 정렬로 남는지 확인
export const LongTitle: Story = {
args: {
title: '모아동 중앙동아리 연합 신입 부원 모집 안내와 자주 묻는 질문',
onBack: () => {},
},
};

// onBack이 없는 경우. 컴포넌트가 window.history.state로 이전 화면을 판단해서
// 스토리에서는 history 분기를 재현할 수 없고 항상 fallbackPath로 이동한다.
export const WithFallbackPath: Story = {
args: {
title: '동아리 상세',
fallbackPath: '/promotions',
},
};
Loading