diff --git a/components.json b/components.json new file mode 100644 index 0000000..761072a --- /dev/null +++ b/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/package.json b/package.json index 829522e..9820c94 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,14 @@ }, "dependencies": { "@fontsource/pretendard": "^5.2.5", + "@radix-ui/react-slot": "^1.2.3", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.544.0", "next": "15.5.3", "react": "19.1.0", - "react-dom": "19.1.0" + "react-dom": "19.1.0", + "tailwind-merge": "^3.3.1" }, "devDependencies": { "@commitlint/cli": "^19.8.1", @@ -47,6 +50,7 @@ "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", "tailwindcss": "^4", + "tw-animate-css": "^1.4.0", "typescript": "^5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c70270..dcdee76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,12 @@ importers: "@fontsource/pretendard": specifier: ^5.2.5 version: 5.2.5 + "@radix-ui/react-slot": + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.13)(react@19.1.0) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 clsx: specifier: ^2.1.1 version: 2.1.1 @@ -25,6 +31,9 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 devDependencies: "@commitlint/cli": specifier: ^19.8.1 @@ -74,6 +83,9 @@ importers: tailwindcss: specifier: ^4 version: 4.1.13 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 typescript: specifier: ^5 version: 5.9.2 @@ -688,6 +700,30 @@ packages: } engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + "@radix-ui/react-compose-refs@1.1.2": + resolution: + { + integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-slot@1.2.3": + resolution: + { + integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@rtsao/scc@1.1.0": resolution: { @@ -1389,6 +1425,12 @@ packages: } engines: { node: ">=18" } + class-variance-authority@0.7.1: + resolution: + { + integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==, + } + cli-cursor@5.0.0: resolution: { @@ -3676,6 +3718,12 @@ packages: } engines: { node: ^14.18.0 || >=16.0.0 } + tailwind-merge@3.3.1: + resolution: + { + integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==, + } + tailwindcss@4.1.13: resolution: { @@ -3750,6 +3798,12 @@ packages: integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, } + tw-animate-css@1.4.0: + resolution: + { + integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==, + } + type-check@0.4.0: resolution: { @@ -4288,6 +4342,19 @@ snapshots: "@pkgr/core@0.2.9": {} + "@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.13)(react@19.1.0)": + dependencies: + react: 19.1.0 + optionalDependencies: + "@types/react": 19.1.13 + + "@radix-ui/react-slot@1.2.3(@types/react@19.1.13)(react@19.1.0)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.13)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + "@types/react": 19.1.13 + "@rtsao/scc@1.1.0": {} "@rushstack/eslint-patch@1.12.0": {} @@ -4716,6 +4783,10 @@ snapshots: chownr@3.0.0: {} + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -6181,6 +6252,8 @@ snapshots: dependencies: "@pkgr/core": 0.2.9 + tailwind-merge@3.3.1: {} + tailwindcss@4.1.13: {} tapable@2.2.3: {} @@ -6222,6 +6295,8 @@ snapshots: tslib@2.8.1: {} + tw-animate-css@1.4.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/src/app/globals.css b/src/app/globals.css index 65b861c..fe28fde 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -5,6 +5,9 @@ @import "@fontsource/pretendard/500.css"; @import "@fontsource/pretendard/600.css"; @import "@fontsource/pretendard/700.css"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); @theme { /* === Font stacks === */ @@ -26,7 +29,6 @@ --breakpoint-2xl: 1536px; /* 2xl */ /* === Colors === */ - --color-background: #ffffff; --color-black: #000000; /* 메인 텍스트 */ --color-white: #ffffff; @@ -52,10 +54,7 @@ --color-gray-50: #fafafa; /* 푸터 배경 */ /* === Error / Danger Scale === */ - --color-danger-700: #b91c1c; /* 에러 텍스트(진한 경고), 심각 오류 버튼 호버 */ --color-danger-600: #dc2626; /* 기본 에러/경고 상태, 경고 버튼 */ - --color-danger-500: #ef4444; /* 경고 텍스트, 라벨 */ - --color-danger-400: #f87171; /* 경고 강조 배경, 라이트 알림 */ /* === Blue Scale === */ --color-blue-600: #2563eb; /* 진한 블루: 모달 헤더, 주요 CTA 강조 */ @@ -69,9 +68,128 @@ /* === Shadow Presets === */ --shadow-soft: 0 6px 16px rgba(0, 0, 0, 0.08); } +/* ========================= + 2) Tailwind 유틸 매핑 (@theme inline) + - shadcn/ui가 기대하는 이름만 + 안전 폴백 + ========================= */ +@theme inline { + /* Radius sizes (2xl 기준 파생) */ + --radius-sm: calc(var(--radius-2xl) - 8px); + --radius-md: calc(var(--radius-2xl) - 4px); + --radius-lg: var(--radius-2xl); + --radius-xl: calc(var(--radius-2xl) + 4px); + + /* Core tokens (Light) */ + --color-background: var(--color-gray-50); + --color-foreground: var(--color-gray-900); + + --color-primary: var(--primary-blue); + --color-primary-foreground: var(--color-white); + + --color-muted: var(--color-gray-100); + --color-muted-foreground: var(--color-gray-600); + + --color-border: var(--color-gray-200); + --color-input: var(--color-gray-300); + --color-ring: var(--color-blue-400); + + --color-destructive: var(--color-danger-600); + --color-destructive-foreground: var(--color-white); + + /* 안전 폴백(일부 컴포넌트에서 참조) */ + --color-card: var(--color-background); + --color-card-foreground: var(--color-foreground); + + --color-popover: var(--color-background); + --color-popover-foreground: var(--color-foreground); + + --color-secondary: var(--color-gray-100); + --color-secondary-foreground: var(--color-gray-900); + + --color-accent: var(--color-gray-100); + --color-accent-foreground: var(--color-gray-900); +} + +/* 다크 모드: 꼭 필요한 것만 오버라이드 */ +.dark { + --color-background: var(--color-gray-900); + --color-foreground: var(--color-white); + + --color-primary: var(--color-blue-400); + --color-primary-foreground: var(--color-black); + + --color-muted: var(--color-gray-800); + --color-muted-foreground: var(--color-gray-300); + + --color-border: var(--color-gray-700); + --color-input: var(--color-gray-700); + --color-ring: var(--color-blue-400); + + /* 폴백 세트 */ + --color-card: var(--color-gray-900); + --color-card-foreground: var(--color-white); + --color-popover: var(--color-gray-900); + --color-popover-foreground: var(--color-white); + + --color-secondary: var(--color-gray-800); + --color-secondary-foreground: var(--color-gray-300); + + --color-accent: var(--color-gray-800); + --color-accent-foreground: var(--color-gray-300); +} + +/* ========================= + 3) Base resets & a11y + ========================= */ +@layer base { + html { + font-size: 62.5%; /* 1rem = 10px */ + font-family: var(--font-sans); + color-scheme: light; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + } + html:lang(ko) { + font-family: var(--font-sans-ko); + } + + body { + @apply bg-background text-foreground; + line-height: 1.6; + letter-spacing: -0.01em; + } + + /* Focus ring */ + :focus-visible { + outline: 2px solid var(--color-ring); + outline-offset: 2px; + } + @media (forced-colors: active) { + :focus-visible { + outline: 2px solid CanvasText; + } + } + + /* Text selection */ + ::selection { + background: var(--color-primary); + color: var(--color-primary-foreground); + } +} + +/* 경계 & 기본 outline */ +@layer base { + * { + @apply border-border outline-ring/50; + } +} + +/* ========================= + 4) Typography Utilities (팀 공통 스케일) + ========================= */ @layer utilities { - /* === Typography Utilities === */ /* 8~32px */ .t-8-m { font-size: 0.8rem; @@ -81,7 +199,6 @@ font-size: 0.8rem; font-weight: 700; } - .t-10-m { font-size: 1rem; font-weight: 500; @@ -90,7 +207,6 @@ font-size: 1rem; font-weight: 700; } - .t-12-m { font-size: 1.2rem; font-weight: 500; @@ -99,7 +215,6 @@ font-size: 1.2rem; font-weight: 700; } - .t-14-m { font-size: 1.4rem; font-weight: 500; @@ -108,7 +223,6 @@ font-size: 1.4rem; font-weight: 700; } - .t-16-m { font-size: 1.6rem; font-weight: 500; @@ -117,7 +231,6 @@ font-size: 1.6rem; font-weight: 700; } - .t-18-m { font-size: 1.8rem; font-weight: 500; @@ -126,7 +239,6 @@ font-size: 1.8rem; font-weight: 700; } - .t-20-m { font-size: 2rem; font-weight: 500; @@ -135,7 +247,6 @@ font-size: 2rem; font-weight: 700; } - .t-22-m { font-size: 2.2rem; font-weight: 500; @@ -144,7 +255,6 @@ font-size: 2.2rem; font-weight: 700; } - .t-24-m { font-size: 2.4rem; font-weight: 500; @@ -153,7 +263,6 @@ font-size: 2.4rem; font-weight: 700; } - .t-26-m { font-size: 2.6rem; font-weight: 500; @@ -162,7 +271,6 @@ font-size: 2.6rem; font-weight: 700; } - .t-28-m { font-size: 2.8rem; font-weight: 500; @@ -171,7 +279,6 @@ font-size: 2.8rem; font-weight: 700; } - .t-30-m { font-size: 3rem; font-weight: 500; @@ -180,7 +287,6 @@ font-size: 3rem; font-weight: 700; } - .t-32-m { font-size: 3.2rem; font-weight: 500; @@ -199,7 +305,6 @@ font-size: 4rem; font-weight: 700; } - .t-44-m { font-size: 4.4rem; font-weight: 500; @@ -208,7 +313,6 @@ font-size: 4.4rem; font-weight: 700; } - .t-48-m { font-size: 4.8rem; font-weight: 500; @@ -217,7 +321,6 @@ font-size: 4.8rem; font-weight: 700; } - .t-50-m { font-size: 5rem; font-weight: 500; @@ -226,7 +329,6 @@ font-size: 5rem; font-weight: 700; } - .t-54-m { font-size: 5.4rem; font-weight: 500; @@ -235,7 +337,6 @@ font-size: 5.4rem; font-weight: 700; } - .t-58-m { font-size: 5.8rem; font-weight: 500; @@ -244,7 +345,6 @@ font-size: 5.8rem; font-weight: 700; } - .t-60-m { font-size: 6rem; font-weight: 500; @@ -254,7 +354,7 @@ font-weight: 700; } - /* === Body Utilities (line-height 포함) === */ + /* Body + line-height */ .tb-12-m { font-size: 1.2rem; font-weight: 500; @@ -291,96 +391,3 @@ line-height: 140%; } } - -@layer base { - html { - font-size: 62.5%; /* 1rem = 10px */ - font-family: var(--font-sans); - - /* 가독성 */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - text-rendering: optimizeLegibility; - color-scheme: light; - } - - html:lang(ko) { - font-family: var(--font-sans-ko); - } - - body { - background-color: var(--color-background); - color: var(--color-gray-900); - line-height: 1.6; - letter-spacing: -0.01em; - } - - /* 포커스 접근성 */ - :focus-visible { - outline: 2px solid var(--color-gray-900); /* 기본: 검정 */ - outline-offset: 2px; - } - @media (forced-colors: active) { - :focus-visible { - outline: 2px solid CanvasText; - } - } - - /* 텍스트 선택 */ - ::selection { - background: var(--color-blue-500); - color: var(--color-white); - } - - /* 폼 요소 */ - input, - select, - textarea { - font-size: 1.6rem; /* iOS 줌 방지 */ - color: var(--color-gray-900); - background-color: var(--color-white); - border: 1px solid var(--color-gray-300); - border-radius: var(--radius-2xl); - caret-color: var(--color-blue-600); - } - input::placeholder, - textarea::placeholder { - color: var(--color-gray-500); - } - input:focus-visible, - select:focus-visible, - textarea:focus-visible { - border: 0.2rem solid var(--color-gray-900); - outline: 0; - } - input:disabled, - select:disabled, - textarea:disabled { - background-color: var(--color-gray-100); - color: var(--color-gray-500); - } - input:invalid, - textarea:invalid { - border-color: var(--color-danger-600); - } - - /* 링크 */ - a { - color: var(--color-blue-600); - text-underline-offset: 2px; - transition: color 0.2s ease; - } - a:hover { - color: var(--color-blue-500); - } - - /* 모션 최소화 */ - @media (prefers-reduced-motion: reduce) { - * { - animation-duration: 0.001ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.001ms !important; - scroll-behavior: auto !important; - } - } -} diff --git a/src/app/page.tsx b/src/app/page.tsx index 2dc5b3b..0e65c2c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,300 +1,21 @@ -import { Icon } from "@/shared/ui/Icon"; -import { ICON_KEYS } from "@/shared/ui/Icon.registry"; -import clsx from "clsx"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; export default function Home() { - // 데모용 플래그 (상황에 따라 토글) - const compact = false; // true면 여백/그리드 조금 더 촘촘히 - + const compact = false; return ( -
- {/* === 0. 페이지 안내 === */} -
-

globals.css 유틸 종합 테스트

-

- 폰트 스택, 폰트 사이즈, 색상 팔레트, 섀도우/라운드, 폼 포커스, 링크/선택, 반응형까지 전부 - 점검합니다. -

-
- - {/* === 1. 폰트 스택 테스트 === */} -
-

1) 폰트 스택

-
-

기본 스택 (var(--font-sans)) → 영문/숫자에 SF 계열

-

- 한글 스택 (var(--font-sans-ko)) → 한글에 Apple SD Gothic Neo -

-
-

한글: 가나다라마바사 아자차카타파하

-

영문: The quick brown fox jumps over the lazy dog

-

숫자: 0123456789

-
-
-
- - {/* === 2. 폰트 사이즈/굵기 테스트 === */} -
-

2) 폰트 사이즈 & 굵기

