[TECH-QA] class-variance-authority ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

CVA๋Š” JavaScript/TypeScript ๊ธฐ๋ฐ˜์˜ ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, CSS ํด๋ž˜์Šค ์ด๋ฆ„์„ ์กฐ๊ฑด์— ๋”ฐ๋ผ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ์Šคํƒ€์ผ ๋ณ€ํ˜•(variants)์„ ์‰ฝ๊ฒŒ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ํŠนํžˆ Tailwind CSS์™€ ๊ฐ™์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํผ์ŠคํŠธ CSS ํ”„๋ ˆ์ž„์›Œํฌ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋  ๋•Œ ์œ ์šฉํ•˜๋ฉฐ, React, Vue, Angular ๋“ฑ ๋‹ค์–‘ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ํ˜ธํ™˜๋ฉ๋‹ˆ๋‹ค.
CVA๋Š” ์ปดํฌ๋„ŒํŠธ์˜ ์Šคํƒ€์ผ ๋ณ€ํ˜•(์˜ˆ: ๋ฒ„ํŠผ์˜ ํฌ๊ธฐ, ์ƒ‰์ƒ, ์ƒํƒœ ๋“ฑ)์„ ํƒ€์ž… ์•ˆ์ „ํ•˜๊ฒŒ ์ •์˜ํ•˜๊ณ , ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ด๋Š” ๋ฐ ์ดˆ์ ์„ ๋งž์ถ˜ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ์˜ "primary", "secondary" ๋ณ€ํ˜•์ด๋‚˜ "small", "large" ํฌ๊ธฐ๋ฅผ ๊ฐ„๋‹จํžˆ ์ •์˜ํ•˜๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์„ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.cva.style ๊ฐ„๋‹จํžˆ ๋งํ•ด, CVA๋Š” UI ์ปดํฌ๋„ŒํŠธ์˜ ์Šคํƒ€์ผ๋ง์„ ๋” ๊ตฌ์กฐ์ ์ด๊ณ  ์„ ์–ธ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค.
TypeScript์™€ Tailwind CSS๋ฅผ ์‚ฌ์šฉํ•ด ๋ฒ„ํŠผ ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ๋ณ€ํ˜•์„ ์ •์˜ํ•˜๋Š” ์ฝ”๋“œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค.
import { cva } from 'class-variance-authority';

// CVA๋กœ ๋ฒ„ํŠผ ์Šคํƒ€์ผ ๋ณ€ํ˜• ์ •์˜
const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md font-medium', // ๊ธฐ๋ณธ ์Šคํƒ€์ผ
  {
   variants: {
       intent: {
       primary: 'bg-blue-500 text-white hover:bg-blue-600',
       secondary: 'bg-gray-500 text-white hover:bg-gray-600',
       danger: 'bg-red-500 text-white hover:bg-red-600',
   },
   size: {
       small: 'px-2 py-1 text-sm',
       medium: 'px-4 py-2 text-base',
       large: 'px-6 py-3 text-lg',
      },
   },
   defaultVariants: {
   	intent: 'primary',
   	size: 'medium',
      },
   }
);

// React ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ
interface ButtonProps {
  intent?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  children: React.ReactNode;
}

const Button = ({ intent, size, children }: ButtonProps) => {
  return (
    <button className={buttonVariants({ intent, size })}>
      {children}
    </button>
  );
};

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const App = () => (
 <div>
   <Button intent="primary" size="small">Primary Small</Button>
   <Button intent="secondary" size="medium">Secondary Medium</Button>
   <Button intent="danger" size="large">Danger Large</Button>
   <Button>Default Button</Button> {/* ๊ธฐ๋ณธ๊ฐ’: primary, medium */}
 </div>
);

export default App;
  • cva ํ•จ์ˆ˜: ๊ธฐ๋ณธ ์Šคํƒ€์ผ๊ณผ ๋ณ€ํ˜•(variants)์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์‹œ์—์„œ๋Š” intent(๋ฒ„ํŠผ ์ƒ‰์ƒ)์™€ size(๋ฒ„ํŠผ ํฌ๊ธฐ)๋ฅผ ๋ณ€ํ˜•์œผ๋กœ ์„ค์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ๋ณ€ํ˜•(variants): intent์™€ size์— ๋”ฐ๋ผ Tailwind CSS ํด๋ž˜์Šค๋ฅผ ๋™์ ์œผ๋กœ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋ณธ๊ฐ’(defaultVariants): ๋ณ€ํ˜•์ด ์ง€์ •๋˜์ง€ ์•Š์„ ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ์šฉ๋  ์Šคํƒ€์ผ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ: Button ์ปดํฌ๋„ŒํŠธ๋Š” buttonVariants๋ฅผ ํ˜ธ์ถœํ•ด ๋™์ ์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์„ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ
CVA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํƒ€์ผ ๋ณ€ํ˜•์„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ , ํƒ€์ž… ์•ˆ์ „์„ฑ(TypeScript)์„ ๋ณด์žฅํ•˜์—ฌ ๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.