diff --git "a/.github/ISSUE_TEMPLATE/\360\237\220\236-\341\204\207\341\205\245\341\204\200\341\205\263-\341\204\205\341\205\265\341\204\221\341\205\251\341\204\220\341\205\263-\341\204\220\341\205\246\341\206\267\341\204\221\341\205\263\341\206\257\341\204\205\341\205\265\341\206\272.md" "b/.github/ISSUE_TEMPLATE/\360\237\220\236-\341\204\207\341\205\245\341\204\200\341\205\263-\341\204\205\341\205\265\341\204\221\341\205\251\341\204\220\341\205\263-\341\204\220\341\205\246\341\206\267\341\204\221\341\205\263\341\206\257\341\204\205\341\205\265\341\206\272.md" new file mode 100644 index 00000000..286df978 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\360\237\220\236-\341\204\207\341\205\245\341\204\200\341\205\263-\341\204\205\341\205\265\341\204\221\341\205\251\341\204\220\341\205\263-\341\204\220\341\205\246\341\206\267\341\204\221\341\205\263\341\206\257\341\204\205\341\205\265\341\206\272.md" @@ -0,0 +1,25 @@ +--- +name: "\U0001F41E 버그 리포트 템플릿" +about: 프로젝트에서 발생하는 버그에 대해 명세합니다. +title: "[Bug] Issue title" +labels: "\U0001F41E BugFix" +assignees: "" +--- + +## 💬 버그 설명 + +문제가 생긴 상황을 간단히 설명해주세요. + +## 🔁 재현 방법 + +문제가 어떻게 발생했는지 순서대로 작성해주세요. + +## ⚙️ 기대 동작 + +정상적으로 어떤 동작을 기대했는지 설명해주세요. + +## 📸 스크린샷 (선택사항) + +## 📄 추가 정보 + +기타 참고할만한 정보나 자료가 있다면 자유롭게 작성해주세요. diff --git "a/.github/ISSUE_TEMPLATE/\360\237\220\236-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270-\355\205\234\355\224\214\353\246\277.md" "b/.github/ISSUE_TEMPLATE/\360\237\220\236-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270-\355\205\234\355\224\214\353\246\277.md" index e668ddcc..286df978 100644 --- "a/.github/ISSUE_TEMPLATE/\360\237\220\236-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270-\355\205\234\355\224\214\353\246\277.md" +++ "b/.github/ISSUE_TEMPLATE/\360\237\220\236-\353\262\204\352\267\270-\353\246\254\355\217\254\355\212\270-\355\205\234\355\224\214\353\246\277.md" @@ -3,20 +3,23 @@ name: "\U0001F41E 버그 리포트 템플릿" about: 프로젝트에서 발생하는 버그에 대해 명세합니다. title: "[Bug] Issue title" labels: "\U0001F41E BugFix" -assignees: '' - +assignees: "" --- ## 💬 버그 설명 + 문제가 생긴 상황을 간단히 설명해주세요. ## 🔁 재현 방법 + 문제가 어떻게 발생했는지 순서대로 작성해주세요. ## ⚙️ 기대 동작 + 정상적으로 어떤 동작을 기대했는지 설명해주세요. ## 📸 스크린샷 (선택사항) ## 📄 추가 정보 + 기타 참고할만한 정보나 자료가 있다면 자유롭게 작성해주세요. diff --git a/.storybook/main.ts b/.storybook/main.ts index aaa61f6f..c53b1e01 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,4 +1,5 @@ import type { StorybookConfig } from "@storybook/nextjs"; + const config: StorybookConfig = { stories: ["../src/**/*.stories.@(js|jsx|mdc|ts|tsx)"], addons: ["@storybook/addon-a11y", "@storybook/addon-docs"], @@ -11,6 +12,7 @@ const config: StorybookConfig = { }, }, }, + staticDirs: ["../public"], typescript: { check: false, reactDocgen: "react-docgen-typescript", @@ -36,4 +38,5 @@ const config: StorybookConfig = { return config; }, }; + export default config; diff --git a/public/images/aroma/aroma-apple.jpg b/public/images/aroma/aroma-apple.jpg new file mode 100644 index 00000000..27d4cc35 Binary files /dev/null and b/public/images/aroma/aroma-apple.jpg differ diff --git a/public/images/aroma/aroma-berry.jpg b/public/images/aroma/aroma-berry.jpg new file mode 100644 index 00000000..1cf6eff4 Binary files /dev/null and b/public/images/aroma/aroma-berry.jpg differ diff --git a/public/images/aroma/aroma-cherry.jpg b/public/images/aroma/aroma-cherry.jpg new file mode 100644 index 00000000..5ef1bf5b Binary files /dev/null and b/public/images/aroma/aroma-cherry.jpg differ diff --git a/public/images/aroma/aroma-chocolate.jpg b/public/images/aroma/aroma-chocolate.jpg new file mode 100644 index 00000000..2c6cb53b Binary files /dev/null and b/public/images/aroma/aroma-chocolate.jpg differ diff --git a/public/images/aroma/aroma-citrus.jpg b/public/images/aroma/aroma-citrus.jpg new file mode 100644 index 00000000..add34b7e Binary files /dev/null and b/public/images/aroma/aroma-citrus.jpg differ diff --git a/public/images/aroma/aroma-coconut.jpg b/public/images/aroma/aroma-coconut.jpg new file mode 100644 index 00000000..b0f81bd9 Binary files /dev/null and b/public/images/aroma/aroma-coconut.jpg differ diff --git a/public/images/aroma/aroma-flower.jpg b/public/images/aroma/aroma-flower.jpg new file mode 100644 index 00000000..f9f4dd65 Binary files /dev/null and b/public/images/aroma/aroma-flower.jpg differ diff --git a/public/images/aroma/aroma-grass.jpg b/public/images/aroma/aroma-grass.jpg new file mode 100644 index 00000000..a44d1675 Binary files /dev/null and b/public/images/aroma/aroma-grass.jpg differ diff --git a/public/images/aroma/aroma-herb.jpg b/public/images/aroma/aroma-herb.jpg new file mode 100644 index 00000000..dc1ffb9d Binary files /dev/null and b/public/images/aroma/aroma-herb.jpg differ diff --git a/public/images/aroma/aroma-mineral.jpg b/public/images/aroma/aroma-mineral.jpg new file mode 100644 index 00000000..1c37a0c8 Binary files /dev/null and b/public/images/aroma/aroma-mineral.jpg differ diff --git a/public/images/aroma/aroma-no-image.jpg b/public/images/aroma/aroma-no-image.jpg new file mode 100644 index 00000000..d109a3b4 Binary files /dev/null and b/public/images/aroma/aroma-no-image.jpg differ diff --git a/public/images/aroma/aroma-oak-cask.jpg b/public/images/aroma/aroma-oak-cask.jpg new file mode 100644 index 00000000..7922cef1 Binary files /dev/null and b/public/images/aroma/aroma-oak-cask.jpg differ diff --git a/public/images/aroma/aroma-peach.jpg b/public/images/aroma/aroma-peach.jpg new file mode 100644 index 00000000..a8c9971b Binary files /dev/null and b/public/images/aroma/aroma-peach.jpg differ diff --git a/public/images/aroma/aroma-toast.jpg b/public/images/aroma/aroma-toast.jpg new file mode 100644 index 00000000..a748d4ff Binary files /dev/null and b/public/images/aroma/aroma-toast.jpg differ diff --git a/public/images/aroma/aroma-tropical.jpg b/public/images/aroma/aroma-tropical.jpg new file mode 100644 index 00000000..cf0a58d4 Binary files /dev/null and b/public/images/aroma/aroma-tropical.jpg differ diff --git a/public/images/aroma/aroma-wet-soil.jpg b/public/images/aroma/aroma-wet-soil.jpg new file mode 100644 index 00000000..4d2e1ae4 Binary files /dev/null and b/public/images/aroma/aroma-wet-soil.jpg differ diff --git a/public/images/test/test_chip.jpg b/public/images/test/test_chip.jpg new file mode 100644 index 00000000..566c46f7 Binary files /dev/null and b/public/images/test/test_chip.jpg differ diff --git a/src/app/example/page.tsx b/src/app/example/page.tsx index 89ae8e82..757d267d 100644 --- a/src/app/example/page.tsx +++ b/src/app/example/page.tsx @@ -1,7 +1,6 @@ "use client"; -import { SelectType } from "@/components"; -import Header from "@/components/header/Header"; +import { Chip, DropdownMenu, Flavor, Header, SelectType } from "@/components"; import React, { ChangeEvent } from "react"; const Page = () => { @@ -9,13 +8,69 @@ const Page = () => { console.log(e.target.value); }; return ( -
+
+
+ console.log("로그아웃") }, + ]} + /> + console.log("임시") }, + ]} + /> + console.log("수정하기") }, + { label: "삭제하기", onClick: () => console.log("삭제하기") }, + ]} + /> + console.log("버튼클릭") }, + ]} + /> +
+
+ + +
+
+ +
diff --git a/src/app/globals.css b/src/app/globals.css index b758fb7d..ecf1d3fd 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -51,3 +51,13 @@ body { /* stroke: currentColor; */ } } + +/* 스크롤바 숨기기 */ +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; +} diff --git a/src/components/chip/Chip.stories.tsx b/src/components/chip/Chip.stories.tsx new file mode 100644 index 00000000..bc211433 --- /dev/null +++ b/src/components/chip/Chip.stories.tsx @@ -0,0 +1,41 @@ +import { Meta, StoryObj } from "@storybook/nextjs"; +import Chip from "./Chip"; + +const meta: Meta = { + title: "Components/Chip", + parameters: { + layout: "centered", + }, + component: Chip, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: "후추", + }, +}; + +export const WithImage: Story = { + args: { + label: "후추", + img: "/images/test/test_chip.jpg", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; diff --git a/src/components/chip/Chip.tsx b/src/components/chip/Chip.tsx new file mode 100644 index 00000000..02a5d545 --- /dev/null +++ b/src/components/chip/Chip.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import Image from "next/image"; +import { useState } from "react"; + +interface ChipProps { + img?: string; + label: string; +} + +const Chip = ({ img, label }: ChipProps) => { + const [selected, setSelected] = useState(false); + + return ( + + ); +}; + +export default Chip; diff --git a/src/components/dropdown-menu/DropdownMenu.stories.tsx b/src/components/dropdown-menu/DropdownMenu.stories.tsx new file mode 100644 index 00000000..80822b97 --- /dev/null +++ b/src/components/dropdown-menu/DropdownMenu.stories.tsx @@ -0,0 +1,37 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import DropdownMenu from "./DropdownMenu"; + +const meta = { + title: "Components/DropdownMenu", + component: DropdownMenu, + tags: ["autodocs"], + parameters: { + layout: "centered", + }, + argTypes: { + items: { control: "object" }, + }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + items: [ + { label: "마이페이지", href: "/my-page" }, + { label: "로그아웃", onClick: () => console.log("로그아웃") }, + ], + }, +}; + +export const EditDelete: Story = { + parameters: { nextjs: { navigation: { pathname: "/write" } } }, + args: { + items: [ + { label: "수정하기", onClick: () => console.log("수정하기") }, + { label: "삭제하기", onClick: () => console.log("삭제하기") }, + ], + }, +}; diff --git a/src/components/dropdown-menu/DropdownMenu.tsx b/src/components/dropdown-menu/DropdownMenu.tsx new file mode 100644 index 00000000..d78ce962 --- /dev/null +++ b/src/components/dropdown-menu/DropdownMenu.tsx @@ -0,0 +1,52 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { cn } from "@/lib/utils"; + +interface DropdownMenuProps { + items: { label: string; href?: string; onClick?: () => void }[]; +} + +const style = { + itemBase: + "flex-center h-full w-full rounded-md text-[16px] leading-[26px] text-gray-800 transition-colors hover:bg-gray-200", +}; + +function DropdownMenu({ items }: DropdownMenuProps) { + const pathname = usePathname(); + + const isActive = (href?: string) => Boolean(href && pathname === href); + + return ( +
+ {items.map(({ label, href, onClick }) => + href ? ( + + {label} + + ) : ( + + ) + )} +
+ ); +} + +export default DropdownMenu; diff --git a/src/components/flavor/Flavor.stories.tsx b/src/components/flavor/Flavor.stories.tsx new file mode 100644 index 00000000..f3c531be --- /dev/null +++ b/src/components/flavor/Flavor.stories.tsx @@ -0,0 +1,45 @@ +import { Meta, StoryObj } from "@storybook/nextjs"; +import Flavor from "./Flavor"; +import { AromaKey } from "@/types/AromaType"; + +const meta: Meta = { + title: "Components/Flavor", + component: Flavor, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], + argTypes: { + count: { control: "number", description: "참여 인원 수 표시" }, + items: { + control: "object", + description: "향 아이템 키 목록 (AromaKey[])", + }, + }, +}; + +export default meta; +type Story = StoryObj; + +const ITEMS: AromaKey[] = ["CHERRY", "PEPPER", "EMPTY"]; + +export const Default: Story = { + args: { + count: 12, + items: ITEMS.slice(0, 1), + }, +}; + +export const ThreeItems: Story = { + args: { + count: 3, + items: ITEMS, + }, +}; + +export const Empty: Story = { + args: { + count: 0, + items: [], + }, +}; diff --git a/src/components/flavor/Flavor.tsx b/src/components/flavor/Flavor.tsx new file mode 100644 index 00000000..8b6c1d9a --- /dev/null +++ b/src/components/flavor/Flavor.tsx @@ -0,0 +1,53 @@ +import { AromaKey } from "@/types/AromaType"; +import Image from "next/image"; +import { aromaMap } from "./aroma-map"; + +interface FlavorItemProps { + aroma: AromaKey; +} + +const FlavorItem = ({ aroma }: FlavorItemProps) => { + const { img, label } = aromaMap[aroma]; + return ( +
+ {label} + {label} +
+ ); +}; + +interface FlavorProps { + count: number; + items: AromaKey[]; +} + +const Flavor = ({ count, items }: FlavorProps) => { + return ( +
+
+

+ 어떤 향이 있나요? +

+ + ({count}명 참여) + +
+ +
+
+ {items.map((item, index) => ( + + ))} +
+
+
+ ); +}; + +export default Flavor; diff --git a/src/components/flavor/aroma-map.ts b/src/components/flavor/aroma-map.ts new file mode 100644 index 00000000..4e52bfeb --- /dev/null +++ b/src/components/flavor/aroma-map.ts @@ -0,0 +1,94 @@ +import { AromaKey } from "@/types/AromaType"; + +type AromaInfo = { + img: string; + label: string; +}; + +export const aromaMap: Record = { + CHERRY: { + img: "/images/aroma/aroma-cherry.jpg", + label: "체리", + }, + BERRY: { + img: "/images/aroma/aroma-berry.jpg", + label: "베리", + }, + OAK: { + img: "/images/aroma/aroma-oak-cask.jpg", + label: "오크", + }, + VANILLA: { + // 이미지 없음 + img: "/images/aroma/aroma-no-image.jpg", + label: "바닐라", + }, + PEPPER: { + img: "/images/aroma/aroma-herb.jpg", + label: "후추", + }, + BAKING: { + img: "/images/aroma/aroma-toast.jpg", + label: "베이킹", + }, + GRASS: { + img: "/images/aroma/aroma-grass.jpg", + label: "풀", + }, + APPLE: { + img: "/images/aroma/aroma-apple.jpg", + label: "사과", + }, + PEACH: { + img: "/images/aroma/aroma-peach.jpg", + label: "복숭아", + }, + CITRUS: { + img: "/images/aroma/aroma-citrus.jpg", + label: "시트러스", + }, + TROPICAL: { + img: "/images/aroma/aroma-coconut.jpg", + label: "트로피컬", + }, + MINERAL: { + img: "/images/aroma/aroma-mineral.jpg", + label: "미네랄", + }, + FLOWER: { + img: "/images/aroma/aroma-flower.jpg", + label: "꽃", + }, + TOBACCO: { + // 이미지 없음 + img: "/images/aroma/aroma-no-image.jpg", + label: "담배", + }, + EARTH: { + img: "/images/aroma/aroma-wet-soil.jpg", + label: "흙", + }, + CHOCOLATE: { + img: "/images/aroma/aroma-chocolate.jpg", + label: "초콜릿", + }, + SPICE: { + // 이미지 없음 + img: "/images/aroma/aroma-no-image.jpg", + label: "향신료", + }, + CARAMEL: { + // 이미지 없음 + img: "/images/aroma/aroma-no-image.jpg", + label: "카라멜", + }, + LEATHER: { + // 이미지 없음 + img: "/images/aroma/aroma-no-image.jpg", + label: "가죽", + }, + EMPTY: { + img: "/images/aroma/aroma-no-image.jpg", + label: "-", + }, +}; diff --git a/src/components/header/Header.stories.tsx b/src/components/header/Header.stories.tsx index 07e39816..f8cda29c 100644 --- a/src/components/header/Header.stories.tsx +++ b/src/components/header/Header.stories.tsx @@ -29,7 +29,7 @@ export const Default: Story = { }; export const ManyReviews: Story = { - name: "리뷰가 많은 경우", + name: "Many Reviews ", args: { review: "12,345", title: "도멘 드 라 로마네 콩티", @@ -39,7 +39,7 @@ export const ManyReviews: Story = { }; export const LongText: Story = { - name: "제목/설명이 긴 경우", + name: "Long Text ", args: { review: "87", title: diff --git a/src/components/index.ts b/src/components/index.ts index fbe8e787..43059486 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,5 +1,8 @@ export { default as Gnb } from "./gnb/Gnb"; export { default as Header } from "./header/Header"; +export { default as DropdownMenu } from "./dropdown-menu/DropdownMenu"; +export { default as Chip } from "./chip/Chip"; +export { default as Flavor } from "./flavor/Flavor"; export { TextInput, ModalTextInput } from "./text-input/text-input"; export { default as SelectType } from "./select-type/select-type"; export { default as Icon } from "./icon/Icon"; diff --git a/src/types/AromaType.ts b/src/types/AromaType.ts new file mode 100644 index 00000000..dfcd4be4 --- /dev/null +++ b/src/types/AromaType.ts @@ -0,0 +1,21 @@ +export type AromaKey = + | "CHERRY" + | "BERRY" + | "OAK" + | "VANILLA" + | "PEPPER" + | "BAKING" + | "GRASS" + | "APPLE" + | "PEACH" + | "CITRUS" + | "TROPICAL" + | "MINERAL" + | "FLOWER" + | "TOBACCO" + | "EARTH" + | "CHOCOLATE" + | "SPICE" + | "CARAMEL" + | "LEATHER" + | "EMPTY"; diff --git a/tailwind.config.ts b/tailwind.config.ts index ace6cba2..7c53b3ef 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -13,6 +13,14 @@ export default { colors: { black: "#1A1918", white: "#FFFFFF", + gray100: "#FAFAFA", + gray300: "#D1D1D1", + gray600: "#8C8C8B", + primary: "#1A1918", + default: "#31302F", + secondary: "#A3A3A3", + neutral200: "#F2F2F2", + neutral400: "#BABABA", gray: { 100: "#fafafa", 150: "#f7f7f7",