diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..e2fa0458 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,857 @@ +# Cursor Rules for ERP-node Project + +## shadcn/ui 웹 스타일 가이드라인 + +모든 프론트엔드 개발 시 다음 shadcn/ui 기반 스타일 가이드라인을 준수해야 합니다. + +### 1. Color System (색상 시스템) + +#### CSS Variables 사용 +shadcn은 CSS Variables를 사용하여 테마를 관리하며, 모든 색상은 HSL 형식으로 정의됩니다. + +**기본 색상 토큰 (항상 사용):** +- `--background`: 페이지 배경 +- `--foreground`: 기본 텍스트 +- `--primary`: 메인 액션 (Indigo 계열) +- `--primary-foreground`: Primary 위 텍스트 +- `--secondary`: 보조 액션 +- `--muted`: 약한 배경 +- `--muted-foreground`: 보조 텍스트 +- `--destructive`: 삭제/에러 (Rose 계열) +- `--border`: 테두리 +- `--ring`: 포커스 링 + +**Tailwind 클래스로 사용:** +```tsx +bg-primary text-primary-foreground +bg-secondary text-secondary-foreground +bg-muted text-muted-foreground +bg-destructive text-destructive-foreground +border-border +``` + +**추가 시맨틱 컬러:** +- Success: `--success` (Emerald-600 계열) +- Warning: `--warning` (Amber-500 계열) +- Info: `--info` (Cyan-500 계열) + +### 2. Spacing System (간격) + +**Tailwind Scale (4px 기준):** +- 0.5 = 2px, 1 = 4px, 2 = 8px, 3 = 12px, 4 = 16px, 6 = 24px, 8 = 32px + +**컴포넌트별 권장 간격:** +- 카드 패딩: `p-6` (24px) +- 카드 간 마진: `gap-6` (24px) +- 폼 필드 간격: `space-y-4` (16px) +- 버튼 내부 패딩: `px-4 py-2` +- 섹션 간격: `space-y-8` 또는 `space-y-12` + +### 3. Typography (타이포그래피) + +**용도별 타이포그래피 (필수):** +- 페이지 제목: `text-3xl font-bold` +- 섹션 제목: `text-2xl font-semibold` +- 카드 제목: `text-xl font-semibold` +- 서브 제목: `text-lg font-medium` +- 본문 텍스트: `text-sm text-muted-foreground` +- 작은 텍스트: `text-xs text-muted-foreground` +- 버튼 텍스트: `text-sm font-medium` +- 폼 라벨: `text-sm font-medium` + +### 4. Button Variants (버튼 스타일) + +**필수 사용 패턴:** +```tsx +// Primary (기본) + + +// Secondary + + +// Outline + + +// Ghost + + +// Destructive + +``` + +**버튼 크기:** +- `size="sm"`: 작은 버튼 (h-9 px-3) +- `size="default"`: 기본 버튼 (h-10 px-4 py-2) +- `size="lg"`: 큰 버튼 (h-11 px-8) +- `size="icon"`: 아이콘 전용 (h-10 w-10) + +### 5. Input States (입력 필드 상태) + +**필수 적용 상태:** +```tsx +// Default +className="border-input" + +// Focus (모든 입력 필드 필수) +className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2" + +// Error +className="border-destructive focus-visible:ring-destructive" + +// Disabled +className="disabled:opacity-50 disabled:cursor-not-allowed" +``` + +### 6. Card Structure (카드 구조) + +**표준 카드 구조 (필수):** +```tsx + + + 제목 + 설명 + + + {/* 내용 */} + + + {/* 액션 버튼들 */} + + +``` + +### 7. Border & Radius (테두리 및 둥근 모서리) + +**컴포넌트별 권장:** +- 버튼: `rounded-md` (6px) +- 입력 필드: `rounded-md` (6px) +- 카드: `rounded-lg` (8px) +- 배지: `rounded-full` +- 모달/대화상자: `rounded-lg` (8px) +- 드롭다운: `rounded-md` (6px) + +### 8. Shadow (그림자) + +**용도별 권장:** +- 카드: `shadow-sm` +- 드롭다운: `shadow-md` +- 모달: `shadow-lg` +- 팝오버: `shadow-md` +- 버튼 호버: `shadow-sm` + +### 9. Interactive States (상호작용 상태) + +**필수 적용 패턴:** +```tsx +// Hover +hover:bg-primary/90 // 버튼 +hover:bg-accent // Ghost 버튼 +hover:underline // 링크 +hover:shadow-md transition-shadow // 카드 + +// Focus (모든 인터랙티브 요소 필수) +focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 + +// Active +active:scale-95 transition-transform // 버튼 + +// Disabled +disabled:opacity-50 disabled:cursor-not-allowed +``` + +### 10. Animation (애니메이션) + +**권장 Duration:** +- 빠른 피드백: `duration-75` +- 기본: `duration-150` +- 부드러운 전환: `duration-300` + +**권장 패턴:** +- 버튼 클릭: `transition-transform duration-150 active:scale-95` +- 색상 전환: `transition-colors duration-150` +- 드롭다운 열기: `transition-all duration-200` + +### 11. Responsive (반응형) + +**Breakpoints:** +- `sm`: 640px (모바일 가로) +- `md`: 768px (태블릿) +- `lg`: 1024px (노트북) +- `xl`: 1280px (데스크톱) + +**반응형 패턴:** +```tsx +// 모바일 우선 접근 +className="flex-col md:flex-row" +className="grid-cols-1 md:grid-cols-2 lg:grid-cols-3" +className="text-2xl md:text-3xl lg:text-4xl" +className="p-4 md:p-6 lg:p-8" +``` + +### 12. Accessibility (접근성) + +**필수 적용 사항:** +1. 포커스 표시: 모든 인터랙티브 요소에 `focus-visible:ring-2` 적용 +2. ARIA 레이블: 적절한 `aria-label`, `aria-describedby` 사용 +3. 키보드 네비게이션: Tab, Enter, Space, Esc 지원 +4. 색상 대비: 최소 4.5:1 (일반 텍스트), 3:1 (큰 텍스트) + +### 13. Class 순서 (일관성 유지) + +**항상 이 순서로 작성:** +1. Layout: `flex`, `grid`, `block` +2. Sizing: `w-full`, `h-10` +3. Spacing: `p-4`, `m-2`, `gap-4` +4. Typography: `text-sm`, `font-medium` +5. Colors: `bg-primary`, `text-white` +6. Border: `border`, `rounded-md` +7. Effects: `shadow-sm`, `opacity-50` +8. States: `hover:`, `focus:`, `disabled:` +9. Responsive: `md:`, `lg:` + +### 14. 실무 적용 규칙 + +1. **shadcn 컴포넌트 우선 사용**: 커스텀 스타일보다 shadcn 기본 컴포넌트 활용 +2. **cn 유틸리티 사용**: 조건부 클래스는 `cn()` 함수로 결합 +3. **테마 변수 사용**: 하드코딩된 색상 대신 CSS 변수 사용 +4. **다크모드 고려**: 모든 컴포넌트는 다크모드 호환 필수 +5. **일관성 유지**: 같은 용도의 컴포넌트는 같은 스타일 사용 + +### 15. 금지 사항 + +1. ❌ 하드코딩된 색상 값 사용 (예: `bg-blue-500` 대신 `bg-primary`) +2. ❌ 인라인 스타일로 색상 지정 (예: `style={{ color: '#3b82f6' }}`) +3. ❌ 포커스 스타일 제거 (`outline-none`만 단독 사용) +4. ❌ 접근성 무시 (ARIA 레이블 누락) +5. ❌ 반응형 무시 (데스크톱 전용 스타일) +6. ❌ **중첩 박스 금지**: 사용자가 명시적으로 요청하지 않는 한 Card 안에 Card, Border 안에 Border 같은 중첩된 컨테이너 구조를 만들지 않음 + +### 16. 중첩 박스 금지 상세 규칙 + +**금지되는 패턴 (사용자 요청 없이):** +```tsx +// ❌ Card 안에 Card + + + // 중첩 금지! + 내용 + + + + +// ❌ Border 안에 Border +
+
// 중첩 금지! + 내용 +
+
+ +// ❌ 불필요한 래퍼 +
+
// 중첩 금지! + 내용 +
+
+``` + +**허용되는 패턴:** +```tsx +// ✅ 단일 Card + + + 제목 + + + 내용 + + + +// ✅ 의미적으로 다른 컴포넌트 조합 + + + // Dialog는 별도 UI 레이어 + ... + + + + +// ✅ 그리드/리스트 내부의 Card들 +
+ 항목 1 + 항목 2 + 항목 3 +
+``` + +**예외 상황 (사용자가 명시적으로 요청한 경우만):** +- 대시보드에서 섹션별 그룹핑이 필요한 경우 +- 복잡한 데이터 구조를 시각적으로 구분해야 하는 경우 +- 드래그앤드롭 등 특수 기능을 위한 경우 + +**원칙:** +- 심플하고 깔끔한 디자인 유지 +- 불필요한 시각적 레이어 제거 +- 사용자가 명시적으로 "박스 안에 박스", "중첩된 카드" 등을 요청하지 않으면 단일 레벨 유지 + +### 17. 표준 모달(Dialog) 디자인 패턴 + +**프로젝트 표준 모달 구조 (플로우 관리 기준):** + +```tsx + + + {/* 헤더: 제목 + 설명 */} + + 모달 제목 + + 모달에 대한 간단한 설명 + + + + {/* 컨텐츠: 폼 필드들 */} +
+ {/* 각 입력 필드 */} +
+ + +

