From bb00dddf6eeefe659025f4207bdf26b5c8c0e89c Mon Sep 17 00:00:00 2001 From: junye0l Date: Thu, 25 Sep 2025 21:48:48 +0900 Subject: [PATCH 1/3] design : add taste, block-gauge --- src/components/gauge/block-gauge.stories.tsx | 53 +++++++ src/components/gauge/block-gauge.tsx | 57 +++++++ src/components/taste/Taste.stories.tsx | 156 +++++++++++++++++++ src/components/taste/Taste.tsx | 53 +++++++ tailwind.config.ts | 2 + 5 files changed, 321 insertions(+) create mode 100644 src/components/gauge/block-gauge.stories.tsx create mode 100644 src/components/gauge/block-gauge.tsx create mode 100644 src/components/taste/Taste.stories.tsx create mode 100644 src/components/taste/Taste.tsx diff --git a/src/components/gauge/block-gauge.stories.tsx b/src/components/gauge/block-gauge.stories.tsx new file mode 100644 index 00000000..27d71d9c --- /dev/null +++ b/src/components/gauge/block-gauge.stories.tsx @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { useState } from "react"; +import BlockGauge from "./block-gauge"; + +const meta: Meta = { + title: "Components/BlockGauge", + component: BlockGauge, + parameters: { + layout: "centered", + docs: { + description: { + component: + "와인의 맛 강도를 시각적으로 표현하는 블록 게이지 컴포넌트입니다.", + }, + }, + }, + tags: ["autodocs"], + argTypes: { + level: { + control: { type: "number", min: 0, max: 6 }, + description: "게이지 레벨 (0-6)", + defaultValue: 3, + }, + maxBlocks: { + control: { type: "number", min: 1, max: 10 }, + description: "전체 블록 수", + defaultValue: 6, + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; + +type Story = StoryObj; + +export const ClickTest: Story = { + render: () => { + const [level, setLevel] = useState(3); + + return ( +
+ +
+ ); + }, +}; diff --git a/src/components/gauge/block-gauge.tsx b/src/components/gauge/block-gauge.tsx new file mode 100644 index 00000000..bc66432e --- /dev/null +++ b/src/components/gauge/block-gauge.tsx @@ -0,0 +1,57 @@ +import { cn } from "@/lib/utils"; + +interface BlockGaugeProps { + level: number; // 0-6 사이의 값 (0: 비어있음, 6: 가득 참) + maxBlocks?: number; // 총 블록 수 (기본값: 6) + color?: string; // 활성화된 블록 색상 + onChange?: (newLevel: number) => void; // 클릭 시 호출될 함수 +} + +const BlockGauge = ({ + level, + maxBlocks = 6, + color = "bg-black", + onChange, +}: BlockGaugeProps) => { + // 레벨이 범위를 벗어나지 않도록 조정 + const safeLevel = Math.max(0, Math.min(level, maxBlocks)); + + // 블록 클릭 핸들러 + const handleClick = (clickedIndex: number) => { + if (!onChange) return; + + const newLevel = clickedIndex + 1; + // 같은 레벨을 클릭하면 0으로, 다른 레벨을 클릭하면 해당 레벨로 + onChange(newLevel === safeLevel ? 0 : newLevel); + }; + + return ( +
+ {Array.from({ length: maxBlocks }).map((_, index) => ( +
+ ); +}; + +export default BlockGauge; diff --git a/src/components/taste/Taste.stories.tsx b/src/components/taste/Taste.stories.tsx new file mode 100644 index 00000000..6dd2bc19 --- /dev/null +++ b/src/components/taste/Taste.stories.tsx @@ -0,0 +1,156 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { useState } from "react"; +import Taste from "./Taste"; +import { cn } from "@/lib/utils"; + +const meta: Meta = { + title: "Components/Taste", + component: Taste, + parameters: { + layout: "centered", + docs: { + description: { + component: + "와인의 맛 특성과 강도를 표시하는 컴포넌트입니다. 모바일 : 343px, 태블릿과 PC : 480px", + }, + }, + }, + tags: ["autodocs"], + argTypes: { + type: { + control: "text", + description: "와인 맛의 종류", + defaultValue: "바디감", + }, + data: { + control: { type: "number", min: 0, max: 6 }, + description: "맛 강도 데이터 (0-6)", + defaultValue: 3, + }, + taste: { + control: "text", + description: "맛의 특징", + defaultValue: "진해요", + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const ResponsiveTasteWrapper = ({ + children, +}: { + children: React.ReactNode; +}) => { + return ( +
+ {children} +
+ ); +}; + +export const InteractiveWineProfile: Story = { + render: () => { + const [bodyLevel, setBodyLevel] = useState(4); + const [tanninLevel, setTanninLevel] = useState(2); + const [sweetnessLevel, setSweetnessLevel] = useState(1); + const [acidityLevel, setAcidityLevel] = useState(3); + + return ( +
+
+ + + + + + + + + + + + + + + +
+ + {/* 현재 설정값 표시 */} +
+ 현재 와인 프로필: 바디감 {bodyLevel}, 탄닌{" "} + {tanninLevel}, 당도 {sweetnessLevel}, 산미 {acidityLevel} +
+
+ ); + }, + parameters: { + layout: "padded", + docs: { + description: { + story: + "블럭을 마우스로 클릭하면 게이지가 차오릅니다. 또한 어느 블럭이든 두 번 클릭하면 게이지가 0으로 됩니다.", + }, + }, + }, +}; diff --git a/src/components/taste/Taste.tsx b/src/components/taste/Taste.tsx new file mode 100644 index 00000000..2fdeee4f --- /dev/null +++ b/src/components/taste/Taste.tsx @@ -0,0 +1,53 @@ +import { cn } from "@/lib/utils"; +import BlockGauge from "../gauge/block-gauge"; + +interface TasteProps { + type: string; + data: number; // 0-6 사이의 값 + taste: string; + onChange?: (newLevel: number) => void; +} + +const Taste = ({ type, data, taste, onChange }: TasteProps) => { + return ( +
+
+ {/* 왼쪽: type */} +
+ {type} +
+ + {/* 중간: 게이지 */} +
+ +
+ + {/* 오른쪽: taste - data가 0일 때 색상 변경 */} +
+ {taste} +
+
+
+ ); +}; + +export default Taste; diff --git a/tailwind.config.ts b/tailwind.config.ts index 93070bc2..07e25202 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -18,6 +18,8 @@ export default { gray600: "#8C8C8B", gray800: "#484746", primary: "#1A1918", + neutral200: "#F2F2F2", + neutral400: "#BABABA", }, screens: { mobile: { max: "743px" }, From 9765b56d3ed728adbd721adc698b9d724944eb44 Mon Sep 17 00:00:00 2001 From: junye0l Date: Fri, 26 Sep 2025 09:37:27 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor=20:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/gauge/block-gauge.stories.tsx | 10 ++---- src/components/gauge/block-gauge.tsx | 37 ++++++++------------ src/components/taste/Taste.stories.tsx | 9 ++--- src/components/taste/Taste.tsx | 6 ++-- 4 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/components/gauge/block-gauge.stories.tsx b/src/components/gauge/block-gauge.stories.tsx index 27d71d9c..17e3439c 100644 --- a/src/components/gauge/block-gauge.stories.tsx +++ b/src/components/gauge/block-gauge.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/nextjs"; import { useState } from "react"; -import BlockGauge from "./block-gauge"; +import BlockGauge, { type GaugeLevel } from "./block-gauge"; const meta: Meta = { title: "Components/BlockGauge", @@ -21,11 +21,6 @@ const meta: Meta = { description: "게이지 레벨 (0-6)", defaultValue: 3, }, - maxBlocks: { - control: { type: "number", min: 1, max: 10 }, - description: "전체 블록 수", - defaultValue: 6, - }, }, decorators: [ (Story) => ( @@ -42,10 +37,11 @@ type Story = StoryObj; export const ClickTest: Story = { render: () => { - const [level, setLevel] = useState(3); + const [level, setLevel] = useState(3); return (
+

레벨: {level}

); diff --git a/src/components/gauge/block-gauge.tsx b/src/components/gauge/block-gauge.tsx index bc66432e..898bea0d 100644 --- a/src/components/gauge/block-gauge.tsx +++ b/src/components/gauge/block-gauge.tsx @@ -1,53 +1,44 @@ import { cn } from "@/lib/utils"; +type GaugeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6; + interface BlockGaugeProps { - level: number; // 0-6 사이의 값 (0: 비어있음, 6: 가득 참) - maxBlocks?: number; // 총 블록 수 (기본값: 6) - color?: string; // 활성화된 블록 색상 - onChange?: (newLevel: number) => void; // 클릭 시 호출될 함수 + level: GaugeLevel; // 0-6 사이의 값 (0: 비어있음, 6: 가득 참) + color?: string; // 활성화된 블록 색상 (기본: bg-black) + onChange?: (newLevel: GaugeLevel) => void; // 클릭 시 호출될 함수 } const BlockGauge = ({ level, - maxBlocks = 6, color = "bg-black", onChange, }: BlockGaugeProps) => { - // 레벨이 범위를 벗어나지 않도록 조정 - const safeLevel = Math.max(0, Math.min(level, maxBlocks)); - - // 블록 클릭 핸들러 const handleClick = (clickedIndex: number) => { if (!onChange) return; - const newLevel = clickedIndex + 1; - // 같은 레벨을 클릭하면 0으로, 다른 레벨을 클릭하면 해당 레벨로 - onChange(newLevel === safeLevel ? 0 : newLevel); + const newLevel = (clickedIndex + 1) as GaugeLevel; + onChange(newLevel === level ? 0 : newLevel); }; return (
- {Array.from({ length: maxBlocks }).map((_, index) => ( + {Array.from({ length: 6 }).map((_, index) => (
@@ -55,3 +46,5 @@ const BlockGauge = ({ }; export default BlockGauge; + +export type { GaugeLevel }; diff --git a/src/components/taste/Taste.stories.tsx b/src/components/taste/Taste.stories.tsx index 6dd2bc19..17b7e672 100644 --- a/src/components/taste/Taste.stories.tsx +++ b/src/components/taste/Taste.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from "@storybook/nextjs"; import { useState } from "react"; import Taste from "./Taste"; import { cn } from "@/lib/utils"; +import { GaugeLevel } from "../gauge/block-gauge"; const meta: Meta = { title: "Components/Taste", @@ -59,10 +60,10 @@ const ResponsiveTasteWrapper = ({ export const InteractiveWineProfile: Story = { render: () => { - const [bodyLevel, setBodyLevel] = useState(4); - const [tanninLevel, setTanninLevel] = useState(2); - const [sweetnessLevel, setSweetnessLevel] = useState(1); - const [acidityLevel, setAcidityLevel] = useState(3); + const [bodyLevel, setBodyLevel] = useState(4); + const [tanninLevel, setTanninLevel] = useState(2); + const [sweetnessLevel, setSweetnessLevel] = useState(1); + const [acidityLevel, setAcidityLevel] = useState(3); return (
diff --git a/src/components/taste/Taste.tsx b/src/components/taste/Taste.tsx index 2fdeee4f..3f601778 100644 --- a/src/components/taste/Taste.tsx +++ b/src/components/taste/Taste.tsx @@ -1,11 +1,11 @@ import { cn } from "@/lib/utils"; -import BlockGauge from "../gauge/block-gauge"; +import BlockGauge, { type GaugeLevel } from "../gauge/block-gauge"; interface TasteProps { type: string; - data: number; // 0-6 사이의 값 + data: GaugeLevel; // 0-6 사이의 값 taste: string; - onChange?: (newLevel: number) => void; + onChange?: (newLevel: GaugeLevel) => void; } const Taste = ({ type, data, taste, onChange }: TasteProps) => { From 31f1cc14530f221420f5e4fc5d9a8a41b8fa4505 Mon Sep 17 00:00:00 2001 From: junye0l Date: Fri, 26 Sep 2025 09:48:38 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor=20:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20'use=20client'=20=EC=A7=80=EC=8B=9C?= =?UTF-8?q?=EB=AC=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/gauge/block-gauge.tsx | 2 ++ src/components/taste/Taste.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/components/gauge/block-gauge.tsx b/src/components/gauge/block-gauge.tsx index 898bea0d..e7781104 100644 --- a/src/components/gauge/block-gauge.tsx +++ b/src/components/gauge/block-gauge.tsx @@ -1,3 +1,5 @@ +"use client"; + import { cn } from "@/lib/utils"; type GaugeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6; diff --git a/src/components/taste/Taste.tsx b/src/components/taste/Taste.tsx index 3f601778..02a68982 100644 --- a/src/components/taste/Taste.tsx +++ b/src/components/taste/Taste.tsx @@ -1,3 +1,5 @@ +"use client"; + import { cn } from "@/lib/utils"; import BlockGauge, { type GaugeLevel } from "../gauge/block-gauge";