diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/.storybook/main.ts b/.storybook/main.ts index 098d7f8..d5d11ee 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -6,11 +6,11 @@ import type { StorybookConfig } from "@storybook/nextjs-vite"; const require = createRequire(import.meta.url); const config: StorybookConfig = { - stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], - addons: [ - "@chromatic-com/storybook", - "@storybook/addon-docs", + stories: [ + "../src/**/*.mdx", + "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)", ], + addons: ["@chromatic-com/storybook", "@storybook/addon-docs"], framework: "@storybook/nextjs-vite", staticDirs: ["../public"], diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 557ab73..633622c 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,7 +1,11 @@ import type { Preview } from "@storybook/nextjs-vite"; +import { INITIAL_VIEWPORTS } from "storybook/viewport"; const preview: Preview = { parameters: { + viewport: { + options: INITIAL_VIEWPORTS, + }, controls: { matchers: { color: /(background|color)$/i, diff --git a/.vscode/workspace.code-snippets b/.vscode/workspace.code-snippets index 3794ad9..a62cea1 100644 --- a/.vscode/workspace.code-snippets +++ b/.vscode/workspace.code-snippets @@ -2,7 +2,7 @@ "StoryBook": { "prefix": ["!sb", "!storybook"], "body": [ - "import type { Meta, StoryObj } from '@storybook/react-vite';\n", + "import type { Meta, StoryObj } from '@storybook/nextjs-vite';\n", "const meta: Meta = {", " component: ${1:Component},", diff --git a/src/entities/feed/ui/FeedCard.stories.tsx b/src/entities/feed/ui/FeedCard.stories.tsx new file mode 100644 index 0000000..d241a77 --- /dev/null +++ b/src/entities/feed/ui/FeedCard.stories.tsx @@ -0,0 +1,29 @@ +import { FeedCard } from "./FeedCard"; +import type { Meta, StoryObj } from "@storybook/nextjs-vite"; +import { expect, userEvent, within } from "storybook/test"; + +const meta: Meta = { + component: FeedCard, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: {}, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const likeButton = canvas.getByRole("button", { name: "좋아요" }); + + const heartIcon = likeButton.querySelector("svg") as SVGSVGElement; + await userEvent.click(heartIcon); + + const unlikeButton = canvas.getByRole("button", { name: "좋아요 취소" }); + await expect(unlikeButton).toBeInTheDocument(); + + await userEvent.click(heartIcon); + + const likeButtonAgain = canvas.getByRole("button", { name: "좋아요" }); + await expect(likeButtonAgain).toBeInTheDocument(); + }, +}; diff --git a/src/shared/ui/badge/Badge.mdx b/src/shared/ui/badge/Badge.mdx new file mode 100644 index 0000000..bacc144 --- /dev/null +++ b/src/shared/ui/badge/Badge.mdx @@ -0,0 +1,70 @@ +import * as BadgeStories from "./Badge.stories"; +import { + Meta, + Canvas, + Controls, + Story, + Description, + ArgTypes, + Source, +} from "@storybook/addon-docs/blocks"; + + + +# Badge + +상태나 카테고리를 나타내는 작은 라벨 컴포넌트입니다. + + + +## Props + + + +## Examples + +### Variants + +`Badge` 컴포넌트는 5가지 스타일(`primary`, `secondary`, `success`, `danger`,`warning`)을 지원합니다. + + + +primary + secondary + success + danger + warning + `} +/> + +
+ +### Sizes + +`Badge` 컴포넌트는 3가지 크기(`sm`, `md`, `lg`)를 지원합니다. + + + +Small + Medium + Large + `} +/> + +
+ +## 사용 가이드 (Usage) + +`children` prop을 통해 뱃지 텍스트를 전달합니다. + +```tsx + + 뱃지 텍스트 + +``` diff --git a/src/shared/ui/badge/Badge.stories.tsx b/src/shared/ui/badge/Badge.stories.tsx new file mode 100644 index 0000000..a116bc5 --- /dev/null +++ b/src/shared/ui/badge/Badge.stories.tsx @@ -0,0 +1,38 @@ +import { Badge } from "./Badge"; +import type { Meta, StoryObj } from "@storybook/nextjs-vite"; + +const meta: Meta = { + title: "design-system/Badge", + component: Badge, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { children: "뱃지입니다." }, +}; + +export const Variants: Story = { + args: { children: "variants" }, + render: () => ( +
+ primary + secondary + success + danger + warning +
+ ), +}; + +export const Sizes: Story = { + args: { children: "size" }, + render: () => ( +
+ Large + Medium + Small +
+ ), +}; diff --git a/src/shared/ui/header/Header.stories.tsx b/src/shared/ui/header/Header.stories.tsx new file mode 100644 index 0000000..0f6f47d --- /dev/null +++ b/src/shared/ui/header/Header.stories.tsx @@ -0,0 +1,48 @@ +import { Header } from "./Header"; +import type { Meta, StoryObj } from "@storybook/nextjs-vite"; +import { expect, userEvent, within } from "storybook/test"; + +const meta: Meta = { + title: "shared/Header", + component: Header, + parameters: { + layout: "fullscreen", + nextjs: { + appDirectory: true, + navigation: { + pathname: "/", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Desktop: Story = {}; + +export const Mobile: Story = { + globals: { + viewport: { + value: "iphone12", + isRotated: false, + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const menuButton = canvas.getByRole("button", { name: "메뉴 열기" }); + + await expect(menuButton).toBeInTheDocument(); + + await userEvent.click(menuButton); + + const closeButton = canvas.getByRole("button", { name: "닫기" }); + + await expect(closeButton).toBeVisible(); + + await userEvent.click(closeButton); + + await expect(menuButton).toBeInTheDocument(); + }, +};