- -
- {/* 8~32 (m) */} -

t-8-m (8px / 500)

-

t-10-m (10px / 500)

-

t-12-m

-

t-14-m

-

t-16-m

-

t-18-m

-

t-20-m

-

t-22-m

-

t-24-m

-

t-26-m

-

t-28-m

-

t-30-m

-

t-32-m

- - {/* 8~32 (b) */} -

t-8-b (bold)

-

t-10-b

-

t-12-b

-

t-14-b

-

t-16-b

-

t-18-b

-

t-20-b

-

t-22-b

-

t-24-b

-

t-26-b

-

t-28-b

-

t-30-b

-

t-32-b

- - {/* 40~60 확장 */} -

t-40-m

-

t-44-m

-

t-48-m

-

t-50-m

-

t-54-m

-

t-58-m

-

t-60-m

- -

t-40-b (bold)

-

t-44-b (bold)

-

t-48-b (bold)

-

t-50-b (bold)

-

t-54-b (bold)

-

t-58-b (bold)

-

t-60-b (bold)

-
- - {/* Body (line-height 포함) */} -
-

tb-12-m (lh 160%)

-

tb-14-m (lh 180%)

-

tb-16-m (lh 180%)

-

tb-18-b (bold, lh 140%)

-

tb-20-b (bold, lh 160%)

