diff --git a/.husky/pre-commit b/.husky/pre-commit index 5c225fb..cb2c84d 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,63 +1 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -echo "πŸ”Ž pre-commit: lint-staged μ‹€ν–‰ 및 μ΅œμ’… 점검 μ‹œμž‘" - -# 0) 컀밋에 ν¬ν•¨λœ 파일 λͺ©λ‘ (μΆ”κ°€/볡사/μˆ˜μ •) -STAGED_FILES="$(git diff --name-only --cached --diff-filter=ACMR)" -if [ -z "$STAGED_FILES" ]; then - echo "ℹ️ μŠ€ν…Œμ΄μ§•λœ 파일이 μ—†μŠ΅λ‹ˆλ‹€. κ±΄λ„ˆλœλ‹ˆλ‹€." - exit 0 -fi - -# 1) λ³€κ²½ 파일만 μžλ™ μˆ˜μ • (eslint --fix, prettier --write) -if ! pnpm lint-staged; then - echo - echo "❌ lint-staged λ‹¨κ³„μ—μ„œ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€." - echo " - μžλ™μˆ˜μ • λΆˆκ°€ν•œ 린트 μ—λŸ¬κ°€ μžˆμ„ 수 μžˆμ–΄μš”." - echo " - λ‘œμ»¬μ—μ„œ 'pnpm lint'둜 μ—λŸ¬λ₯Ό ν™•μΈν•˜κ³  μˆ˜μ •ν•œ λ’€ λ‹€μ‹œ μ»€λ°‹ν•˜μ„Έμš”." - exit 1 -fi - -# 2) μ΅œμ’… 점검: μŠ€ν…Œμ΄μ§•λœ 파일만 λŒ€μƒμœΌλ‘œ 검사(μˆ˜μ • 없이 μ‹€νŒ¨λ§Œ 감지) -PRETTIER_FAIL=0 -ESLINT_FAIL=0 - -# κ°œν–‰λ§Œ κ΅¬λΆ„μžλ‘œ μ‚¬μš© (곡백 포함 파일λͺ… μ•ˆμ „) -IFS="$(printf '\n')" -for f in $STAGED_FILES; do - case "$f" in - *.js|*.jsx|*.ts|*.tsx|*.json|*.css|*.md) - # Prettier 포맷 μ€€μˆ˜ 확인 (μˆ˜μ • 없이 검사) - pnpm -s prettier --check "$f" || PRETTIER_FAIL=1 - ;; - esac -done - -for f in $STAGED_FILES; do - case "$f" in - *.js|*.jsx|*.ts|*.tsx) - # ESLint μ΅œμ’… 확인 (μˆ˜μ • 없이 검사) - pnpm -s eslint --max-warnings=0 "$f" || ESLINT_FAIL=1 - ;; - esac -done -unset IFS - -if [ "$PRETTIER_FAIL" -ne 0 ]; then - echo - echo "❌ Prettier 포맷 μœ„λ°˜μ΄ 남아 μžˆμŠ΅λ‹ˆλ‹€." - echo " - 'pnpm format' ν›„ λ‹€μ‹œ μ»€λ°‹ν•˜μ„Έμš”." - exit 1 -fi - -if [ "$ESLINT_FAIL" -ne 0 ]; then - echo - echo "❌ ESLint μœ„λ°˜μ΄ 남아 μžˆμŠ΅λ‹ˆλ‹€." - echo " - 'pnpm lint:fix' λ˜λŠ” μˆ˜λ™ μˆ˜μ • ν›„ λ‹€μ‹œ μ»€λ°‹ν•˜μ„Έμš”." - exit 1 -fi - -COUNT="$(printf '%s\n' "$STAGED_FILES" | wc -l | tr -d ' ')" -echo "βœ… pre-commit 톡과: ${COUNT}개 파일 점검 μ™„λ£Œ" -exit 0 +pnpm lint-staged diff --git a/package.json b/package.json index a6fd854..829522e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ - "eslint --fix", + "eslint --fix --max-warnings=0", "prettier --write" ], "*.{json,css,md}": [ @@ -24,6 +24,8 @@ }, "dependencies": { "@fontsource/pretendard": "^5.2.5", + "clsx": "^2.1.1", + "lucide-react": "^0.544.0", "next": "15.5.3", "react": "19.1.0", "react-dom": "19.1.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f254cf..6c70270 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,6 +10,12 @@ importers: "@fontsource/pretendard": specifier: ^5.2.5 version: 5.2.5 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + lucide-react: + specifier: ^0.544.0 + version: 0.544.0(react@19.1.0) next: specifier: 15.5.3 version: 15.5.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -1410,6 +1416,13 @@ packages: } engines: { node: ">=12" } + clsx@2.1.1: + resolution: + { + integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, + } + engines: { node: ">=6" } + color-convert@2.0.1: resolution: { @@ -2811,6 +2824,14 @@ packages: } hasBin: true + lucide-react@0.544.0: + resolution: + { + integrity: sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==, + } + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.19: resolution: { @@ -4712,6 +4733,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -5652,6 +5675,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lucide-react@0.544.0(react@19.1.0): + dependencies: + react: 19.1.0 + magic-string@0.30.19: dependencies: "@jridgewell/sourcemap-codec": 1.5.5 diff --git a/src/app/page.tsx b/src/app/page.tsx index 7275477..2dc5b3b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,13 @@ +import { Icon } from "@/shared/ui/Icon"; +import { ICON_KEYS } from "@/shared/ui/Icon.registry"; +import clsx from "clsx"; + export default function Home() { + // 데λͺ¨μš© ν”Œλž˜κ·Έ (상황에 따라 ν† κΈ€) + const compact = false; // trueλ©΄ μ—¬λ°±/κ·Έλ¦¬λ“œ 쑰금 더 촘촘히 + return ( -
+
{/* === 0. νŽ˜μ΄μ§€ μ•ˆλ‚΄ === */}

