417 lines
11 KiB
Markdown
417 lines
11 KiB
Markdown
|
|
# 레이아웃 추가 가이드
|
||
|
|
|
||
|
|
화면관리 시스템에서 새로운 레이아웃을 추가하는 방법을 설명합니다.
|
||
|
|
|
||
|
|
## 📋 목차
|
||
|
|
|
||
|
|
1. [CLI를 이용한 자동 생성](#cli를-이용한-자동-생성)
|
||
|
|
2. [생성된 파일 구조](#생성된-파일-구조)
|
||
|
|
3. [레이아웃 커스터마이징](#레이아웃-커스터마이징)
|
||
|
|
4. [고급 설정](#고급-설정)
|
||
|
|
5. [문제 해결](#문제-해결)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 CLI를 이용한 자동 생성
|
||
|
|
|
||
|
|
### 기본 사용법
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 기본 형태
|
||
|
|
node scripts/create-layout.js <레이아웃이름> [옵션]
|
||
|
|
|
||
|
|
# 예시
|
||
|
|
node scripts/create-layout.js card-grid --category=dashboard --zones=6 --description="카드 형태의 그리드 레이아웃"
|
||
|
|
```
|
||
|
|
|
||
|
|
### 📝 사용 가능한 옵션
|
||
|
|
|
||
|
|
| 옵션 | 필수 여부 | 기본값 | 설명 | 예시 |
|
||
|
|
| --------------- | --------- | ----------- | ----------------- | ----------------------------------- |
|
||
|
|
| `--category` | 선택 | `basic` | 레이아웃 카테고리 | `--category=dashboard` |
|
||
|
|
| `--zones` | 선택 | `2` | 영역 개수 | `--zones=4` |
|
||
|
|
| `--description` | 선택 | 자동 생성 | 레이아웃 설명 | `--description="사이드바 레이아웃"` |
|
||
|
|
| `--author` | 선택 | `Developer` | 작성자 이름 | `--author="김개발"` |
|
||
|
|
|
||
|
|
### 🏷️ 카테고리 종류
|
||
|
|
|
||
|
|
| 카테고리 | 설명 | 예시 레이아웃 |
|
||
|
|
| ------------ | ------------- | ------------------------ |
|
||
|
|
| `basic` | 기본 레이아웃 | 그리드, 플렉스박스, 분할 |
|
||
|
|
| `navigation` | 네비게이션 | 탭, 아코디언, 메뉴 |
|
||
|
|
| `dashboard` | 대시보드 | 카드, 위젯, 차트 |
|
||
|
|
| `content` | 콘텐츠 | 헤더-본문, 영웅 섹션 |
|
||
|
|
| `form` | 폼 | 입력 폼, 설정 패널 |
|
||
|
|
| `table` | 테이블 | 데이터 테이블, 목록 |
|
||
|
|
|
||
|
|
### 💡 이름 규칙
|
||
|
|
|
||
|
|
레이아웃 이름은 **하이픈(`-`)을 사용한 kebab-case**로 입력하면 자동으로 변환됩니다:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 입력: hero-section
|
||
|
|
📁 디렉토리: hero-section/
|
||
|
|
🔖 ID: hero-section
|
||
|
|
📄 클래스명: HeroSection
|
||
|
|
🔧 변수명: heroSection
|
||
|
|
```
|
||
|
|
|
||
|
|
#### ✅ 올바른 이름 예시
|
||
|
|
|
||
|
|
- `card-grid`
|
||
|
|
- `side-navigation`
|
||
|
|
- `data-table`
|
||
|
|
- `hero-section`
|
||
|
|
- `my-awesome-layout`
|
||
|
|
|
||
|
|
#### ❌ 피해야 할 이름
|
||
|
|
|
||
|
|
- `CardGrid` (파스칼케이스)
|
||
|
|
- `card_grid` (스네이크케이스)
|
||
|
|
- `cardGrid` (카멜케이스)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📂 생성된 파일 구조
|
||
|
|
|
||
|
|
CLI로 레이아웃을 생성하면 다음과 같은 파일들이 자동으로 생성됩니다:
|
||
|
|
|
||
|
|
```
|
||
|
|
lib/registry/layouts/your-layout/
|
||
|
|
├── index.ts # 레이아웃 정의 및 등록
|
||
|
|
├── YourLayoutLayout.tsx # React 컴포넌트
|
||
|
|
├── YourLayoutRenderer.tsx # 렌더링 로직
|
||
|
|
├── config.ts # 기본 설정
|
||
|
|
├── types.ts # 타입 정의
|
||
|
|
└── README.md # 문서
|
||
|
|
```
|
||
|
|
|
||
|
|
### 🔧 각 파일의 역할
|
||
|
|
|
||
|
|
#### 1. `index.ts` - 레이아웃 정의
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const YourLayoutDefinition = createLayoutDefinition({
|
||
|
|
id: "your-layout",
|
||
|
|
name: "yourLayout",
|
||
|
|
nameEng: "Your Layout",
|
||
|
|
description: "사용자 정의 레이아웃입니다",
|
||
|
|
category: "basic",
|
||
|
|
component: YourLayoutWrapper,
|
||
|
|
defaultConfig: {
|
||
|
|
/* 기본 설정 */
|
||
|
|
},
|
||
|
|
defaultZones: [
|
||
|
|
/* 기본 영역들 */
|
||
|
|
],
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2. `YourLayoutLayout.tsx` - React 컴포넌트
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const YourLayoutLayout: React.FC<YourLayoutProps> = ({ layout, isDesignMode, renderer, ...props }) => {
|
||
|
|
// 레이아웃 UI 구현
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 3. `YourLayoutRenderer.tsx` - 렌더링 로직
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export class YourLayoutRenderer extends AutoRegisteringLayoutRenderer {
|
||
|
|
static layoutDefinition = YourLayoutDefinition;
|
||
|
|
|
||
|
|
render(): React.ReactElement {
|
||
|
|
return <YourLayoutLayout {...this.props} renderer={this} />;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎨 레이아웃 커스터마이징
|
||
|
|
|
||
|
|
### 1. 기본 구조 수정
|
||
|
|
|
||
|
|
생성된 `YourLayoutLayout.tsx`에서 레이아웃 구조를 정의합니다:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const YourLayoutLayout: React.FC<YourLayoutProps> = ({
|
||
|
|
layout,
|
||
|
|
isDesignMode = false,
|
||
|
|
renderer,
|
||
|
|
}) => {
|
||
|
|
const yourLayoutConfig = layout.layoutConfig.yourLayout;
|
||
|
|
const containerStyle = renderer.getLayoutContainerStyle();
|
||
|
|
|
||
|
|
// 레이아웃별 커스텀 스타일
|
||
|
|
const yourLayoutStyle: React.CSSProperties = {
|
||
|
|
...containerStyle,
|
||
|
|
display: "grid",
|
||
|
|
gridTemplateColumns: "repeat(3, 1fr)",
|
||
|
|
gridTemplateRows: "repeat(2, 200px)",
|
||
|
|
gap: "16px",
|
||
|
|
padding: "16px",
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div style={yourLayoutStyle}>
|
||
|
|
{layout.zones.map((zone) => {
|
||
|
|
const zoneChildren = renderer.getZoneChildren(zone.id);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div key={zone.id} className="zone-area">
|
||
|
|
{/* 존 렌더링 */}
|
||
|
|
{renderer.renderZone(zone, zoneChildren)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 설정 옵션 추가
|
||
|
|
|
||
|
|
`config.ts`에서 레이아웃별 설정을 정의합니다:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export const YourLayoutConfig = {
|
||
|
|
defaultConfig: {
|
||
|
|
yourLayout: {
|
||
|
|
columns: 3, // 열 개수
|
||
|
|
rows: 2, // 행 개수
|
||
|
|
gap: 16, // 간격
|
||
|
|
aspectRatio: "16:9", // 비율
|
||
|
|
backgroundColor: "#ffffff",
|
||
|
|
borderRadius: "8px",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 타입 정의
|
||
|
|
|
||
|
|
`types.ts`에서 설정 타입을 정의합니다:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
export interface YourLayoutConfig {
|
||
|
|
columns?: number;
|
||
|
|
rows?: number;
|
||
|
|
gap?: number;
|
||
|
|
aspectRatio?: string;
|
||
|
|
backgroundColor?: string;
|
||
|
|
borderRadius?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface YourLayoutProps extends LayoutRendererProps {
|
||
|
|
renderer: YourLayoutRenderer;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 고급 설정
|
||
|
|
|
||
|
|
### 영역(Zone) 커스터마이징
|
||
|
|
|
||
|
|
영역별로 다른 스타일을 적용하려면:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 영역별 스타일 계산
|
||
|
|
const getZoneStyle = (zone: LayoutZone, index: number): React.CSSProperties => {
|
||
|
|
const baseStyle = {
|
||
|
|
backgroundColor: "#f8f9fa",
|
||
|
|
border: "1px solid #e9ecef",
|
||
|
|
borderRadius: "4px",
|
||
|
|
padding: "12px",
|
||
|
|
};
|
||
|
|
|
||
|
|
// 첫 번째 영역은 다른 스타일
|
||
|
|
if (index === 0) {
|
||
|
|
return {
|
||
|
|
...baseStyle,
|
||
|
|
backgroundColor: "#e3f2fd",
|
||
|
|
gridColumn: "1 / -1", // 전체 너비
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
return baseStyle;
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 반응형 레이아웃
|
||
|
|
|
||
|
|
미디어 쿼리를 사용한 반응형 구현:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const getResponsiveStyle = (): React.CSSProperties => {
|
||
|
|
return {
|
||
|
|
display: "grid",
|
||
|
|
gridTemplateColumns: `repeat(auto-fit, minmax(300px, 1fr))`,
|
||
|
|
gap: "16px",
|
||
|
|
// CSS-in-JS에서는 미디어 쿼리를 직접 사용할 수 없으므로
|
||
|
|
// CSS 클래스나 컨테이너 쿼리 사용 권장
|
||
|
|
};
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 애니메이션 추가
|
||
|
|
|
||
|
|
CSS 애니메이션을 포함한 레이아웃:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const animatedStyle: React.CSSProperties = {
|
||
|
|
transition: "all 0.3s ease-in-out",
|
||
|
|
opacity: isDesignMode ? 0.9 : 1,
|
||
|
|
transform: isDesignMode ? "scale(0.98)" : "scale(1)",
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 자동 등록 시스템
|
||
|
|
|
||
|
|
### Hot Reload 지원
|
||
|
|
|
||
|
|
새 레이아웃은 다음과 같이 자동으로 등록됩니다:
|
||
|
|
|
||
|
|
1. **파일 저장 시**: Hot Reload로 즉시 반영
|
||
|
|
2. **자동 등록**: `AutoRegisteringLayoutRenderer` 상속으로 자동 등록
|
||
|
|
3. **즉시 사용**: 화면편집기에서 바로 사용 가능
|
||
|
|
|
||
|
|
### 수동 등록 (필요한 경우)
|
||
|
|
|
||
|
|
`lib/registry/layouts/index.ts`에 직접 추가:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 새 구조 레이아웃들 (자동 등록)
|
||
|
|
import "./your-layout/YourLayoutRenderer";
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🛠️ 문제 해결
|
||
|
|
|
||
|
|
### 자주 발생하는 오류
|
||
|
|
|
||
|
|
#### 1. "Cannot read properties of undefined" 오류
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ 문제: 설정이 없을 때 오류
|
||
|
|
const config = layout.layoutConfig.yourLayout.someProperty;
|
||
|
|
|
||
|
|
// ✅ 해결: 안전한 접근
|
||
|
|
const config = layout.layoutConfig.yourLayout?.someProperty || defaultValue;
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2. "React does not recognize prop" 경고
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// ❌ 문제: 모든 props를 DOM에 전달
|
||
|
|
<div {...props}>
|
||
|
|
|
||
|
|
// ✅ 해결: DOM props만 전달
|
||
|
|
const { layout, isDesignMode, renderer, ...domProps } = props;
|
||
|
|
<div {...domProps}>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 3. 레이아웃이 화면편집기에 나타나지 않음
|
||
|
|
|
||
|
|
1. **파일 저장 확인**: 모든 파일이 저장되었는지 확인
|
||
|
|
2. **자동 등록 확인**: `YourLayoutRenderer.registerSelf()` 호출 여부
|
||
|
|
3. **브라우저 새로고침**: 캐시 문제일 수 있음
|
||
|
|
4. **개발자 도구**: `window.__LAYOUT_REGISTRY__.list()` 로 등록 상태 확인
|
||
|
|
|
||
|
|
### 디버깅 도구
|
||
|
|
|
||
|
|
#### 브라우저 개발자 도구
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// 등록된 레이아웃 목록 확인
|
||
|
|
window.__LAYOUT_REGISTRY__.list();
|
||
|
|
|
||
|
|
// 특정 레이아웃 정보 확인
|
||
|
|
window.__LAYOUT_REGISTRY__.get("your-layout");
|
||
|
|
|
||
|
|
// 레지스트리 통계
|
||
|
|
window.__LAYOUT_REGISTRY__.stats();
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📖 예시: 완전한 레이아웃 생성
|
||
|
|
|
||
|
|
### 1. CLI로 생성
|
||
|
|
|
||
|
|
```bash
|
||
|
|
node scripts/create-layout.js pricing-table --category=content --zones=4 --description="가격표 레이아웃" --author="개발팀"
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 생성 결과
|
||
|
|
|
||
|
|
```
|
||
|
|
✅ 레이아웃 생성 완료!
|
||
|
|
📁 이름: pricingTable
|
||
|
|
🔖 ID: pricing-table
|
||
|
|
📂 카테고리: content
|
||
|
|
🎯 존 개수: 4
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. 커스터마이징
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// PricingTableLayout.tsx
|
||
|
|
export const PricingTableLayout: React.FC<PricingTableLayoutProps> = ({
|
||
|
|
layout,
|
||
|
|
isDesignMode,
|
||
|
|
renderer,
|
||
|
|
}) => {
|
||
|
|
const pricingTableStyle: React.CSSProperties = {
|
||
|
|
display: "grid",
|
||
|
|
gridTemplateColumns: "repeat(auto-fit, minmax(250px, 1fr))",
|
||
|
|
gap: "24px",
|
||
|
|
padding: "32px",
|
||
|
|
backgroundColor: "#f8f9fa",
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div style={pricingTableStyle}>
|
||
|
|
{layout.zones.map((zone, index) => (
|
||
|
|
<div
|
||
|
|
key={zone.id}
|
||
|
|
className={`pricing-card ${index === 1 ? 'featured' : ''}`}
|
||
|
|
style={{
|
||
|
|
backgroundColor: "white",
|
||
|
|
borderRadius: "12px",
|
||
|
|
padding: "24px",
|
||
|
|
boxShadow: index === 1 ? "0 8px 32px rgba(0,0,0,0.1)" : "0 2px 8px rgba(0,0,0,0.1)",
|
||
|
|
border: index === 1 ? "2px solid #3b82f6" : "1px solid #e5e7eb",
|
||
|
|
transform: index === 1 ? "scale(1.05)" : "scale(1)",
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
{renderer.renderZone(zone, renderer.getZoneChildren(zone.id))}
|
||
|
|
</div>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. 즉시 사용 가능
|
||
|
|
|
||
|
|
레이아웃이 자동으로 등록되어 화면편집기에서 바로 사용할 수 있습니다!
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 마무리
|
||
|
|
|
||
|
|
새로운 CLI 방식으로 레이아웃 추가가 매우 간단해졌습니다:
|
||
|
|
|
||
|
|
1. **한 줄 명령어**로 모든 파일 자동 생성
|
||
|
|
2. **타입 안전성** 보장
|
||
|
|
3. **자동 등록**으로 즉시 사용 가능
|
||
|
|
4. **Hot Reload** 지원으로 빠른 개발
|
||
|
|
|
||
|
|
더 자세한 정보가 필요하면 각 레이아웃의 `README.md` 파일을 참고하세요! 🚀
|