+ 도움말 텍스트 (선택사항) +

+
+
+ + {/* 푸터: 액션 버튼들 */} + + + + +
+
+``` + +**필수 적용 사항:** + +1. **반응형 크기** + - 모바일: `max-w-[95vw]` (화면 너비의 95%) + - 데스크톱: `sm:max-w-[500px]` (고정 500px) + +2. **헤더 구조** + - DialogTitle: `text-base sm:text-lg` (16px → 18px) + - DialogDescription: `text-xs sm:text-sm` (12px → 14px) + - 항상 제목과 설명 모두 포함 + +3. **컨텐츠 간격** + - 필드 간 간격: `space-y-3 sm:space-y-4` (12px → 16px) + - 각 필드는 `
` 로 감싸기 + +4. **입력 필드 패턴** + - Label: `text-xs sm:text-sm` + 필수 필드는 `*` 표시 + - Input/Select: `h-8 text-xs sm:h-10 sm:text-sm` (32px → 40px) + - 도움말: `text-muted-foreground mt-1 text-[10px] sm:text-xs` + +5. **푸터 버튼** + - 컨테이너: `gap-2 sm:gap-0` (모바일에서 간격, 데스크톱에서 자동) + - 버튼: `h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm` + - 모바일: 같은 크기 (`flex-1`) + - 데스크톱: 자동 크기 (`flex-none`) + - 순서: 취소(outline) → 확인(default) + +6. **접근성** + - Label의 `htmlFor`와 Input의 `id` 매칭 + - Button에 적절한 `onClick` 핸들러 + - Dialog의 `open`과 `onOpenChange` 필수 + +**확인 모달 (간단한 경고/확인):** + +```tsx + + + + 작업 확인 + + 정말로 이 작업을 수행하시겠습니까? +
이 작업은 되돌릴 수 없습니다. +
+
+ + + + + +
+
+``` + +**원칙:** +- 모든 모달은 모바일 우선 반응형 디자인 +- 일관된 크기, 간격, 폰트 크기 사용 +- 사용자가 다른 크기를 명시하지 않으면 `sm:max-w-[500px]` 사용 +- 삭제/위험한 작업은 `variant="destructive"` 사용 + +### 18. 검색 가능한 Select 박스 (Combobox 패턴) + +**적용 조건**: 사용자가 "검색 기능이 있는 Select 박스" 또는 "Combobox"를 명시적으로 요청한 경우만 사용 + +**표준 Combobox 구조 (플로우 관리 기준):** + +```tsx +import { Check, ChevronsUpDown } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; + +// 상태 관리 +const [open, setOpen] = useState(false); +const [value, setValue] = useState(""); + +// 렌더링 + + + + + + + + + + 항목을 찾을 수 없습니다. + + + {items.map((item) => ( + { + setValue(currentValue === value ? "" : currentValue); + setOpen(false); + }} + className="text-xs sm:text-sm" + > + + {item.label} + + ))} + + + + + +``` + +**복잡한 데이터 표시 (라벨 + 설명):** + +```tsx + { + setValue(currentValue); + setOpen(false); + }} + className="text-xs sm:text-sm" +> + +
+ {item.label} + {item.description && ( + {item.description} + )} +
+
+``` + +**필수 적용 사항:** + +1. **반응형 크기** + - 버튼 높이: `h-8 sm:h-10` (32px → 40px) + - 텍스트 크기: `text-xs sm:text-sm` (12px → 14px) + - PopoverContent 너비: `width: "var(--radix-popover-trigger-width)"` (트리거와 동일) + +2. **필수 컴포넌트** + - Popover: 드롭다운 컨테이너 + - Command: 검색 및 필터링 기능 + - CommandInput: 검색 입력 필드 + - CommandList: 항목 목록 컨테이너 + - CommandEmpty: 검색 결과 없음 메시지 + - CommandGroup: 항목 그룹 + - CommandItem: 개별 항목 + +3. **아이콘 사용** + - ChevronsUpDown: 드롭다운 표시 아이콘 (오른쪽) + - Check: 선택된 항목 표시 (왼쪽) + +4. **접근성** + - `role="combobox"`: ARIA 역할 명시 + - `aria-expanded={open}`: 열림/닫힘 상태 + - PopoverTrigger에 `asChild` 사용 + +5. **로딩 상태** + ```tsx + + ``` + +**일반 Select vs Combobox 선택 기준:** + +| 상황 | 컴포넌트 | 이유 | +|------|----------|------| +| 항목 5개 이하 | `` | 빠른 선택 | + +**원칙:** +- 사용자가 명시적으로 요청하지 않으면 일반 Select 사용 +- 많은 항목(10개 이상)을 다룰 때는 Combobox 권장 +- 일관된 반응형 크기 유지 +- 검색 플레이스홀더는 구체적으로 작성 + +### 19. Form Validation (폼 검증) + +**입력 필드 상태별 스타일:** + +```tsx +// Default (기본) +className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm +focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" + +// Error (에러) +className="flex h-10 w-full rounded-md border border-destructive bg-background px-3 py-2 text-sm +focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive" + +// Success (성공) +className="flex h-10 w-full rounded-md border border-success bg-background px-3 py-2 text-sm +focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-success" + +// Disabled (비활성) +className="flex h-10 w-full rounded-md border border-input bg-muted px-3 py-2 text-sm +opacity-50 cursor-not-allowed" +``` + +**Helper Text (도움말 텍스트):** + +```tsx +// 기본 Helper Text +