globals.css μœ ν‹Έ μ’…ν•© ν…ŒμŠ€νŠΈ

@@ -270,6 +277,24 @@ export default function Home() {

흰 μΉ΄λ“œ μœ„μ˜ λŒ€λΉ„ 체크

+ + {/* === μ•„μ΄μ½˜ μ‡ΌμΌ€μ΄μŠ€ === */} +

πŸ“¦ Icon Showcase

+
+ {ICON_KEYS.map((name) => ( +
+ + {name} +
+ ))} +
); } diff --git a/src/shared/ui/Icon.registry.ts b/src/shared/ui/Icon.registry.ts new file mode 100644 index 0000000..44d553e --- /dev/null +++ b/src/shared/ui/Icon.registry.ts @@ -0,0 +1,80 @@ +import { + ArrowLeft, + ArrowRight, + Bell, + BellRing, + Calendar, + CalendarDays, + CalendarRange, + Check, + CheckSquare, + Clock, + Eye, + EyeOff, + Grid3X3, + HelpCircle, + Info, + Laptop, + Layout, + Mail, + Maximize, + Menu, + Minus, + Monitor, + Move, + Plus, + RotateCcw, + Search, + Settings, + Smartphone, + StickyNote, + UserPlus, + Users, + X, +} from "lucide-react"; + +/** + * μ•„μ΄μ½˜ λ ˆμ§€μŠ€νŠΈλ¦¬ + * - ν•„μš”ν•œ Lucide μ•„μ΄μ½˜λ§Œ import ν›„ 객체둜 λ§€ν•‘ + * - keyλŠ” ν”„λ‘œμ νŠΈμ—μ„œ μ‚¬μš©ν•  이름 (camelCase) + */ +export const icons = { + eye: Eye, + eyeOff: EyeOff, + check: Check, + x: X, + mail: Mail, + arrowLeft: ArrowLeft, + arrowRight: ArrowRight, + search: Search, + userPlus: UserPlus, + monitor: Monitor, + laptop: Laptop, + smartphone: Smartphone, + calendar: Calendar, + calendarDays: CalendarDays, + calendarRange: CalendarRange, + checkSquare: CheckSquare, + stickyNote: StickyNote, + rotateCcw: RotateCcw, + bell: Bell, + clock: Clock, + users: Users, + bellRing: BellRing, + settings: Settings, + menu: Menu, + info: Info, + helpCircle: HelpCircle, + minus: Minus, + plus: Plus, + grid3X3: Grid3X3, + layout: Layout, + move: Move, + maximize: Maximize, +} as const; + +/** μ•„μ΄μ½˜ 이름 νƒ€μž… */ +export type IconName = keyof typeof icons; + +/** μ•„μ΄μ½˜ key 리슀트 (예: map λ Œλ”λ§μ— ν™œμš©) */ +export const ICON_KEYS = Object.keys(icons) as IconName[]; diff --git a/src/shared/ui/Icon.tsx b/src/shared/ui/Icon.tsx new file mode 100644 index 0000000..3427ce9 --- /dev/null +++ b/src/shared/ui/Icon.tsx @@ -0,0 +1,18 @@ +import type { IconProps } from "@/types/icon"; +import clsx from "clsx"; +import { icons } from "./Icon.registry"; + +export function Icon({ name, size = 24, strokeWidth = 2, label, className, ...rest }: IconProps) { + const Cmp = icons[name]; + return ( + + ); +} diff --git a/src/types/icon.ts b/src/types/icon.ts new file mode 100644 index 0000000..ee0a287 --- /dev/null +++ b/src/types/icon.ts @@ -0,0 +1,23 @@ +import type { IconName } from "@/shared/ui/Icon.registry"; +import type { Calendar } from "lucide-react"; +import type { ComponentProps } from "react"; + +/** Lucide μ•„μ΄μ½˜ 곡톡 props */ +type LucideBaseProps = ComponentProps; + +/** + * 곡톡 Icon μ»΄ν¬λ„ŒνŠΈ Props + * - 색상은 Tailwind text-* 클래슀둜 μ œμ–΄ + * - color prop은 μ œμ™Έν•˜κ³  항상 currentColor μ‚¬μš© + */ +export type IconProps = { + name: IconName; + /** μŠ€ν¬λ¦°λ¦¬λ”μš© 라벨 (μ‹œκ°μ  title μ•„λ‹˜) */ + label?: string; + /** μ•„μ΄μ½˜ 크기 (κΈ°λ³Έ 24) */ + size?: number; + /** μ„  κ΅΅κΈ° (κΈ°λ³Έ 2) */ + strokeWidth?: number; + /** Tailwind ν΄λž˜μŠ€μ™€ κ²°ν•© κ°€λŠ₯ */ + className?: string; +} & Omit;