# POP 그리드 시스템 도입 계획 > 작성일: 2026-02-05 > 상태: 계획 승인, 구현 대기 --- ## 개요 ### 목표 현재 Flexbox 기반 v4 시스템을 **CSS Grid 기반 v5 시스템**으로 전환하여 4~14인치 화면에서 일관된 배치와 예측 가능한 반응형 레이아웃 구현 ### 핵심 변경점 | 항목 | v4 (현재) | v5 (그리드) | |------|----------|-------------| | 배치 방식 | Flexbox 흐름 | **Grid 위치 지정** | | 크기 단위 | 픽셀 (200px) | **칸 (col, row)** | | 위치 지정 | 순서대로 자동 | **열/행 좌표** | | 줄바꿈 | 자동 (넘치면) | **명시적 (row 지정)** | --- ## Phase 구조 ``` [Phase 5.1] [Phase 5.2] [Phase 5.3] [Phase 5.4] 그리드 타입 정의 → 그리드 렌더러 → 디자이너 UI → 반응형 자동화 1주 1주 1~2주 1주 ``` --- ## Phase 5.1: 그리드 타입 정의 ### 목표 v5 레이아웃 데이터 구조 설계 ### 작업 항목 - [ ] `PopLayoutDataV5` 인터페이스 정의 - [ ] `PopGridConfig` 인터페이스 (그리드 설정) - [ ] `PopComponentPositionV5` 인터페이스 (위치: col, row, colSpan, rowSpan) - [ ] `PopSizeConstraintV5` 인터페이스 (칸 기반 크기) - [ ] 브레이크포인트 상수 정의 - [ ] `createEmptyPopLayoutV5()` 생성 함수 - [ ] `isV5Layout()` 타입 가드 ### 데이터 구조 설계 ```typescript // v5 레이아웃 interface PopLayoutDataV5 { version: "pop-5.0"; // 그리드 설정 gridConfig: PopGridConfig; // 컴포넌트 목록 components: Record; // 모드별 오버라이드 overrides?: { mobile_portrait?: PopModeOverrideV5; mobile_landscape?: PopModeOverrideV5; tablet_portrait?: PopModeOverrideV5; }; // 기존 호환 dataFlow: PopDataFlow; settings: PopGlobalSettingsV5; } // 그리드 설정 interface PopGridConfig { // 모드별 칸 수 columns: { tablet_landscape: 12; // 기본 (10~14인치) tablet_portrait: 8; // 8~10인치 세로 mobile_landscape: 6; // 6~8인치 가로 mobile_portrait: 4; // 4~6인치 세로 }; // 행 높이 (px) - 1행의 기본 높이 rowHeight: number; // 기본 48px // 간격 gap: number; // 기본 8px padding: number; // 기본 16px } // 컴포넌트 정의 interface PopComponentDefinitionV5 { id: string; type: PopComponentType; label?: string; // 위치 (열/행 좌표) - 기본 모드(태블릿 가로) 기준 position: { col: number; // 시작 열 (1부터) row: number; // 시작 행 (1부터) colSpan: number; // 차지할 열 수 (1~12) rowSpan: number; // 차지할 행 수 (1~) }; // 모드별 표시/숨김 visibility?: { tablet_landscape?: boolean; tablet_portrait?: boolean; mobile_landscape?: boolean; mobile_portrait?: boolean; }; // 기존 속성 dataBinding?: PopDataBinding; config?: PopComponentConfig; } ``` ### 브레이크포인트 정의 ```typescript // 브레이크포인트 상수 const GRID_BREAKPOINTS = { // 4~6인치 모바일 세로 mobile_portrait: { maxWidth: 599, columns: 4, rowHeight: 40, gap: 8, padding: 12, }, // 6~8인치 모바일 가로 / 작은 태블릿 mobile_landscape: { minWidth: 600, maxWidth: 839, columns: 6, rowHeight: 44, gap: 8, padding: 16, }, // 8~10인치 태블릿 세로 tablet_portrait: { minWidth: 840, maxWidth: 1023, columns: 8, rowHeight: 48, gap: 12, padding: 16, }, // 10~14인치 태블릿 가로 (기본) tablet_landscape: { minWidth: 1024, columns: 12, rowHeight: 48, gap: 16, padding: 24, }, } as const; ``` ### 산출물 - `frontend/components/pop/designer/types/pop-layout-v5.ts` --- ## Phase 5.2: 그리드 렌더러 ### 목표 CSS Grid 기반 렌더러 구현 ### 작업 항목 - [ ] `PopGridRenderer.tsx` 생성 - [ ] CSS Grid 스타일 계산 로직 - [ ] 브레이크포인트 감지 및 칸 수 자동 변경 - [ ] 컴포넌트 위치 렌더링 (grid-column, grid-row) - [ ] 모드별 자동 위치 재계산 (12칸→4칸 변환) - [ ] visibility 처리 - [ ] 기존 PopFlexRenderer와 공존 ### 렌더링 로직 ```typescript // CSS Grid 스타일 생성 function calculateGridStyle(config: PopGridConfig, mode: string): React.CSSProperties { const columns = config.columns[mode]; const { rowHeight, gap, padding } = config; return { display: 'grid', gridTemplateColumns: `repeat(${columns}, 1fr)`, gridAutoRows: `${rowHeight}px`, gap: `${gap}px`, padding: `${padding}px`, }; } // 컴포넌트 위치 스타일 function calculatePositionStyle( position: PopComponentPositionV5['position'], sourceColumns: number, // 원본 모드 칸 수 (12) targetColumns: number // 현재 모드 칸 수 (4) ): React.CSSProperties { // 12칸 → 4칸 변환 예시 // col: 7, colSpan: 3 → col: 3, colSpan: 1 const ratio = targetColumns / sourceColumns; const newCol = Math.max(1, Math.ceil(position.col * ratio)); const newColSpan = Math.max(1, Math.round(position.colSpan * ratio)); return { gridColumn: `${newCol} / span ${Math.min(newColSpan, targetColumns - newCol + 1)}`, gridRow: `${position.row} / span ${position.rowSpan}`, }; } ``` ### 산출물 - `frontend/components/pop/designer/renderers/PopGridRenderer.tsx` --- ## Phase 5.3: 디자이너 UI ### 목표 그리드 기반 편집 UI 구현 ### 작업 항목 - [ ] `PopCanvasV5.tsx` 생성 (그리드 캔버스) - [ ] 그리드 배경 표시 (바둑판 모양) - [ ] 컴포넌트 드래그 배치 (칸에 스냅) - [ ] 컴포넌트 리사이즈 (칸 단위) - [ ] 위치 편집 패널 (col, row, colSpan, rowSpan) - [ ] 모드 전환 시 그리드 칸 수 변경 표시 - [ ] v4/v5 자동 판별 및 전환 ### UI 구조 ``` ┌─────────────────────────────────────────────────────────────────┐ │ ← 목록 화면명 *변경됨 [↶][↷] 그리드 레이아웃 (v5) [저장] │ ├─────────────────────────────────────────────────────────────────┤ │ 미리보기: [모바일↕ 4칸] [모바일↔ 6칸] [태블릿↕ 8칸] [태블릿↔ 12칸] │ ├────────────┬────────────────────────────────────┬───────────────┤ │ │ 1 2 3 4 5 6 ... 12 │ │ │ 컴포넌트 │ ┌───────────┬───────────┐ │ 위치 │ │ │1│ A │ B │ │ 열: [1-12] │ │ 필드 │ ├───────────┴───────────┤ │ 행: [1-99] │ │ 버튼 │2│ C │ │ 너비: [1-12]│ │ 리스트 │ ├───────────┬───────────┤ │ 높이: [1-10]│ │ 인디케이터 │3│ D │ E │ │ │ │ ... │ └───────────┴───────────┘ │ 표시 설정 │ │ │ │ [x] 태블릿↔ │ │ │ (그리드 배경 표시) │ [x] 모바일↕ │ └────────────┴────────────────────────────────────┴───────────────┘ ``` ### 드래그 앤 드롭 로직 ```typescript // 마우스 위치 → 그리드 좌표 변환 function mouseToGridPosition( mouseX: number, mouseY: number, gridConfig: PopGridConfig, canvasRect: DOMRect ): { col: number; row: number } { const { columns, rowHeight, gap, padding } = gridConfig; // 캔버스 내 상대 위치 const relX = mouseX - canvasRect.left - padding; const relY = mouseY - canvasRect.top - padding; // 칸 너비 계산 const totalGap = gap * (columns - 1); const colWidth = (canvasRect.width - padding * 2 - totalGap) / columns; // 그리드 좌표 계산 const col = Math.max(1, Math.min(columns, Math.floor(relX / (colWidth + gap)) + 1)); const row = Math.max(1, Math.floor(relY / (rowHeight + gap)) + 1); return { col, row }; } ``` ### 산출물 - `frontend/components/pop/designer/PopCanvasV5.tsx` - `frontend/components/pop/designer/panels/ComponentEditorPanelV5.tsx` --- ## Phase 5.4: 반응형 자동화 ### 목표 모드 전환 시 자동 레이아웃 조정 ### 작업 항목 - [ ] 12칸 → 4칸 자동 변환 알고리즘 - [ ] 겹침 감지 및 자동 재배치 - [ ] 모드별 오버라이드 저장 - [ ] "자동 배치" vs "수동 고정" 선택 - [ ] 변환 미리보기 ### 자동 변환 알고리즘 ```typescript // 12칸 → 4칸 변환 전략 function convertLayoutToMode( components: PopComponentDefinitionV5[], sourceMode: 'tablet_landscape', // 12칸 targetMode: 'mobile_portrait' // 4칸 ): PopComponentDefinitionV5[] { const sourceColumns = 12; const targetColumns = 4; const ratio = targetColumns / sourceColumns; // 0.333 // 1. 각 컴포넌트 위치 변환 const converted = components.map(comp => { const newCol = Math.max(1, Math.ceil(comp.position.col * ratio)); const newColSpan = Math.max(1, Math.round(comp.position.colSpan * ratio)); return { ...comp, position: { ...comp.position, col: newCol, colSpan: Math.min(newColSpan, targetColumns), }, }; }); // 2. 겹침 감지 및 해결 return resolveOverlaps(converted, targetColumns); } // 겹침 해결 (아래로 밀기) function resolveOverlaps( components: PopComponentDefinitionV5[], columns: number ): PopComponentDefinitionV5[] { // 행 단위로 그리드 점유 상태 추적 const grid: boolean[][] = []; // row 순서대로 처리 const sorted = [...components].sort((a, b) => a.position.row - b.position.row || a.position.col - b.position.col ); return sorted.map(comp => { let { row, col, colSpan, rowSpan } = comp.position; // 배치 가능한 위치 찾기 while (isOccupied(grid, row, col, colSpan, rowSpan, columns)) { row++; // 아래로 이동 } // 그리드에 표시 markOccupied(grid, row, col, colSpan, rowSpan); return { ...comp, position: { row, col, colSpan, rowSpan }, }; }); } ``` ### 산출물 - `frontend/components/pop/designer/utils/gridLayoutUtils.ts` --- ## 마이그레이션 전략 ### v4 → v5 변환 ```typescript function migrateV4ToV5(layoutV4: PopLayoutDataV4): PopLayoutDataV5 { const componentsList = Object.values(layoutV4.components); // Flexbox 순서 → Grid 위치 변환 let currentRow = 1; let currentCol = 1; const columns = 12; const componentsV5: Record = {}; componentsList.forEach((comp, index) => { // 기본 크기 추정 (픽셀 → 칸) const colSpan = Math.max(1, Math.round((comp.size.fixedWidth || 100) / 85)); const rowSpan = Math.max(1, Math.round((comp.size.fixedHeight || 48) / 48)); // 줄바꿈 체크 if (currentCol + colSpan - 1 > columns) { currentRow++; currentCol = 1; } componentsV5[comp.id] = { ...comp, position: { col: currentCol, row: currentRow, colSpan, rowSpan, }, }; currentCol += colSpan; }); return { version: "pop-5.0", gridConfig: { /* 기본값 */ }, components: componentsV5, dataFlow: layoutV4.dataFlow, settings: { /* 변환 */ }, }; } ``` ### 하위 호환 | 버전 | 처리 방식 | |------|----------| | v1~v2 | v3로 변환 후 v5로 | | v3 | v5로 직접 변환 | | v4 | v5로 직접 변환 | | v5 | 그대로 사용 | --- ## 일정 (예상) | Phase | 작업 | 예상 기간 | |-------|------|----------| | 5.1 | 타입 정의 | 2~3일 | | 5.2 | 그리드 렌더러 | 3~5일 | | 5.3 | 디자이너 UI | 5~7일 | | 5.4 | 반응형 자동화 | 3~5일 | | - | 테스트 및 버그 수정 | 2~3일 | | **총** | | **약 2~3주** | --- ## 리스크 및 대응 | 리스크 | 영향 | 대응 | |--------|------|------| | 기존 v4 화면 깨짐 | 높음 | 하위 호환 유지, v4 렌더러 보존 | | 자동 변환 품질 | 중간 | 수동 오버라이드로 보완 | | 드래그 UX 복잡 | 중간 | 스냅 기능으로 편의성 확보 | | 성능 저하 | 낮음 | CSS Grid는 네이티브 성능 | --- ## 성공 기준 1. **배치 예측 가능**: "2열 3행"이라고 하면 정확히 그 위치에 표시 2. **일관된 디자인**: 12칸 → 4칸 전환 시 비율 유지 3. **쉬운 편집**: 드래그로 칸에 스냅되어 배치 4. **하위 호환**: 기존 v4 화면이 정상 동작 --- ## 관련 문서 - [GRID_SYSTEM_DESIGN.md](./GRID_SYSTEM_DESIGN.md) - 그리드 시스템 설계 상세 - [PLAN.md](./PLAN.md) - 전체 POP 개발 계획 - [V4_UNIFIED_DESIGN_SPEC.md](./V4_UNIFIED_DESIGN_SPEC.md) - 현재 v4 스펙 --- *최종 업데이트: 2026-02-05*