diff --git a/package.json b/package.json index aa4ec8f..774e4bd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", "clsx": "^2.1.1", + "framer-motion": "^12.34.0", "next": "16.1.3", "react": "19.2.3", "react-calendar": "^6.0.0", @@ -26,31 +27,31 @@ "zod": "^4.3.5" }, "devDependencies": { + "@chromatic-com/storybook": "^5.0.0", "@commitlint/cli": "^20.3.1", "@commitlint/config-conventional": "^20.3.1", + "@storybook/addon-a11y": "^10.2.3", + "@storybook/addon-docs": "^10.2.3", + "@storybook/addon-vitest": "^10.2.3", + "@storybook/nextjs-vite": "^10.2.3", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "@vitest/browser-playwright": "^4.0.18", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^9", "eslint-config-next": "16.1.3", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-storybook": "^10.2.3", "husky": "^9.1.7", "lint-staged": "^16.2.7", + "playwright": "^1.58.1", "prettier": "^3.8.0", - "typescript": "^5", "storybook": "^10.2.3", - "@storybook/nextjs-vite": "^10.2.3", - "@chromatic-com/storybook": "^5.0.0", - "@storybook/addon-vitest": "^10.2.3", - "@storybook/addon-a11y": "^10.2.3", - "@storybook/addon-docs": "^10.2.3", + "typescript": "^5", "vite": "^7.3.1", - "eslint-plugin-storybook": "^10.2.3", - "vitest": "^4.0.18", - "playwright": "^1.58.1", - "@vitest/browser-playwright": "^4.0.18", - "@vitest/coverage-v8": "^4.0.18" + "vitest": "^4.0.18" }, "lint-staged": { "*.{ts,tsx,js,jsx}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81805e3..3d792c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + framer-motion: + specifier: ^12.34.0 + version: 12.34.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next: specifier: 16.1.3 version: 16.1.3(@babel/core@7.28.6)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -2024,6 +2027,20 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + framer-motion@12.34.0: + resolution: {integrity: sha512-+/H49owhzkzQyxtn7nZeF4kdH++I2FWrESQ184Zbcw5cEqNHYkE5yxWxcTLSj5lNx3NWdbIRy5FHqUvetD8FWg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2557,6 +2574,12 @@ packages: module-alias@2.2.3: resolution: {integrity: sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==} + motion-dom@12.34.0: + resolution: {integrity: sha512-Lql3NuEcScRDxTAO6GgUsRHBZOWI/3fnMlkMcH5NftzcN37zJta+bpbMAV9px4Nj057TuvRooMK7QrzMCgtz6Q==} + + motion-utils@12.29.2: + resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -5427,6 +5450,15 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + framer-motion@12.34.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + motion-dom: 12.34.0 + motion-utils: 12.29.2 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + fsevents@2.3.2: optional: true @@ -5930,6 +5962,12 @@ snapshots: module-alias@2.2.3: {} + motion-dom@12.34.0: + dependencies: + motion-utils: 12.29.2 + + motion-utils@12.29.2: {} + mrmime@2.0.1: {} ms@2.1.3: {} diff --git a/src/components/input/AccountInput.stories.tsx b/src/components/input/AccountInput.stories.tsx new file mode 100644 index 0000000..f9c41cd --- /dev/null +++ b/src/components/input/AccountInput.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; + +import AccountInput from './AccountInput'; + +const meta = { + title: 'Components/AccountInput', + component: AccountInput, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + args: { + email: 'user@example.com', + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithButton: Story = { + args: { + email: 'user@example.com', + children: ( + + ), + }, +}; + +export const Overview: Story = { + render: () => ( +
+ + + + +
+ ), + parameters: { + controls: { disable: true }, + }, +}; diff --git a/src/components/input/ActionTextArea.stories.tsx b/src/components/input/ActionTextArea.stories.tsx new file mode 100644 index 0000000..cb7ea88 --- /dev/null +++ b/src/components/input/ActionTextArea.stories.tsx @@ -0,0 +1,54 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; + +import { fn } from 'storybook/test'; + +import ActionTextArea from './ActionTextArea'; + +const meta = { + title: 'Components/ActionTextArea', + component: ActionTextArea, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + args: { + placeholder: '텍스트를 입력해 주세요.', + onSubmit: fn(), + onChange: fn(), + }, + argTypes: { + disabled: { + control: 'boolean', + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithPlaceholder: Story = { + args: { + placeholder: '댓글을 입력해 주세요.', + }, +}; + +export const Overview: Story = { + render: () => ( +
+ + +
+ ), + parameters: { + controls: { disable: true }, + }, +}; diff --git a/src/components/input/ChangePassword.stories.tsx b/src/components/input/ChangePassword.stories.tsx new file mode 100644 index 0000000..4fcb328 --- /dev/null +++ b/src/components/input/ChangePassword.stories.tsx @@ -0,0 +1,116 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; + +import { fn } from 'storybook/test'; + +import ChangePassword from './ChangePassword'; + +const meta = { + title: 'Components/ChangePassword', + component: ChangePassword, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + args: { + isEditing: false, + newPasswordProps: { + onChange: fn(), + }, + confirmPasswordProps: { + onChange: fn(), + }, + }, + argTypes: { + isEditing: { + control: 'boolean', + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Editing: Story = { + args: { + isEditing: true, + }, +}; + +export const WithError: Story = { + args: { + isEditing: true, + confirmPasswordProps: { + errorMessage: '비밀번호가 일치하지 않습니다.', + }, + }, +}; + +export const WithButtons: Story = { + args: { + isEditing: true, + children: ( +
+ + +
+ ), + }, +}; + +export const Overview: Story = { + render: () => ( +
+
+

비활성 상태

+ +
+
+

편집 상태

+ +
+
+

에러 상태

+ +
+
+ ), + parameters: { + controls: { disable: true }, + }, +}; diff --git a/src/components/input/CommentInput.stories.tsx b/src/components/input/CommentInput.stories.tsx new file mode 100644 index 0000000..bb33c3d --- /dev/null +++ b/src/components/input/CommentInput.stories.tsx @@ -0,0 +1,42 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; + +import { fn } from 'storybook/test'; + +import CommentInput from './CommentInput'; + +const meta = { + title: 'Components/CommentInput', + component: CommentInput, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + args: { + placeholder: '댓글을 달아주세요.', + onSubmit: fn(), + onChange: fn(), + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Overview: Story = { + render: () => ( +
+ +
+ ), + parameters: { + controls: { disable: true }, + }, +}; diff --git a/src/components/input/TextArea.stories.tsx b/src/components/input/TextArea.stories.tsx new file mode 100644 index 0000000..92a17bd --- /dev/null +++ b/src/components/input/TextArea.stories.tsx @@ -0,0 +1,65 @@ +import type { Meta, StoryObj } from '@storybook/nextjs-vite'; + +import { fn } from 'storybook/test'; + +import TextArea from './TextArea'; + +const meta = { + title: 'Components/TextArea', + component: TextArea, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + args: { + placeholder: '텍스트를 입력해 주세요.', + onChange: fn(), + }, + argTypes: { + rows: { + control: 'number', + }, + disabled: { + control: 'boolean', + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithRows: Story = { + args: { + rows: 5, + placeholder: '여러 줄 입력', + }, +}; + +export const Disabled: Story = { + args: { + disabled: true, + value: '비활성 상태', + }, +}; + +export const Overview: Story = { + render: () => ( +
+