-
Notifications
You must be signed in to change notification settings - Fork 7
๐ชต 6. ํจ์จ์ ์ธ UI ์ปดํฌ๋ํธ ์คํ์ผ๋ง: Tailwind CSS cn.ts
ํ๋ก ํธ์๋ ๊ฐ๋ฐ์์๋ ๋น์ฆ๋์ค ๋ก์ง๊ณผ UI๋ฅผ ๋ถ๋ฆฌํด ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ ์ ์ฐํ ์ปดํฌ๋ํธ๋ฅผ ์ค๊ณํ๋ ๋์์ UI ๋ถ๋ถ์์์ ์ผ๊ด๋ ๋์์ธ ์์คํ
์ ํจ์จ์ ์ผ๋ก ๊ตฌํํ๊ณ ๊ด๋ฆฌํ๋ ๊ฒ์ด ํต์ฌ ๊ณผ์ ๊ฐ ๋ ์ ์์ต๋๋ค. ๊ทธ๋์ ์ข์ ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ? + Headless Pattern
์์ ์ดํด๋ณธ ๊ฒ์ฒ๋ผ Headless ํจํด์ ๋ฐ๋ฅด๋ ๊ฒฝ์ฐ๊ฐ ๋๋ฌ ์์ต๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ๋ ๋์ ์ธ UI๋ฅผ ๊ตฌํํ๊ธฐ ์ํด Emotion ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋๊ฒ ์ผ๋ฐ์ ์ด์ง๋ง, ์ฐ๋ฆฌ๋ ๋น ๋ฅธ ํผ๋ธ๋ฆฌ์ฑ ๊ตฌ์ถ์ ์ํด Tailwind CSS๋ฅผ ์ ํํ์ต๋๋ค.
Tailwind CSS๋ ์ ํธ๋ฆฌํฐ ์ ์ฉ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ CSS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก์ Zero Runtime + ๋ฌธ์์ด ๊ธฐ๋ฐ์ ํด๋์ค ์กฐ์ ๊ธฐ๋ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ฌ์ ์ฑ๋ฅ์ด ๊ฐ๋ ฅํ๋ฉด์ ํธํ๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค.
ํ์ง๋ง ํ ์ผ์๋ ํน์ฑ์ ๋ถ๋ฆฌ ๊ฐ๋ฅํ๊ณ , ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ UI ์ปดํฌ๋ํธ์ ์คํ์ผ์ ์ ์ฉํ ๋, ์๋์ ๊ฐ์ ์ ๋ก์ฌํญ์ด ์์ต๋๋ค.
- ์กฐ๊ฑด๋ถ ํด๋์ค ์ ์ฉ์ ์ด๋ ค์
- ํด๋์ค ์ถฉ๋ ๋ฌธ์
- ํด๋์ค๋ช ๋ณํฉ์ ๋ณต์ก์ฑ
์ฆ, ํ
์ผ์๋๋ ๋์ ์ผ๋ก ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค๊ธฐ ์ด๋ ต์ต๋๋ค.(๋ฌผ๋ก ๋ณต์กํ๊ฒ join
, filter
๋ฑ์ ์ฐ๋ฉด ๊ฐ๋ฅํ์ง๋ง์.)
์ด๋ฌํ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ณ ๋ ํจ์จ์ ์ธ UI ์ปดํฌ๋ํธ ๊ฐ๋ฐ์ ์ํด, ์ฐ๋ฆฌ๋ ๋ช ๊ฐ์ง ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ์กฐํฉํ cn.ts
์ ํธ๋ฆฌํฐ๋ฅผ ๊ตฌ์ถํ์ต๋๋ค.
์กฐ๊ฑด๋ถ ํด๋์ค๋ฅผ ์ง๊ด์ ์ผ๋ก ๊ฒฐํฉํ ์ ์๊ฒ ํด์ฃผ๋ ์์ ์ ํธ๋ฆฌํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
clsx('base-class', isActive && 'active-class')
// -> isActive๊ฐ true๋ฉด 'base-class active-class'
// -> false๋ฉด 'base-class'
Tailwind ํด๋์ค ๊ฐ์ ์ถฉ๋์ CSS์ค๋ฝ๊ฒ ํด๊ฒฐํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
twMerge('px-2 py-1', 'p-3') // -> 'p-3'
twMerge('text-red-500', 'text-blue-500') // -> 'text-blue-500'
์ปดํฌ๋ํธ์ variant๋ณ ํด๋์ค๋ฅผ ํ์ ์์ ํ๊ฒ ๊ด๋ฆฌํ ์ ์๊ฒ ํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
const buttonVariants = cva('base-button-styles', {
variants: {
variant: {
primary: 'bg-blue-500',
secondary: 'bg-gray-500'
},
size: {
sm: 'text-sm',
lg: 'text-lg'
}
}
});
์ด ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ cn.ts
์ ํธ๋ฆฌํฐ๋ก ํตํฉํด ์ฌ์ฉํฉ๋๋ค.
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/utils/cn';
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
proimary: 'bg-violet-500 border-2 border-violet-950 hover:bg-violet-600',
secondary: 'bg-eastbay-900 border-2 border-violet-950 hover:bg-eastbay-950',
},
size: {
sm: 'h-11 w-full text-2xl font-medium text-stroke-md',
lg: 'h-14 rounded-2xl w-full text-2xl font-medium text-stroke-md',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'proimary',
size: 'lg',
},
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, ...props }, ref) => {
return <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
});
Button.displayName = 'Button';
export { Button, buttonVariants };
์ด๋ฐ ์์ผ๋ก ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
cn.ts
์ ํธ๋ฆฌํฐ๋ฅผ ํ์ฉํ UI ์ปดํฌ๋ํธ ์คํ์ผ๋ง ์์คํ
์ ์๋์ ๊ฐ์ ํต์ฌ์ ์ธ ์ด์ ์ ์ค ์ ์์ต๋๋ค.
-
์ฒด๊ณ์ ์ธ ๋์์ธ ์์คํ
๊ตฌํ
- ์ผ๊ด๋ ํด๋์ค ์ฒ๋ฆฌ ํจํด์ผ๋ก ์ปดํฌ๋ํธ ์คํ์ผ๋ง
- ํ๋ก์ ํธ ์ ๋ฐ์ ๋์์ธ ์ผ๊ด์ฑ ํ๋ณด
- ์ฝ๋ ์คํ์ผ์ ๊ท๊ฒฉํ ๋ฌ์ฑ
-
์๋ํ๋ ์คํ์ผ ๊ด๋ฆฌ
- ์ปดํฌ๋ํธ ๋ด๋ถ/์ธ๋ถ ์คํ์ผ ์ฐ์ ์์ ์๋ ์กฐ์
- ํด๋์ค ์ถฉ๋ ์๋ ์์ ํ ์คํ์ผ ํ์ฅ
- ์กฐ๊ฑด๋ถ ์คํ์ผ๋ง์ ๊ฐ์ํ
-
๊ฐ๋ฐ ์์ฐ์ฑ ํฅ์
- ํ์
์์คํ
์ ํตํ
props
์๋ ์์ฑ - ์ปดํฌ๋ํธ
variants
์size
๋ฑ์ ํ์ ์์ ์ฑ - ๋น๋ ํ์ ์ต์ ํ๋ก ๋ฐํ์ ์ฑ๋ฅ ๋ณด์ฅ
- ํ์
์์คํ
์ ํตํ
-
ํ์ฅ ๊ฐ๋ฅํ ์ปดํฌ๋ํธ ์ํคํ
์ฒ
- ์คํ์ผ ๋ณํ์ ์ฌ์ฌ์ฉ์ฑ ํ๋ณด
- ๊ธฐ์กด ์ปดํฌ๋ํธ์ ์ ์ฐํ ํ์ฅ
- ์๋ก์ด ๋์์ธ ์๊ตฌ์ฌํญ์ ๋น ๋ฅธ ๋์
์ด๋ ๊ฒ ๋ง๋ Tailwind CSS ๊ธฐ๋ฐ์ UI ์ปดํฌ๋ํธ ์์คํ ์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๋ฉด์๋ ์ ์ฐํ ๋์์ธ ์์คํ ์ ํจ์จ์ ์ผ๋ก ๊ตฌํํ๊ณ ๊ด๋ฆฌํ ์ ์๊ฒ ํด์ค๋๋ค.
- 1. ๊ฐ๋ฐ ํ๊ฒฝ ์ธํ ๋ฐ ํ๋ก์ ํธ ๋ฌธ์ํ
- 2. ์ค์๊ฐ ํต์
- 3. ์ธํ๋ผ ๋ฐ CI/CD
- 4. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์ด Canvas ๊ตฌํํ๊ธฐ
- 5. ์บ๋ฒ์ค ๋๊ธฐํ๋ฅผ ์ํ ์์ CRDT ๊ตฌํ๊ธฐ
-
6. ์ปดํฌ๋ํธ ํจํด๋ถํฐ ์น์์ผ๊น์ง, ํจ์จ์ ์ธ FE ์ค๊ณ
- ์ข์ ์ปดํฌ๋ํธ๋ ๋ฌด์์ธ๊ฐ? + Headless Pattern
- ํจ์จ์ ์ธ UI ์ปดํฌ๋ํธ ์คํ์ผ๋ง: Tailwind CSS + cn.ts
- Tailwind CSS๋ก ๋์์ธ ์์คํ ๋ฐ UI ์ปดํฌ๋ํธ ์ธํ
- ์น์์ผ ํด๋ผ์ด์ธํธ ๊ตฌํ๊ธฐ: React ํ๊ฒฝ์์ ํจ์จ์ ์ธ ์น์์ผ ์ํคํ ์ฒ
- ์น์์ผ ํด๋ผ์ด์ธํธ ์ฝ๋ ๋ถ์ ๋ฐ ๊ณต์
- 7. ํธ๋ฌ๋ธ ์ํ ๋ฐ ์ฑ๋ฅ/UX ๊ฐ์
- 1์ฃผ์ฐจ ๊ธฐ์ ๊ณต์
- 2์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 3์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 4์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- 5์ฃผ์ฐจ ๋ฐ๋ชจ ๋ฐ์ด
- WEEK 06 ์ฃผ๊ฐ ๊ณํ
- WEEK 06 ๋ฐ์ผ๋ฆฌ ์คํฌ๋ผ
- WEEK 06 ์ฃผ๊ฐ ํ๊ณ