546 lines
13 KiB
Markdown
546 lines
13 KiB
Markdown
|
|
# 디자인 시스템 표준화 가이드
|
||
|
|
|
||
|
|
## 목적
|
||
|
|
SAM MES 시스템의 모든 페이지가 일관된 디자인을 유지하도록 표준화합니다.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. 필수 컴포넌트 사용
|
||
|
|
|
||
|
|
### 1.1 PageLayout
|
||
|
|
**모든 페이지는 반드시 PageLayout으로 감싸야 합니다.**
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { PageLayout } from "./organisms/PageLayout";
|
||
|
|
|
||
|
|
export function YourPage() {
|
||
|
|
return (
|
||
|
|
<PageLayout maxWidth="full">
|
||
|
|
{/* 페이지 내용 */}
|
||
|
|
</PageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**maxWidth 옵션:**
|
||
|
|
- `full`: 대시보드, 목록 페이지 (기본값)
|
||
|
|
- `2xl`: 넓은 폼 페이지
|
||
|
|
- `xl`: 일반 폼 페이지
|
||
|
|
- `lg`: 좁은 폼 페이지
|
||
|
|
|
||
|
|
### 1.2 PageHeader
|
||
|
|
**모든 페이지는 반드시 PageHeader를 사용해야 합니다.**
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { PageHeader } from "./organisms/PageHeader";
|
||
|
|
import { Package } from "lucide-react";
|
||
|
|
import { Button } from "./ui/button";
|
||
|
|
|
||
|
|
<PageHeader
|
||
|
|
title="페이지 제목"
|
||
|
|
description="페이지에 대한 간단한 설명"
|
||
|
|
icon={Package}
|
||
|
|
actions={
|
||
|
|
<>
|
||
|
|
<Button variant="outline">취소</Button>
|
||
|
|
<Button>등록</Button>
|
||
|
|
</>
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
**금지 사항:**
|
||
|
|
```tsx
|
||
|
|
// ❌ 직접 헤더 작성 금지
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<h1>제목</h1>
|
||
|
|
<Button>등록</Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// ✅ PageHeader 사용
|
||
|
|
<PageHeader title="제목" actions={<Button>등록</Button>} />
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 표준 페이지 구조
|
||
|
|
|
||
|
|
### 2.1 기본 목록 페이지 구조
|
||
|
|
```tsx
|
||
|
|
import { PageLayout } from "./organisms/PageLayout";
|
||
|
|
import { PageHeader } from "./organisms/PageHeader";
|
||
|
|
import { StatCards } from "./organisms/StatCards";
|
||
|
|
import { SearchFilter } from "./organisms/SearchFilter";
|
||
|
|
import { DataTable } from "./organisms/DataTable";
|
||
|
|
|
||
|
|
export function ListPage() {
|
||
|
|
return (
|
||
|
|
<PageLayout>
|
||
|
|
{/* 1. 헤더 (제목 + 서브제목 + 아이콘 + 액션 버튼) */}
|
||
|
|
<PageHeader
|
||
|
|
title="페이지 제목"
|
||
|
|
description="페이지 설명"
|
||
|
|
icon={Icon}
|
||
|
|
actions={<Button>등록</Button>}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 2. 통계 카드 (4개) */}
|
||
|
|
<StatCards
|
||
|
|
stats={[
|
||
|
|
{ title: "통계1", value: "100", icon: Icon, color: "blue" },
|
||
|
|
{ title: "통계2", value: "200", icon: Icon, color: "green" },
|
||
|
|
{ title: "통계3", value: "300", icon: Icon, color: "yellow" },
|
||
|
|
{ title: "통계4", value: "400", icon: Icon, color: "purple" },
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 3. 검색/필터 */}
|
||
|
|
<SearchFilter
|
||
|
|
searchValue={searchValue}
|
||
|
|
onSearchChange={setSearchValue}
|
||
|
|
filters={filterConfig}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* 4. 콘텐츠 (테이블) */}
|
||
|
|
<DataTable
|
||
|
|
columns={columns}
|
||
|
|
data={filteredData}
|
||
|
|
/>
|
||
|
|
</PageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 대시보드 페이지 구조
|
||
|
|
```tsx
|
||
|
|
export function DashboardPage() {
|
||
|
|
return (
|
||
|
|
<PageLayout>
|
||
|
|
<PageHeader
|
||
|
|
title="대시보드"
|
||
|
|
description="실시간 현황을 확인하세요"
|
||
|
|
icon={LayoutDashboard}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<StatCards stats={stats} />
|
||
|
|
|
||
|
|
{/* 차트 및 위젯 */}
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||
|
|
<Widget1 />
|
||
|
|
<Widget2 />
|
||
|
|
<Widget3 />
|
||
|
|
</div>
|
||
|
|
</PageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.3 폼 페이지 구조
|
||
|
|
```tsx
|
||
|
|
export function FormPage() {
|
||
|
|
return (
|
||
|
|
<PageLayout maxWidth="xl">
|
||
|
|
<PageHeader
|
||
|
|
title="신규 등록"
|
||
|
|
description="필수 항목을 입력하세요"
|
||
|
|
icon={Plus}
|
||
|
|
actions={
|
||
|
|
<>
|
||
|
|
<Button variant="outline" onClick={handleCancel}>
|
||
|
|
취소
|
||
|
|
</Button>
|
||
|
|
<Button onClick={handleSubmit}>
|
||
|
|
저장
|
||
|
|
</Button>
|
||
|
|
</>
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<FormSection title="기본 정보">
|
||
|
|
{/* 폼 필드들 */}
|
||
|
|
</FormSection>
|
||
|
|
|
||
|
|
<FormSection title="추가 정보">
|
||
|
|
{/* 폼 필드들 */}
|
||
|
|
</FormSection>
|
||
|
|
</PageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 스타일 가이드
|
||
|
|
|
||
|
|
### 3.1 금지된 패턴
|
||
|
|
```tsx
|
||
|
|
// ❌ 직접 패딩/여백 정의 금지
|
||
|
|
<div className="p-6 space-y-6">
|
||
|
|
|
||
|
|
// ❌ 배경 그라데이션 금지 (특별한 경우 제외)
|
||
|
|
<div className="bg-gradient-to-br from-slate-50 to-blue-50/30">
|
||
|
|
|
||
|
|
// ❌ 불필요한 min-h-screen 금지
|
||
|
|
<div className="min-h-screen">
|
||
|
|
|
||
|
|
// ❌ 인라인 헤더 스타일 금지
|
||
|
|
<h1 className="text-3xl font-bold text-foreground">
|
||
|
|
|
||
|
|
// ❌ 커스텀 카드 스타일 금지
|
||
|
|
<div className="bg-card border border-border/20 rounded-xl p-6">
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3.2 권장 패턴
|
||
|
|
```tsx
|
||
|
|
// ✅ PageLayout 사용
|
||
|
|
<PageLayout>
|
||
|
|
|
||
|
|
// ✅ PageHeader 사용
|
||
|
|
<PageHeader title="제목" />
|
||
|
|
|
||
|
|
// ✅ Card 컴포넌트 사용
|
||
|
|
import { Card, CardHeader, CardTitle, CardContent } from "./ui/card";
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>제목</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
{/* 내용 */}
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
// ✅ 그리드 레이아웃
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 타이포그래피 표준
|
||
|
|
|
||
|
|
### 4.1 절대 사용하지 말 것
|
||
|
|
**globals.css에 타이포그래피가 정의되어 있으므로 Tailwind 클래스를 사용하지 마세요.**
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
// ❌ 절대 금지!
|
||
|
|
className="text-2xl"
|
||
|
|
className="text-xl"
|
||
|
|
className="text-lg"
|
||
|
|
className="font-bold"
|
||
|
|
className="font-semibold"
|
||
|
|
className="leading-tight"
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 올바른 사용법
|
||
|
|
```tsx
|
||
|
|
// ✅ HTML 태그 사용 (자동 스타일 적용)
|
||
|
|
<h1>대제목</h1>
|
||
|
|
<h2>중제목</h2>
|
||
|
|
<h3>소제목</h3>
|
||
|
|
<p>본문</p>
|
||
|
|
<label>레이블</label>
|
||
|
|
```
|
||
|
|
|
||
|
|
**예외:** 사용자 요청이 있을 때만 타이포그래피 클래스 사용
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 색상 시스템
|
||
|
|
|
||
|
|
### 5.1 디자인 토큰 사용
|
||
|
|
```tsx
|
||
|
|
// ✅ CSS 변수 사용
|
||
|
|
className="bg-primary text-primary-foreground"
|
||
|
|
className="bg-secondary text-secondary-foreground"
|
||
|
|
className="bg-muted text-muted-foreground"
|
||
|
|
className="bg-destructive text-destructive-foreground"
|
||
|
|
className="border-border"
|
||
|
|
className="bg-background text-foreground"
|
||
|
|
className="bg-card text-card-foreground"
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2 아이콘 배경 색상
|
||
|
|
```tsx
|
||
|
|
// ✅ 표준 아이콘 배경
|
||
|
|
<div className="p-2 bg-primary/10 rounded-lg">
|
||
|
|
<Icon className="w-6 h-6 text-primary" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
// ✅ 다른 색상 옵션
|
||
|
|
bg-blue-100 text-blue-600
|
||
|
|
bg-green-100 text-green-600
|
||
|
|
bg-yellow-100 text-yellow-600
|
||
|
|
bg-purple-100 text-purple-600
|
||
|
|
bg-orange-100 text-orange-600
|
||
|
|
bg-red-100 text-red-600
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. 반응형 디자인
|
||
|
|
|
||
|
|
### 6.1 브레이크포인트
|
||
|
|
```tsx
|
||
|
|
// 모바일 우선
|
||
|
|
className="grid-cols-1" // < 768px
|
||
|
|
className="md:grid-cols-2" // 768px ~ 1023px (태블릿)
|
||
|
|
className="lg:grid-cols-4" // 1024px+ (데스크톱)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.2 통계 카드 레이아웃
|
||
|
|
```tsx
|
||
|
|
// ✅ 표준 반응형 그리드
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
|
|
<StatCard />
|
||
|
|
<StatCard />
|
||
|
|
<StatCard />
|
||
|
|
<StatCard />
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.3 폼 레이아웃
|
||
|
|
```tsx
|
||
|
|
// ✅ 반응형 폼
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
|
|
<FormField />
|
||
|
|
<FormField />
|
||
|
|
<FormField />
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. 컴포넌트 사용 우선순위
|
||
|
|
|
||
|
|
### 7.1 Organisms (가장 높은 우선순위)
|
||
|
|
1. **PageLayout** - 모든 페이지 필수
|
||
|
|
2. **PageHeader** - 모든 페이지 필수
|
||
|
|
3. **StatCards** - 통계 카드가 있는 페이지
|
||
|
|
4. **SearchFilter** - 검색/필터가 있는 페이지
|
||
|
|
5. **DataTable** - 테이블이 있는 페이지
|
||
|
|
6. **FormSection** - 폼이 있는 페이지
|
||
|
|
7. **EmptyState** - 데이터가 없을 때
|
||
|
|
|
||
|
|
### 7.2 Molecules
|
||
|
|
1. **StatCard** - 개별 통계 카드
|
||
|
|
2. **FormField** - 개별 폼 필드
|
||
|
|
3. **SearchBar** - 검색 입력
|
||
|
|
4. **StatusBadge** - 상태 표시
|
||
|
|
5. **TableActions** - 테이블 액션 버튼
|
||
|
|
|
||
|
|
### 7.3 ShadCN UI Components
|
||
|
|
1. **Card** - 카드 레이아웃
|
||
|
|
2. **Button** - 버튼
|
||
|
|
3. **Input** - 입력 필드
|
||
|
|
4. **Select** - 드롭다운
|
||
|
|
5. **Dialog** - 다이얼로그
|
||
|
|
6. **Table** - 테이블 (DataTable 내부에서 사용)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. 페이지별 체크리스트
|
||
|
|
|
||
|
|
### ✅ 목록 페이지 체크리스트
|
||
|
|
- [ ] PageLayout으로 감싸져 있는가?
|
||
|
|
- [ ] PageHeader를 사용하고 있는가?
|
||
|
|
- [ ] 아이콘이 PageHeader에 전달되었는가?
|
||
|
|
- [ ] 통계 카드가 4개인가?
|
||
|
|
- [ ] StatCards 컴포넌트를 사용하는가?
|
||
|
|
- [ ] SearchFilter 컴포넌트를 사용하는가?
|
||
|
|
- [ ] DataTable 컴포넌트를 사용하는가?
|
||
|
|
- [ ] 데이터가 없을 때 EmptyState를 보여주는가?
|
||
|
|
- [ ] 반응형 레이아웃인가? (grid-cols-1 md:grid-cols-2 lg:grid-cols-4)
|
||
|
|
|
||
|
|
### ✅ 대시보드 페이지 체크리스트
|
||
|
|
- [ ] PageLayout으로 감싸져 있는가?
|
||
|
|
- [ ] PageHeader를 사용하고 있는가?
|
||
|
|
- [ ] StatCards를 사용하는가?
|
||
|
|
- [ ] 위젯이 반응형 그리드로 배치되어 있는가?
|
||
|
|
- [ ] 타이포그래피 클래스를 사용하지 않았는가?
|
||
|
|
|
||
|
|
### ✅ 폼 페이지 체크리스트
|
||
|
|
- [ ] PageLayout (maxWidth="xl")으로 감싸져 있는가?
|
||
|
|
- [ ] PageHeader를 사용하고 있는가?
|
||
|
|
- [ ] 액션 버튼이 PageHeader에 전달되었는가?
|
||
|
|
- [ ] FormSection으로 섹션을 구분했는가?
|
||
|
|
- [ ] FormField를 사용하는가?
|
||
|
|
- [ ] 반응형 폼 레이아웃인가?
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. 마이그레이션 예시
|
||
|
|
|
||
|
|
### Before (❌)
|
||
|
|
```tsx
|
||
|
|
export function OldPage() {
|
||
|
|
return (
|
||
|
|
<div className="p-6 space-y-6 bg-gradient-to-br from-slate-50 to-blue-50/30 min-h-screen">
|
||
|
|
<div className="flex justify-between items-center">
|
||
|
|
<div>
|
||
|
|
<h1 className="text-3xl font-bold text-foreground">페이지 제목</h1>
|
||
|
|
<p className="text-sm text-muted-foreground mt-1">설명</p>
|
||
|
|
</div>
|
||
|
|
<Button>등록</Button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>통계 1</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<p className="text-2xl font-bold">100</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
{/* ... */}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div className="bg-card border rounded-xl p-4">
|
||
|
|
<Input placeholder="검색..." />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<Card>
|
||
|
|
<Table>
|
||
|
|
{/* ... */}
|
||
|
|
</Table>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### After (✅)
|
||
|
|
```tsx
|
||
|
|
import { PageLayout } from "./organisms/PageLayout";
|
||
|
|
import { PageHeader } from "./organisms/PageHeader";
|
||
|
|
import { StatCards } from "./organisms/StatCards";
|
||
|
|
import { SearchFilter } from "./organisms/SearchFilter";
|
||
|
|
import { DataTable } from "./organisms/DataTable";
|
||
|
|
import { Package } from "lucide-react";
|
||
|
|
|
||
|
|
export function NewPage() {
|
||
|
|
return (
|
||
|
|
<PageLayout>
|
||
|
|
<PageHeader
|
||
|
|
title="페이지 제목"
|
||
|
|
description="설명"
|
||
|
|
icon={Package}
|
||
|
|
actions={<Button>등록</Button>}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<StatCards
|
||
|
|
stats={[
|
||
|
|
{ title: "통계 1", value: "100", icon: Package, color: "blue" },
|
||
|
|
{ title: "통계 2", value: "200", icon: Package, color: "green" },
|
||
|
|
{ title: "통계 3", value: "300", icon: Package, color: "yellow" },
|
||
|
|
{ title: "통계 4", value: "400", icon: Package, color: "purple" },
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<SearchFilter
|
||
|
|
searchValue={searchValue}
|
||
|
|
onSearchChange={setSearchValue}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<DataTable
|
||
|
|
columns={columns}
|
||
|
|
data={data}
|
||
|
|
/>
|
||
|
|
</PageLayout>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10. 자주 발생하는 실수
|
||
|
|
|
||
|
|
### ❌ 실수 1: 직접 헤더 작성
|
||
|
|
```tsx
|
||
|
|
<div className="flex justify-between">
|
||
|
|
<h1>제목</h1>
|
||
|
|
<Button>등록</Button>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### ✅ 해결
|
||
|
|
```tsx
|
||
|
|
<PageHeader
|
||
|
|
title="제목"
|
||
|
|
actions={<Button>등록</Button>}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### ❌ 실수 2: 커스텀 카드 스타일
|
||
|
|
```tsx
|
||
|
|
<div className="bg-card border border-border/20 rounded-xl p-6">
|
||
|
|
<h3 className="text-lg font-semibold">제목</h3>
|
||
|
|
<p>내용</p>
|
||
|
|
</div>
|
||
|
|
```
|
||
|
|
|
||
|
|
### ✅ 해결
|
||
|
|
```tsx
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>제목</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<p>내용</p>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### ❌ 실수 3: 타이포그래피 클래스 사용
|
||
|
|
```tsx
|
||
|
|
<h1 className="text-3xl font-bold">제목</h1>
|
||
|
|
```
|
||
|
|
|
||
|
|
### ✅ 해결
|
||
|
|
```tsx
|
||
|
|
<h1>제목</h1> {/* globals.css에 정의된 스타일 자동 적용 */}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11. 개발 워크플로우
|
||
|
|
|
||
|
|
### 새 페이지 생성 시
|
||
|
|
1. ✅ PageLayout으로 시작
|
||
|
|
2. ✅ PageHeader 추가
|
||
|
|
3. ✅ 필요한 Organisms 컴포넌트 추가 (StatCards, SearchFilter, DataTable 등)
|
||
|
|
4. ✅ ShadCN UI 컴포넌트로 나머지 구현
|
||
|
|
5. ✅ 타이포그래피 클래스 사용하지 않기
|
||
|
|
6. ✅ 반응형 확인 (모바일, 태블릿, 데스크톱)
|
||
|
|
7. ✅ 다크모드 확인
|
||
|
|
|
||
|
|
### 기존 페이지 수정 시
|
||
|
|
1. ✅ 현재 PageLayout 사용 여부 확인
|
||
|
|
2. ✅ PageHeader 사용 여부 확인
|
||
|
|
3. ✅ 커스텀 스타일 제거
|
||
|
|
4. ✅ Organisms 컴포넌트로 교체
|
||
|
|
5. ✅ 타이포그래피 클래스 제거
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 12. 참고 자료
|
||
|
|
|
||
|
|
- **디자인 토큰**: `/styles/globals.css`
|
||
|
|
- **공통 컴포넌트**: `/components/organisms/`, `/components/molecules/`
|
||
|
|
- **ShadCN UI**: `/components/ui/`
|
||
|
|
- **디자인 시스템 관리**: "기준정보 > 디자인시스템" 메뉴
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 요약
|
||
|
|
|
||
|
|
### 핵심 원칙 3가지
|
||
|
|
1. **PageLayout + PageHeader는 필수**
|
||
|
|
2. **타이포그래피 클래스 사용 금지**
|
||
|
|
3. **Organisms 컴포넌트 최대한 활용**
|
||
|
|
|
||
|
|
이 가이드를 따르면 모든 페이지가 일관된 디자인을 유지할 수 있습니다.
|