-

tb-24-m (lh 160%)

-

tb-32-b (bold, lh 140%)

-
-
- - {/* === 3. 컬러 팔레트 테스트 === */} -
-

3) 컬러 팔레트

- - {/* 텍스트 컬러 */} -
-

text-gray-900

-

text-gray-800

-

text-gray-700

-

text-gray-600

-

text-gray-500

-

text-gray-400

-

text-danger-700

-

text-danger-600

-

text-danger-500

-

text-danger-400

-

text-blue-600

-

text-blue-500

-

text-blue-400

-
- - {/* 배경 컬러 스와치 */} -
- {[ - ["bg-gray-50", "bg-gray-50"], - ["bg-gray-100", "bg-gray-100"], - ["bg-gray-200", "bg-gray-200"], - ["bg-gray-300", "bg-gray-300"], - ["bg-gray-400", "bg-gray-400"], - ["bg-gray-500", "bg-gray-500"], - ["bg-blue-100", "bg-blue-100"], - ["bg-blue-400", "bg-blue-400"], - ["bg-blue-500", "bg-blue-500"], - ["bg-blue-600", "bg-blue-600"], - ["bg-danger-400", "bg-danger-400"], - ["bg-danger-600", "bg-danger-600"], - ].map(([cls, label]) => ( -
- {label} -
- ))} -
-
- - {/* === 4. 섀도우/라운드 & 카드 === */} -
-

4) 섀도우 / 라운드

-
-
-

rounded-2xl + shadow-soft

-
-
-

bg-gray-100 카드

-
-
-

bg-blue-100 카드

-
-
-
- - {/* === 5. 폼 & 포커스 접근성 === */} -
-

5) 폼 / 포커스

-
-
- - -
-
- - -
- {/* danger input 섹션 제거됨 */} -
- -