+ 8자 이상 입력해주세요 +

+ +// Error Message +

+ + 이메일 형식이 올바르지 않습니다 +

+ +// Success Message +

+ + 사용 가능한 이메일입니다 +

+``` + +**Form Label (폼 라벨):** + +```tsx +// 기본 라벨 + + +// 필수 항목 표시 + +``` + +**전체 폼 필드 구조:** + +```tsx +
+ + + {error && ( +

+ + {errorMessage} +

+ )} + {!error && helperText && ( +

{helperText}

+ )} +
+``` + +**실시간 검증 피드백:** + +```tsx +// 로딩 중 (검증 진행) +
+ + +
+ +// 성공 +
+ + +
+ +// 실패 +
+ + +
+``` + +### 20. Loading States (로딩 상태) + +**Spinner (스피너) 크기별:** + +```tsx +// Small + + +// Default + + +// Large + +``` + +**Spinner 색상별:** + +```tsx +// Primary + + +// Muted + + +// White (다크 배경용) + +``` + +**Button Loading:** + +```tsx + +``` + +**Skeleton UI:** + +```tsx +// 텍스트 스켈레톤 +
+
+
+
+
+ +// 카드 스켈레톤 +
+
+
+
+
+
+
+
+
+
+
+
+
+
+``` + +**Progress Bar (진행률):** + +```tsx +// 기본 Progress Bar +
+
+
+ +// 라벨 포함 +
+
+ 업로드 중... + {progress}% +
+
+
+
+
+``` + +**Full Page Loading:** + +```tsx +
+
+ +

로딩 중...

+
+
+``` + +### 21. Empty States (빈 상태) + +**기본 Empty State:** + +```tsx +
+
+ +
+

데이터가 없습니다

+

+ 아직 생성된 항목이 없습니다. 새로운 항목을 추가해보세요. +

+ +
+``` + +**검색 결과 없음:** + +```tsx +
+
+ +
+

검색 결과가 없습니다

+

+ "{searchQuery}"에 대한 결과를 찾을 수 없습니다. 다른 검색어로 시도해보세요. +

+ +
+``` + +**에러 상태:** + +```tsx +
+
+ +
+

데이터를 불러올 수 없습니다

+

+ 일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요. +

+ +
+``` + +**아이콘 가이드:** +- 데이터 없음: Inbox, Package, FileText +- 검색 결과 없음: Search, SearchX +- 필터 결과 없음: Filter, FilterX +- 에러: AlertCircle, XCircle +- 네트워크 오류: WifiOff, CloudOff +- 권한 없음: Lock, ShieldOff + +--- + +## 추가 프로젝트 규칙 + +- 백엔드 재실행 금지 +- 항상 한글로 답변 +- 이모지 사용 금지 (명시적 요청 없이) +- 심플하고 깔끔한 디자인 유지 +