-
Notifications
You must be signed in to change notification settings - Fork 2
[#3] 공통 컴포넌트 개발 #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#3] 공통 컴포넌트 개발 #21
Changes from 8 commits
3ba8db1
2839565
e120d0a
8b56dcd
ead6177
2d5b579
29bdf7d
274202d
4dc9fe3
b0e3800
70beb2b
c975f08
81b15c8
86658d4
066140c
743c9fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| --- | ||
| name: "\U0001F41E 버그 리포트 템플릿" | ||
| about: 프로젝트에서 발생하는 버그에 대해 명세합니다. | ||
| title: "[Bug] Issue title" | ||
| labels: "\U0001F41E BugFix" | ||
| assignees: "" | ||
| --- | ||
|
|
||
| ## 💬 버그 설명 | ||
|
|
||
| 문제가 생긴 상황을 간단히 설명해주세요. | ||
|
|
||
| ## 🔁 재현 방법 | ||
|
|
||
| 문제가 어떻게 발생했는지 순서대로 작성해주세요. | ||
|
|
||
| ## ⚙️ 기대 동작 | ||
|
|
||
| 정상적으로 어떤 동작을 기대했는지 설명해주세요. | ||
|
|
||
| ## 📸 스크린샷 (선택사항) | ||
|
|
||
| ## 📄 추가 정보 | ||
|
|
||
| 기타 참고할만한 정보나 자료가 있다면 자유롭게 작성해주세요. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { Meta, StoryObj } from "@storybook/nextjs"; | ||
| import Chip from "./Chip"; | ||
|
|
||
| const meta: Meta<typeof Chip> = { | ||
| title: "Components/Chip", | ||
| parameters: { | ||
| layout: "centered", | ||
| }, | ||
| component: Chip, | ||
| tags: ["autodocs"], | ||
| decorators: [ | ||
| (Story) => ( | ||
| <div className="w-[66px]"> | ||
| <Story /> | ||
| </div> | ||
| ), | ||
| ], | ||
| }; | ||
|
|
||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
|
|
||
| export const Default: Story = { | ||
| args: { | ||
| label: "후추", | ||
| }, | ||
| }; | ||
|
|
||
| export const WithImage: Story = { | ||
| args: { | ||
| label: "후추", | ||
| img: "/images/test/test_chip.jpg", | ||
| }, | ||
| decorators: [ | ||
| (Story) => ( | ||
| <div className="w-[100px]"> | ||
| <Story /> | ||
| </div> | ||
| ), | ||
| ], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| "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 ( | ||
| <div | ||
| data-selected={selected} | ||
| className={cn( | ||
| "flex-center h-[38px] cursor-pointer rounded-full border text-body-sm tracking-[-0.02em]", | ||
| "transition-colors duration-150 ease-in-out hover:bg-gray100 hover:text-black", | ||
| "data-[selected=true]:bg-gray800 data-[selected=true]:text-white data-[selected=true]:hover:bg-gray800 data-[selected=true]:hover:text-white", | ||
| "pc:h-[48px] pc:text-body-md", | ||
| img | ||
| ? "gap-2 pl-2 pr-4 mobile:gap-[6px] mobile:pr-3" | ||
| : "px-[18px] mobile:px-3" | ||
| )} | ||
| onClick={() => setSelected(!selected)} | ||
| > | ||
| {img && ( | ||
| <Image | ||
| src={img} | ||
| alt={`${label} 이미지`} | ||
| width={32} | ||
| height={32} | ||
| className="h-8 w-8 rounded-full mobile:h-6 mobile:w-6" | ||
| /> | ||
| )} | ||
| <span>{label}</span> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Chip; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import type { Meta, StoryObj } from "@storybook/nextjs"; | ||
| import DropdownMenu from "./DropdownMenu"; | ||
|
|
||
| const meta: Meta<typeof DropdownMenu> = { | ||
| title: "Components/DropdownMenu", | ||
| component: DropdownMenu, | ||
| tags: ["autodocs"], | ||
| parameters: { | ||
| layout: "centered", | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| type Story = StoryObj<typeof DropdownMenu>; | ||
|
|
||
| export const Default: Story = { | ||
| args: {}, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| "use client"; | ||
|
|
||
| import Link from "next/link"; | ||
| import { usePathname } from "next/navigation"; | ||
| import { cn } from "@/lib/utils"; | ||
|
|
||
| const style = { | ||
| itemBase: | ||
| "flex-center h-full w-full rounded-md text-[16px] leading-[26px] text-gray800 transition-colors hover:bg-whiteHover active:bg-gray200", | ||
| }; | ||
|
|
||
| function DropdownMenu() { | ||
| const pathname = usePathname(); | ||
|
|
||
| const isActive = (href?: string) => Boolean(href && pathname === href); | ||
|
|
||
| const items: { label: string; href?: string; onClick?: () => void }[] = [ | ||
| { label: "마이페이지", href: "/my-page" }, | ||
| { label: "로그아웃", onClick: () => console.log("logout") }, | ||
| ]; | ||
|
||
|
|
||
| return ( | ||
| <div | ||
| className={cn( | ||
| "flex-col-center rounded-[4px] border border-gray300 p-[3px] shadow-md", | ||
| "h-[92px] w-[101px]", | ||
| "tablet:h-[90px] tablet:w-[126px]", | ||
| "pc:h-[90px] pc:w-[126px]" | ||
| )} | ||
| > | ||
| {items.map(({ label, href, onClick }) => | ||
| href ? ( | ||
| <Link | ||
| key={label} | ||
| href={href} | ||
| className={cn( | ||
| style.itemBase, | ||
| isActive(href) && "bg-gray800 text-white" | ||
| )} | ||
| > | ||
| {label} | ||
| </Link> | ||
| ) : ( | ||
| <button key={label} onClick={onClick} className={style.itemBase}> | ||
| {label} | ||
| </button> | ||
| ) | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default DropdownMenu; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { Meta, StoryObj } from "@storybook/nextjs"; | ||
| import Flavor from "./Flavor"; | ||
| import { AromaKey } from "@/types/AromaType"; | ||
|
|
||
| const meta: Meta<typeof Flavor> = { | ||
| 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<typeof Flavor>; | ||
|
|
||
| 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: [], | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <div className="flex shrink-0 flex-col items-center gap-[14px] tablet:w-[100px] pc:w-[100px]"> | ||
| <Image | ||
| src={img} | ||
| alt={label} | ||
| width={90} | ||
| height={90} | ||
| className="rounded-4 h-[90px] w-full tablet:h-[100px] pc:h-[100px]" | ||
| /> | ||
| <span className="text-body-md tracking-[-0.02em]">{label}</span> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| interface FlavorProps { | ||
| count: number; | ||
| items: AromaKey[]; | ||
| } | ||
|
|
||
| const Flavor = ({ count, items }: FlavorProps) => { | ||
| return ( | ||
| <div className="flex min-h-[197px] w-full flex-col items-start justify-between gap-[17px]"> | ||
| <div> | ||
| <h2 className="text-heading-lg tracking-[-0.48px] text-[#31302F]"> | ||
| 어떤 향이 있나요? | ||
| </h2> | ||
| <span className="text-body-sm tracking-[-0.28px] text-[#BABABA]"> | ||
| (<span>{count}</span>명 참여) | ||
| </span> | ||
| </div> | ||
|
|
||
| <div className="scrollbar-hide w-[calc(3*90px+2*14px+16px)] overflow-x-auto tablet:w-[calc(4*100px+2*14px+2*16px)] pc:w-[calc(4*100px+2*14px+2*16px)]"> | ||
| <div className="flex flex-nowrap gap-4"> | ||
| {items.map((item, index) => ( | ||
| <FlavorItem aroma={item} key={index} /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Flavor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 버튼 컴포넌트가 없어 임시로 작업해두신걸까요? 클릭 가능한 요소라면 접근성 개선을 위해
<button>태그로 개선해도 좋을 것 같습니다!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
감사합니다 반영했습니다!