diff --git a/package-lock.json b/package-lock.json index 4ba4974..a735708 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@hookform/resolvers": "^3.10.0", "@tanstack/react-query": "^5.65.1", "axios": "^1.7.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "es-toolkit": "^1.32.0", "lenis": "^1.1.20", "lottie-react": "^2.4.1", @@ -20,6 +22,7 @@ "react-datepicker": "^7.6.0", "react-dom": "^19.0.0", "react-hook-form": "^7.54.2", + "tailwind-merge": "^3.0.1", "zod": "^3.24.1", "zustand": "^5.0.3" }, @@ -1702,6 +1705,18 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -1711,6 +1726,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5450,6 +5466,16 @@ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, + "node_modules/tailwind-merge": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz", + "integrity": "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.4.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", diff --git a/package.json b/package.json index dd7092c..2ca3bd8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "@hookform/resolvers": "^3.10.0", "@tanstack/react-query": "^5.65.1", "axios": "^1.7.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "es-toolkit": "^1.32.0", "lenis": "^1.1.20", "lottie-react": "^2.4.1", @@ -22,6 +24,7 @@ "react-datepicker": "^7.6.0", "react-dom": "^19.0.0", "react-hook-form": "^7.54.2", + "tailwind-merge": "^3.0.1", "zod": "^3.24.1", "zustand": "^5.0.3" }, diff --git a/src/components/ui/Button/Button.tsx b/src/components/ui/Button/Button.tsx new file mode 100644 index 0000000..401a49f --- /dev/null +++ b/src/components/ui/Button/Button.tsx @@ -0,0 +1,74 @@ +import { ButtonHTMLAttributes } from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '@/utils/helper'; + +/** + * 커스터마이징 가능한 기본 공용 Button 컴포넌트입니다. + * + * ### Variants: + * - `variant`: + * - `default`: 보라색 배경과 흰색 텍스트. + * - `outline`: 흰색 배경과 보라색 텍스트, 회색 테두리. + * + * - `size`: + * - `default`: (PC 기준) : height:48px, font-size:16px + * (Mobile 기준) : height:42px, font-size:14px + * + * - `sm`: (PC 기준) : height:32px, font-size:14px; + * (Mobile 기준) : height:32px, font-size:12px; + * + * 단독으로 사용 시 최소 너비 84px을 가지며, + * flex 컨테이너 안에서 사용되면 `flex-1`로 늘어나고, 컨테이너가 전체너비를 통해서 컨트롤. + * + * - `lg`: height:48px, font-size:16px + * + * ### Compound Variants (조합된 스타일): + * - `size: lg` + `variant: outline`: 텍스트 색상이 회색으로 변경. + * + * ### Default Variants (기본 스타일): + * - `variant`: `default` + * - `size`: `default` + * + * + * @example + * + */ + +const buttonVariants = cva( + //prettier-ignore + [ + 'inline-flex items-center justify-center gap-2 flex-1', + 'whitespace-nowrap font-semibold', + 'rounded-[4px] px-4 py-2', + 'disabled:cursor-not-allowed', + ].join(' '), + { + variants: { + variant: { + default: 'bg-violet-20 text-white disabled:bg-gray-40', + outline: 'bg-white text-violet-20 border border-gray-30 disabled:opacity-50', + }, + size: { + default: 'h-[42px] text-md md:text-lg md:h-12', + sm: 'h-8 text-xs md:text-md w-auto min-w-[84px]', + lg: 'h-[54px] text-lg rounded-lg', + }, + }, + compoundVariants: [ + { + size: 'lg', + variant: 'outline', + className: 'text-gray-50', + }, + ], + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +); +interface ButtonProps extends ButtonHTMLAttributes, VariantProps {} + +export default function Button({ variant, size, className, ...props }: ButtonProps) { + return