Skip to content

Commit b6b5f62

Browse files
authored
Merge pull request #39 from Wedvice/f/tab/heeji_kang
[feat] Tab 컴포넌트 구현
2 parents 4bd1cae + 8292c6a commit b6b5f62

3 files changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use client';
2+
3+
import type { Meta, StoryObj } from '@storybook/react';
4+
import { useState } from 'react';
5+
import { Tab, TabInfo, TabProps } from './Tab';
6+
7+
// Tab 타입 정의
8+
type TabType = 'comment' | 'photo' | 'link';
9+
10+
const defaultTabInfo: TabInfo<TabType> = [
11+
{ tabType: 'comment', label: '댓글' },
12+
{ tabType: 'photo', label: '사진', count: 0 },
13+
{ tabType: 'link', label: '링크', count: 0 },
14+
];
15+
16+
const meta: Meta<typeof Tab> = {
17+
title: 'components/Tab',
18+
component: Tab,
19+
argTypes: {
20+
selectedTab: { control: 'text' },
21+
onClick: { action: 'clicked' },
22+
},
23+
};
24+
export default meta;
25+
26+
type Story = StoryObj<TabProps<TabType>>;
27+
28+
// 기본 Tab UI
29+
export const Default: Story = {
30+
render: (args) => {
31+
const [selectedTab, setSelectedTab] = useState<TabType>('comment');
32+
33+
return <Tab {...args} selectedTab={selectedTab} onClick={setSelectedTab} />;
34+
},
35+
args: {
36+
tabInfo: defaultTabInfo,
37+
},
38+
};
39+
40+
// count가 있는 Tab
41+
export const WithCounts: Story = {
42+
render: (args) => {
43+
const [selectedTab, setSelectedTab] = useState<TabType>('comment');
44+
45+
return (
46+
<Tab
47+
{...args}
48+
tabInfo={[
49+
{ tabType: 'comment', label: '댓글' },
50+
{ tabType: 'photo', label: '사진', count: 9 },
51+
{ tabType: 'link', label: '링크', count: 5 },
52+
]}
53+
selectedTab={selectedTab}
54+
onClick={setSelectedTab}
55+
/>
56+
);
57+
},
58+
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use client';
2+
3+
import { Dispatch, forwardRef, ReactNode, SetStateAction } from 'react';
4+
5+
// Tab 정보를 나타내는 타입
6+
export type TabInfo<T> = {
7+
tabType: T; // Tab의 타입을 나타내는 제네릭 타입 T
8+
label: string; // Tab의 이름
9+
count?: number; // 컨텐츠 개수
10+
}[];
11+
12+
// 각 tab에 대응되는 content
13+
export type ContentInfo<TabType extends string> = Record<
14+
TabType,
15+
{ render: () => ReactNode }
16+
>;
17+
18+
export interface TabProps<T> {
19+
tabInfo: TabInfo<T>;
20+
selectedTab: T;
21+
className?: string;
22+
onClick: Dispatch<SetStateAction<T>>;
23+
}
24+
25+
const TabComponent = <T,>(
26+
{ className, tabInfo, selectedTab, onClick, ...props }: TabProps<T>,
27+
ref: React.Ref<HTMLDivElement>,
28+
) => {
29+
return (
30+
<div
31+
ref={ref}
32+
className={`${className} flex h-[45px] w-full items-center text-center`}
33+
{...props}
34+
>
35+
{tabInfo.map(({ tabType, label, count }) => (
36+
<button
37+
key={String(tabType)}
38+
className={`h-[45px] w-full select-none border-b-2 text-base font-medium ${
39+
selectedTab === tabType
40+
? 'border-white text-white'
41+
: 'border-gray-200 text-gray-600'
42+
}`}
43+
onClick={() => onClick(tabType)}
44+
>
45+
<span>{label}</span>
46+
{count !== undefined && count > 0 && (
47+
<span className='ml-1'>{count}</span>
48+
)}
49+
</button>
50+
))}
51+
</div>
52+
);
53+
};
54+
55+
export const Tab = forwardRef(TabComponent) as <T>(
56+
props: TabProps<T> & { ref?: React.Ref<HTMLDivElement> },
57+
) => JSX.Element;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { Tab } from './Tab';

0 commit comments

Comments
 (0)