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" deleted file mode 100644 index 286df978..00000000 --- "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" +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: "\U0001F41E 버그 리포트 템플릿" -about: 프로젝트에서 발생하는 버그에 대해 명세합니다. -title: "[Bug] Issue title" -labels: "\U0001F41E BugFix" -assignees: "" ---- - -## 💬 버그 설명 - -문제가 생긴 상황을 간단히 설명해주세요. - -## 🔁 재현 방법 - -문제가 어떻게 발생했는지 순서대로 작성해주세요. - -## ⚙️ 기대 동작 - -정상적으로 어떤 동작을 기대했는지 설명해주세요. - -## 📸 스크린샷 (선택사항) - -## 📄 추가 정보 - -기타 참고할만한 정보나 자료가 있다면 자유롭게 작성해주세요. diff --git a/src/components/button/Button.stories.tsx b/src/components/button/Button.stories.tsx new file mode 100644 index 00000000..40ac5dbe --- /dev/null +++ b/src/components/button/Button.stories.tsx @@ -0,0 +1,133 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import Button from "./basic-button"; +import IconButton from "./icon-button"; +import ArrowButton from "./arrow-button"; +import ICON_MAP from "../icon/icon-map"; + +const meta: Meta = { + title: "Components/Button", + component: Button, + parameters: { + layout: "centered", + docs: { + description: { + component: "공통으로 사용되는 버튼 컴포넌트입니다.", + }, + }, + }, + tags: ["autodocs"], + argTypes: { + type: { + control: "select", + options: ["default", "outline", "secondary"], + description: "버튼 상태", + }, + icon: { + control: "select", + options: Object.keys(ICON_MAP), + description: "버튼 아이콘", + }, + + className: { + control: "text", + description: "추가 CSS 클래스", + }, + shape: { + control: "text", + description: "-", + table: { + category: "X", + }, + }, + label: { + control: "text", + description: "버튼에 들어가는 텍스트", + }, + textColor: { + description: "-", + table: { + category: "X", + }, + }, + disabled: { + control: "boolean", + description: "비활성화 여부", + table: { + category: "X", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + appearance: "default", + label: "버튼 이름을 입력해보세요", + }, +}; + +export const Outline: Story = { + args: { + appearance: "outline", + label: "버튼 이름을 입력해보세요", + }, +}; + +export const Icon: Story = { + render: () => ( +
+ + + +
+ ), +}; + +export const Arrow: Story = { + render: () => ( +
+ + +
+ ), +}; + +export const Disabled: Story = { + args: { + appearance: "default", + label: "비활성 버튼", + disabled: true, + }, +}; + +export const Variations: Story = { + render: () => ( +
+

대표 버튼

+ +
+
+ +

아이콘이 들어간 버튼

+
+
+
+ ), + parameters: { + layout: "fullscreen", + }, +}; diff --git a/src/components/button/arrow-button.tsx b/src/components/button/arrow-button.tsx new file mode 100644 index 00000000..ca4ab542 --- /dev/null +++ b/src/components/button/arrow-button.tsx @@ -0,0 +1,34 @@ +import type { ButtonHTMLAttributes } from "react"; +import { cn } from "@/lib/utils"; +import Icon from "../icon/icon"; +import { + COMMON_BUTTON_STYLES, + BUTTON_SHAPE_VARIANTS, + BUTTON_STATE_VARIANTS, +} from "./style"; + +interface ArrowButtonProps extends ButtonHTMLAttributes { + direction: "prev" | "next"; + className?: string; +} + +const ArrowButton = ({ direction, className, ...props }: ArrowButtonProps) => { + const iconName = direction === "prev" ? "ArrowLeftIcon" : "ArrowRightIcon"; + const ariaLabel = direction === "prev" ? "이전으로 이동" : "다음으로 이동"; + return ( + + ); +}; + +export default ArrowButton; diff --git a/src/components/button/basic-button.tsx b/src/components/button/basic-button.tsx new file mode 100644 index 00000000..f9863803 --- /dev/null +++ b/src/components/button/basic-button.tsx @@ -0,0 +1,50 @@ +import type { ButtonHTMLAttributes } from "react"; +import { cn } from "@/lib/utils"; +import { + COMMON_BUTTON_STYLES, + BUTTON_SHAPE_VARIANTS, + BUTTON_STATE_VARIANTS, + BUTTON_TEXT_COLOR_VARIANTS, + ButtonShape, + ButtonState, + ButtonTextColor, +} from "./style"; +import Icon from "../icon/icon"; +import type { IconName } from "../icon/icon-map"; + +interface ButtonProps extends ButtonHTMLAttributes { + icon?: IconName; + appearance?: ButtonState; + label?: string; + shape?: ButtonShape; + textColor?: ButtonTextColor; + className?: string; + children?: React.ReactNode; +} + +const Button = ({ + appearance = "default", + icon, + label, + className, + children, + ...props +}: ButtonProps) => { + return ( + + ); +}; + +export default Button; diff --git a/src/components/button/icon-button.tsx b/src/components/button/icon-button.tsx new file mode 100644 index 00000000..12aa759c --- /dev/null +++ b/src/components/button/icon-button.tsx @@ -0,0 +1,34 @@ +import type { ButtonHTMLAttributes } from "react"; +import { cn } from "@/lib/utils"; +import Icon from "../icon/icon"; +import type { IconName } from "../icon/icon-map"; + +import { + COMMON_BUTTON_STYLES, + BUTTON_SHAPE_VARIANTS, + BUTTON_STATE_VARIANTS, +} from "./style"; + +interface IconButtonProps extends ButtonHTMLAttributes { + icon: IconName; + className?: string; + "aria-label": string; //접근성을 위해 아이콘 버튼 사용시 필수적으로 넣어주세요 +} + +const IconButton = ({ icon, className, ...props }: IconButtonProps) => { + return ( + + ); +}; + +export default IconButton; diff --git a/src/components/button/style.ts b/src/components/button/style.ts new file mode 100644 index 00000000..73152aef --- /dev/null +++ b/src/components/button/style.ts @@ -0,0 +1,30 @@ +export type ButtonShape = "default" | "round" | "square"; +export type ButtonState = "default" | "outline" | "secondary"; +export type ButtonTextColor = "default" | "outline" | "secondary"; + +export const COMMON_BUTTON_STYLES = + "group w-full h-[42px] tablet:h-[50px] pc:h-[50px] inline-flex flex-center gap-x-[8px] tablet:gap-x-[12px] pc:gap-x-[12px] whitespace-nowrap transition-colors disabled:pointer-events-none"; + +export const BUTTON_SHAPE_VARIANTS = { + default: "rounded-[4px] px-[23px]", + round: "rounded-full w-[48px] h-[48px] active:text-gray-100", + square: + "rounded-[8px] w-[42px] tablet:w-[50px] pc:w-[50px] active:text-gray-100", +}; + +export const BUTTON_STATE_VARIANTS = { + default: + "bg-black hover:bg-default active:bg-gray-800 disabled:bg-gray-300 disabled:text-gray-600", + outline: + "bg-white border border-gray-300 hover:bg-gray-100 active:bg-gray-200 disabled:border-gray-300", + secondary: + "bg-white border border-gray-200 hover:bg-gray-100 active:bg-gray-600 disabled:border-gray-300 disabled:text-gray-400", +}; + +export const BUTTON_TEXT_COLOR_VARIANTS = { + default: + "text-gray-100 mobile:text-button-md tablet:text-button-lg pc:text-button-lg group-disabled:text-gray-600 group-disabled:bg-gray-300", + outline: + "text-default mobile:text-button-md tablet:text-button-lg pc:text-button-lg group-disabled:text-gray-400", + secondary: "text-gray-800 group-disabled:text-gray-400", +}; diff --git a/src/components/icon/icon-map.ts b/src/components/icon/icon-map.ts index 6c3b4584..8ff4fa5b 100644 --- a/src/components/icon/icon-map.ts +++ b/src/components/icon/icon-map.ts @@ -39,4 +39,5 @@ const ICON_MAP = { VanillaIcon: () => import("/public/icons/flavor/ic-vanilla.svg"), }; +export type IconName = keyof typeof ICON_MAP; export default ICON_MAP; diff --git a/src/components/index.ts b/src/components/index.ts index b0a3602a..647de881 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,3 +9,6 @@ export { default as Icon } from "./icon/icon"; export { default as BlockGauge } from "./gauge/block-gauge"; export { default as DetailTaste } from "./taste/detail-taste"; export { default as ReviewTaste } from "./taste/review-taste"; +export { default as Button } from "./button/basic-button"; +export { default as ArrowButton } from "./button/arrow-button"; +export { default as IconButton } from "./button/icon-button"; diff --git a/src/components/select-type/select-type.tsx b/src/components/select-type/select-type.tsx index 1be83f09..2dcbb910 100644 --- a/src/components/select-type/select-type.tsx +++ b/src/components/select-type/select-type.tsx @@ -68,7 +68,7 @@ const SelectType = ({ isError, ...props }: SelectTypeValue) => { return (
-

타입

+

타입

{isError && (

와인 타입은 필수 입력이에요