Skip to content

Commit

Permalink
Merge pull request #99 from sopt-makers/feat/tab
Browse files Browse the repository at this point in the history
feat(Tab) : Tab 컴포넌트 개발
  • Loading branch information
sohee-K authored Jun 22, 2024
2 parents 0772d03 + c2ca0cd commit 281f7ac
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 0 deletions.
34 changes: 34 additions & 0 deletions apps/docs/src/stories/Tab.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Meta, StoryObj } from "@storybook/react";
import { Tab } from "@sopt-makers/ui";

const meta = {
title: "Components/Tab",
component: Tab,
tags: ["autodocs"],
args: {
tabItems: ['Tab1', 'Tab2', 'Tab3'],
selectedInitial: 'Tab1',
},
argTypes: {
style: { control: 'radio', options: ['primary', 'secondary'] },
size: { control: 'radio', options: ['sm', 'md', 'lg'] },
selectedInitial: { control: 'select', options: ['Tab1', 'Tab2', 'Tab3'] },
}
} as Meta<typeof Tab>;

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

export const Primary: Story = {
args: {
style: 'primary',
size: 'sm',
},
};

export const Secondary: StoryObj = {
args: {
style: 'secondary',
size: 'lg',
},
};
45 changes: 45 additions & 0 deletions packages/ui/Tab/Tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState } from 'react';
import * as S from './style.css';
import { createTabVariant, createTabItemVariant } from './utils';

export type TabStyle = 'primary' | 'secondary';
export type TabSize = 'sm' | 'md' | 'lg';

interface TabProps<T extends string> {
style: TabStyle;
size: TabSize;
tabItems: T[];
selectedInitial?: T;
translator?: Record<T, string>;
className?: string;
onChange: (selected: T) => void;
}

function Tab<T extends string>(props: TabProps<T>) {
const { style, size, tabItems, selectedInitial, translator, className, onChange } = props;

const tabVariant = createTabVariant(style);
const tabItemVariant = createTabItemVariant(style);

const [selected, setSelected] = useState<T>(selectedInitial ?? tabItems[0]);

const handleClickTabItem = (item: T) => {
setSelected(item);
onChange(item);
}

return <div className={`${S.tab} ${tabVariant} ${className}`}>
{tabItems.map(item => {
const isSelected = selected === item ? 'selected' : '';
return <div className={S.tabItemWrap} key={item}>
<button className={`${S.tabItem} ${tabItemVariant} ${isSelected} ${S.fontStyles[size]}`} onClick={() => { handleClickTabItem(item); }} type="button">
{translator ? translator[item] : item}
</button>
<div className={`${S.tabItemUnderline} ${isSelected}`} />
</div>
}
)}
</div>
}

export default Tab;
11 changes: 11 additions & 0 deletions packages/ui/Tab/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { TabStyle } from "./Tab";

export const itemWidths: Record<TabStyle, string> = {
primary: "fit-content",
secondary: "120px",
};

export const flexGaps: Record<TabStyle, string> = {
primary: "16px",
secondary: "0",
};
1 change: 1 addition & 0 deletions packages/ui/Tab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Tab';
72 changes: 72 additions & 0 deletions packages/ui/Tab/style.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { globalStyle, style, styleVariants } from "@vanilla-extract/css";
import { createSprinkles, defineProperties } from "@vanilla-extract/sprinkles";
import theme from "../theme.css";
import { itemWidths, flexGaps } from "./constants";

export const tab = style({
display: "flex",
width: "100%",
overflowX: "scroll",

msOverflowStyle: "none",
scrollbarWidth: "none",
"::-webkit-scrollbar": {
display: "none",
},
});

export const tabItemWrap = style({
display: "flex",
flexDirection: "column",
alignItems: "center",
});

export const tabItem = style({
color: theme.colors.gray600,
background: "none",
border: "none",
cursor: "pointer",
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",

":hover": {
color: theme.colors.gray300,
},
});
globalStyle(`${tabItem}.selected`, {
color: theme.colors.white,
transition: "color 0.5s",
});

export const tabItemUnderline = style({
width: "1%",
height: "2px",
marginTop: "4px",
background: "none",
});
globalStyle(`${tabItemUnderline}.selected`, {
width: "100%",
background: theme.colors.white,
transition: "all 0.5s",
});

export const fontStyles = styleVariants({
sm: theme.fontsObject.HEADING_7_16_B,
md: theme.fontsObject.HEADING_5_20_B,
lg: theme.fontsObject.HEADING_4_24_B,
});

const tabSprinkleProperties = defineProperties({
properties: {
gap: flexGaps,
},
});
const tabItemSprinkleProperties = defineProperties({
properties: {
width: itemWidths,
},
});

export const tabSprinkles = createSprinkles(tabSprinkleProperties);
export const tabItemSprinkles = createSprinkles(tabItemSprinkleProperties);
12 changes: 12 additions & 0 deletions packages/ui/Tab/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { tabItemSprinkles, tabSprinkles } from "./style.css";
import type { TabStyle } from "./Tab";

export const createTabVariant = (style: TabStyle) =>
tabSprinkles({
gap: style,
});

export const createTabItemVariant = (style: TabStyle) =>
tabItemSprinkles({
width: style,
});
1 change: 1 addition & 0 deletions packages/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { TextField, TextArea, SearchField } from './Input';
export { default as Tag } from "./Tag";
export { default as Chip } from './Chip';
export { default as Callout } from "./Callout";
export { default as Tab } from "./Tab";

// test component
export { default as Test } from './Test';

0 comments on commit 281f7ac

Please sign in to comment.