WACE PLM — Modal Design System
이 파일은 Claude Code(CLAUDE.md)와 Cursor(.cursor/rules/modal-design.mdc)에서 자동 참조됩니다.
모달 패턴만 정의합니다. Designer 페이지 레이아웃은 변경하지 않습니다.
1. 모달 Shell 구조
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-[{SIZE}] h-[80vh] overflow-hidden flex flex-col p-0 [&>button]:hidden">
<DialogTitle className="sr-only">{title}</DialogTitle>
<DialogDescription className="sr-only">{description}</DialogDescription>
{/* Header */}
<div className="px-6 py-4 border-b border-border flex items-center justify-between">
<div className="flex items-center gap-2">
<{Icon} className="w-4 h-4 text-blue-600" />
<h2 className="text-base font-semibold text-foreground">{title}</h2>
</div>
<Button variant="ghost" size="icon" onClick={() => onOpenChange(false)} className="w-8 h-8">
<X className="w-4 h-4" />
</Button>
</div>
{/* Tab (선택) */}
{/* → Section 2 참조 */}
{/* Content */}
<div className="flex-1 overflow-y-auto px-6 py-4">
{/* 콘텐츠 */}
</div>
{/* Footer */}
<div className="px-6 py-4 border-t border-border flex items-center justify-end gap-2">
<Button variant="outline" onClick={() => onOpenChange(false)}>취소</Button>
<Button className="bg-blue-600 hover:bg-blue-700">저장</Button>
</div>
</DialogContent>
</Dialog>
사이즈 변형
| 용도 |
className |
| 기본 (512px) |
max-w-lg |
| 중간 (672px) |
max-w-2xl |
| 넓음 (800px) |
max-w-[800px] |
| 최대 (1024px) |
max-w-5xl |
2. 탭 패턴 (모달 내부)
<div className="mx-6 mt-3">
<div className="h-9 bg-muted/30 rounded-lg p-0.5 inline-flex">
{tabs.map(tab => (
<button
key={tab.value}
onClick={() => setActiveTab(tab.value)}
className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all flex items-center gap-1.5 ${
activeTab === tab.value
? "bg-blue-50 text-blue-700 shadow-sm"
: "bg-transparent text-foreground hover:text-foreground/80"
}`}
>
<tab.Icon className="w-3.5 h-3.5" />
{tab.label}
</button>
))}
</div>
</div>
- shadcn
<Tabs> 컴포넌트 사용 금지 — 위 커스텀 버튼 패턴 사용
- 활성 탭:
bg-blue-50 text-blue-700 shadow-sm
- 비활성 탭:
bg-transparent text-foreground
3. 섹션 패턴
정보 섹션 (강조, Teal)
<div className="bg-teal-50 border border-teal-200 rounded-xl p-4">
{/* 데이터 소스, 정보 입력 등 주요 섹션 */}
</div>
일반 섹션 (흰색)
<div className="bg-white border border-border rounded-xl p-4 space-y-4">
{/* 설정, 목록 등 */}
</div>
4. 폼 필드 패턴
{/* Label + Input */}
<div className="space-y-2">
<Label className="text-xs font-medium text-foreground">라벨</Label>
<Input className="h-9 text-sm" placeholder="..." />
</div>
{/* Label + Select */}
<div className="space-y-2">
<Label className="text-xs font-medium text-foreground">라벨</Label>
<Select>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="선택하세요" />
</SelectTrigger>
<SelectContent>...</SelectContent>
</Select>
</div>
{/* 읽기 전용 Input */}
<div className="space-y-2">
<Label className="text-xs font-medium text-muted-foreground">라벨 (자동 감지)</Label>
<Input className="h-9 text-sm bg-muted" readOnly />
</div>
- 필드 간격:
space-y-3
- Label:
text-xs font-medium
- Input/Select 높이:
h-9
5. 버튼 규칙 (모달 내)
| 위치 |
variant |
용도 |
| Footer 취소 |
outline |
닫기, 취소 |
| Footer 저장 |
className="bg-blue-600 hover:bg-blue-700" |
저장, 확인 |
| Footer 삭제 |
variant="destructive" |
삭제 확인 |
| 콘텐츠 내 추가 |
variant="outline" size="sm" className="w-full gap-2" |
행/항목 추가 |
| 콘텐츠 내 아이콘 |
variant="ghost" size="sm" |
인라인 액션 |
| 삭제 아이콘 |
variant="ghost" size="sm" className="text-destructive hover:text-destructive hover:bg-destructive/10" |
인라인 삭제 |
6. 오버레이 & 애니메이션
- 오버레이:
rgba(0, 0, 0, 0.6) — globals.css에 이미 전역 설정됨
- 모달 열기:
fade-in 200ms + zoom-in-95 (shadcn 기본)
- 모달 닫기:
fade-out 150ms + zoom-out-95 (shadcn 기본)
- 탭 전환: 애니메이션 없음 (즉시)
7. 아이콘 규칙 (모달 내)
| 위치 |
크기 |
색상 |
| 헤더 타이틀 아이콘 |
w-4 h-4 (16px) |
text-blue-600 |
| 탭 내부 아이콘 |
w-3.5 h-3.5 (14px) |
탭 상태에 따라 자동 |
| 닫기 버튼 X |
w-4 h-4 (16px) |
기본 foreground |
| 콘텐츠 내 액션 아이콘 |
w-3 h-3 (12px) |
text-muted-foreground |
| 행 추가 버튼 아이콘 |
w-3.5 h-3.5 (14px) |
기본 |
8. 접근성 필수 사항
{/* DialogContent 내부 반드시 포함 */}
<DialogTitle className="sr-only">{모달 목적 설명}</DialogTitle>
<DialogDescription className="sr-only">{상세 설명}</DialogDescription>
- Escape 키로 닫기: shadcn Dialog 기본 제공
- 포커스 트랩: shadcn Dialog 기본 제공
- 닫기 버튼에
aria-label 불필요 (shadcn 처리)