diff --git a/src/composable/Tab/Tab.css.ts b/src/composable/Tab/Tab.css.ts
new file mode 100644
index 0000000..afb865a
--- /dev/null
+++ b/src/composable/Tab/Tab.css.ts
@@ -0,0 +1,50 @@
+import { style, styleVariants } from '@vanilla-extract/css';
+import { backgroundColorVariants } from '@/styles/common/color.css';
+import { flexCenter } from '@/styles/common/flex.css';
+
+export const wrapStyle = style({
+ display: 'flex',
+ gap: '0.25rem',
+});
+
+const buttonAnchorCommonStyle = style([
+ flexCenter,
+ backgroundColorVariants['secondary-variant'],
+ {
+ position: 'relative',
+ height: '0.125rem',
+ borderRadius: '0.25rem',
+ transition: 'all 0.3s ease-in-out',
+ },
+]);
+
+export const buttonAnchorStyleVariant = styleVariants({
+ SELECTED: [
+ buttonAnchorCommonStyle,
+ {
+ width: '2rem',
+ opacity: 1,
+ ':hover': {
+ opacity: 0.8,
+ },
+ },
+ ],
+ DEFAULT: [
+ buttonAnchorCommonStyle,
+ {
+ width: '1.5rem',
+ opacity: 0.4,
+ ':hover': {
+ opacity: 0.2,
+ },
+ },
+ ],
+});
+
+export const buttonStyle = style({
+ position: 'absolute',
+ boxSizing: 'content-box',
+ width: '100%',
+ height: '100%',
+ padding: '0.25rem 0.1rem',
+});
diff --git a/src/composable/Tab/Tab.tsx b/src/composable/Tab/Tab.tsx
index 26f63ad..6315cfa 100644
--- a/src/composable/Tab/Tab.tsx
+++ b/src/composable/Tab/Tab.tsx
@@ -1,7 +1,47 @@
import React from 'react';
+import Button from '../Button/Button';
+import { buttonAnchorStyleVariant, buttonStyle, wrapStyle } from './Tab.css';
-const Tab = () => {
- return
;
+export interface TabProps {
+ selectedIdx: number;
+ onClick: (currentIdx: number) => void;
+ length: number;
+}
+
+const Tab = ({ length, selectedIdx, onClick }: TabProps) => {
+ return (
+
+ {Array.from({ length: length > 5 ? 5 : length }).map((_, idx) => (
+
+ ))}
+
+ );
};
+interface ItemProps {
+ idx: number;
+ isSelected: boolean;
+ onClick: (currentIdx: number) => void;
+}
+
+const Item = ({ idx, isSelected, onClick }: ItemProps) => {
+ const handleClick = () => {
+ onClick(idx);
+ };
+ return (
+
+
+
+ );
+};
+
+Tab.Item = Item;
+
export default Tab;
diff --git a/src/hook/useOceanTab.ts b/src/hook/useOceanTab.ts
new file mode 100644
index 0000000..6bd1787
--- /dev/null
+++ b/src/hook/useOceanTab.ts
@@ -0,0 +1,16 @@
+import { useState } from 'react';
+
+const useOceanTab = (initIdx?: number) => {
+ const [selectedIdx, setSelectedIdx] = useState(initIdx || 0);
+
+ const handleClick = (idx: number) => {
+ setSelectedIdx(idx);
+ };
+
+ return {
+ selectedIdx,
+ handleClick,
+ };
+};
+
+export default useOceanTab;
diff --git a/src/stories/Common/Tab/Tab.stories.tsx b/src/stories/Common/Tab/Tab.stories.tsx
new file mode 100644
index 0000000..4c7dd21
--- /dev/null
+++ b/src/stories/Common/Tab/Tab.stories.tsx
@@ -0,0 +1,32 @@
+import { Meta, StoryObj } from '@storybook/react';
+import Tab from '@/composable/Tab/Tab';
+
+const meta: Meta = {
+ title: 'Common/Tab',
+ component: Tab,
+ tags: ['autodocs'],
+ parameters: { layout: 'fullscreen' },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+type Story = StoryObj;
+export const Static: Story = {
+ args: {
+ length: 5,
+ selectedIdx: 0,
+ },
+};
+export default meta;
diff --git a/src/stories/Common/Tab/Usage.stories.tsx b/src/stories/Common/Tab/Usage.stories.tsx
new file mode 100644
index 0000000..121e539
--- /dev/null
+++ b/src/stories/Common/Tab/Usage.stories.tsx
@@ -0,0 +1,39 @@
+import { Meta, StoryObj } from '@storybook/react';
+import Tab, { TabProps } from '@/composable/Tab/Tab';
+import useOceanTab from '@/hook/useOceanTab';
+
+const Template = (args: TabProps) => {
+ const { selectedIdx, handleClick } = useOceanTab(args.selectedIdx);
+ return (
+
+
+
+ );
+};
+
+const meta: Meta = {
+ title: 'Common/Tab',
+ component: Template,
+ parameters: { layout: 'fullscreen' },
+};
+
+type Story = StoryObj;
+export const Usage: Story = {
+ args: {
+ length: 5,
+ selectedIdx: 0,
+ },
+};
+
+export default meta;