feat(pop-designer): 반응형 레이아웃 시스템 구현

모드별(4/6/8/12칸) 컴포넌트 위치/크기 오버라이드 저장
화면 밖 컴포넌트 오른쪽 패널 표시 및 드래그 재배치
컴포넌트 숨김 기능 (드래그/H키/클릭, 드래그로 해제)
리사이즈 겹침 검사 추가
드롭 위치 그리드 범위 초과 시 자동 조정
숨김 컴포넌트 드래그 안됨 버그 수정 (상태 업데이트 통합)
This commit is contained in:
SeongHyun Kim
2026-02-05 19:16:23 +09:00
parent 9ebc8c4219
commit 726f6ac395
15 changed files with 2088 additions and 257 deletions

View File

@@ -12,6 +12,277 @@
---
## [2026-02-05 심야] 반응형 레이아웃 + 숨김 기능 완성
### 배경 (왜 이 작업이 필요했는가)
**문제 상황**:
- 12칸 모드에서 배치한 컴포넌트가 4칸 모드에서 초과됨
- 모드별로 컴포넌트 위치/크기를 다르게 설정할 방법 없음
- 특정 모드에서만 컴포넌트를 숨길 방법 없음
**해결 방향**:
- 모드별 오버라이드 시스템으로 위치/크기 개별 저장
- 화면 밖 컴포넌트를 별도 패널에 표시하고 드래그로 재배치
- 숨김 기능으로 특정 모드에서 컴포넌트 제외
### Added
- **모드별 오버라이드 시스템** (PopDesigner.tsx, pop-layout.ts)
- `PopModeOverrideV5.positions`: 모드별 컴포넌트 위치 저장
- `PopModeOverrideV5.hidden`: 모드별 숨김 컴포넌트 ID 배열
- `getEffectiveComponentPosition()`: 오버라이드된 위치 반환
- 드래그/리사이즈 시 자동으로 오버라이드 저장
- **화면 밖 컴포넌트 패널** (PopCanvas.tsx)
- `OutOfBoundsPanel`: 현재 모드에서 초과하는 컴포넌트 표시
- `OutOfBoundsItem`: 드래그 가능한 회색 컴포넌트 카드
- `isOutOfBounds()`: 컴포넌트가 현재 모드 칸 수 초과 여부 판단
- 클릭하면 숨김 패널로 이동
- **숨김 기능** (PopDesigner.tsx, PopCanvas.tsx)
- `HiddenPanel`: 숨김 처리된 컴포넌트 표시
- `HiddenItem`: 드래그로 숨김 해제 가능
- `handleHideComponent()`: 컴포넌트 숨김 처리
- `handleUnhideComponent()`: 숨김 해제 (handleMoveComponent에 통합)
- 숨김 방법 3가지:
1. 그리드 → 숨김패널 드래그
2. H키 단축키
3. 화면밖 컴포넌트 클릭
- **리사이즈 겹침 검사** (PopRenderer.tsx)
- `checkResizeOverlap()`: 리사이즈 시 다른 컴포넌트와 겹침 검사
- 겹치면 리사이즈 취소 및 toast 알림
- **원본으로 되돌리기** (PopDesigner.tsx)
- `handleResetToDefault()`: 현재 모드 오버라이드 삭제
- 자동 위치 계산으로 복원
### Fixed
- **숨김 컴포넌트 드래그 안됨 버그**
- 원인: `onUnhideComponent``onMoveComponent`가 별도로 호출되어 상태 충돌
- 해결: `handleMoveComponent`에서 숨김 해제 로직 통합 (단일 상태 업데이트)
- **그리드 범위 초과 에러**
- 원인: 드롭 위치 + colSpan이 칸 수 초과
- 해결: 드롭 시 `adjustedCol` 계산하여 자동으로 왼쪽으로 밀어서 배치
- **getAllEffectivePositions에 숨김 컴포넌트 포함**
- 해결: 숨김 및 화면밖 컴포넌트를 결과에서 제외
### Changed
- **PopModeOverrideV5 타입 확장**
```typescript
interface PopModeOverrideV5 {
positions?: Record<string, Partial<PopGridPosition>>; // 위치 오버라이드
hidden?: string[]; // 숨김 컴포넌트 ID 배열
}
```
- **12칸 모드(tablet_landscape) 제한**
- 기본 모드이므로 숨김 기능 비활성화
- 화면밖 패널 표시 안함
- 위치 변경은 기본 position에 직접 저장
- **패널 레이아웃 재구성** (PopCanvas.tsx)
- 오른쪽에 화면밖 패널 + 숨김 패널 세로 배치
- 12칸 모드에서는 패널 숨김
### Technical Details
```
오버라이드 데이터 흐름:
1. 컴포넌트 드래그/리사이즈
2. currentMode 확인
3-a. tablet_landscape → layout.components[id].position 직접 수정
3-b. 다른 모드 → layout.overrides[mode].positions[id]에 저장
4. getEffectiveComponentPosition()이 우선순위대로 반환
우선순위: overrides > autoResolved > 기본 position
숨김 기능 흐름:
1. 숨김 요청 (드래그/H키/클릭)
2. layout.overrides[mode].hidden 배열에 ID 추가
3. PopRenderer에서 hidden 체크 → 렌더링 제외
4. HiddenPanel에서 표시
5. 드래그로 그리드에 복원 → hidden 배열에서 제거 + 위치 업데이트 (단일 상태 업데이트)
```
### 수정 파일
| 파일 | 변경 내용 |
|------|----------|
| `pop-layout.ts` | PopModeOverrideV5.hidden 추가 |
| `PopDesigner.tsx` | handleHideComponent, handleUnhideComponent 통합, 오버라이드 저장 |
| `PopCanvas.tsx` | OutOfBoundsPanel, HiddenPanel 추가, 드롭 위치 자동 조정 |
| `PopRenderer.tsx` | 숨김 필터링, 리사이즈 겹침 검사 |
| `gridUtils.ts` | getAllEffectivePositions에서 숨김/화면밖 제외, isOutOfBounds 함수 |
---
## [2026-02-05 저녁] 드래그앤드롭 완전 수정
### 배경 (왜 좌표 계산이 틀렸는가)
**문제 상황**:
- 컴포넌트를 아래로 드래그해도 위로 올라감
- Row 92 같은 비정상적인 좌표로 배치됨
- 드래그 이동/리사이즈가 전혀 작동하지 않음
**핵심 원인**: 캔버스에 `transform: scale(0.8)` 적용 시 좌표 계산 불일치
```
문제:
- getBoundingClientRect() → 스케일 적용된 크기 반환 (예: 1024px → 819px)
- getClientOffset() → 뷰포트 기준 실제 마우스 좌표
- 이 둘을 그대로 계산하면 좌표가 완전히 틀림
```
**해결**: 단순한 상대 좌표 + 스케일 보정
```typescript
// 캔버스 내 상대 좌표 (스케일 보정)
const relX = (마우스X - 캔버스left) / canvasScale;
const relY = (마우스Y - 캔버스top) / canvasScale;
calcGridPosition(relX, relY, customWidth, ...); // 실제 캔버스 크기 사용
```
### Added
- **`calcGridPosition()` 함수** (PopCanvas.tsx)
- 캔버스 내 상대 좌표를 그리드 좌표로 변환
- 패딩, gap, 셀 너비를 고려한 정확한 계산
- **공통 DND 상수** (constants/dnd.ts)
- `DND_ITEM_TYPES.COMPONENT`: 팔레트에서 새 컴포넌트
- `DND_ITEM_TYPES.MOVE_COMPONENT`: 기존 컴포넌트 이동
- 3개 파일에서 중복 정의되던 것을 통합
### Fixed
- **스케일 보정 누락**
- 캔버스 줌(scale)이 적용된 상태에서 좌표 계산 오류
- `(offset - rect.left) / scale`로 보정
- **DND 타입 상수 불일치**
- PopCanvas: `"component"`, `"MOVE_COMPONENT"`
- PopRenderer: `"MOVE_COMPONENT"` (하드코딩)
- ComponentPalette: `"component"` (로컬 정의)
- 모두 공통 상수로 통합
- **컴포넌트 중첩(겹침) 문제**
- 원인: `toast` import 누락으로 겹침 감지 로직이 실행 안됨
- 해결: `sonner`에서 toast import 추가
- 겹침 시 `findNextEmptyPosition()`으로 자동 재배치
- **리사이즈 핸들 작동 안됨**
- 원인: `useDrop` 훅 2개가 같은 `canvasRef`에 중복 적용
- 해결: 단일 `useDrop`으로 통합 (`COMPONENT` + `MOVE_COMPONENT` 모두 처리)
- **불필요한 toast 메시지 제거**
- "컴포넌트가 이동되었습니다" 알림 삭제
### Changed
- **mouseToGridPosition 단순화**
- 복잡한 DOMRect 전달 대신 필요한 값만 직접 전달
- gridUtils.ts의 함수는 유지 (다른 곳에서 사용)
### Technical Details
```
좌표 변환 흐름 (수정 후):
1. 마우스 드롭
offset = monitor.getClientOffset() // 뷰포트 기준 {x: 500, y: 300}
2. 캔버스 위치
canvasRect = canvasRef.getBoundingClientRect() // {left: 250, top: 100}
3. 스케일 보정된 상대 좌표
relX = (500 - 250) / 0.8 = 312.5 // 캔버스 내 실제 X
relY = (300 - 100) / 0.8 = 250 // 캔버스 내 실제 Y
4. 그리드 좌표 계산
calcGridPosition(312.5, 250, 1024, 12, 48, 16, 24)
→ { col: 5, row: 4 }
```
### 수정 파일
| 파일 | 변경 내용 |
|------|----------|
| `PopCanvas.tsx` | calcGridPosition 추가, 스케일 보정 적용 |
| `PopDesigner.tsx` | toast 메시지 제거 |
| `PopRenderer.tsx` | DND 상수 import |
| `ComponentPalette.tsx` | DND 상수 import |
| `constants/dnd.ts` | 새 파일 (DND 타입 상수) |
| `constants/index.ts` | 새 파일 (export) |
---
## [2026-02-05 오후] 그리드 가이드 CSS Grid 통합
### 배경 (왜 재설계했는가)
**문제 상황**:
- GridGuide.tsx(SVG 기반)와 PopRenderer.tsx(CSS Grid)가 좌표계 불일치
- 격자선과 컴포넌트가 정렬되지 않음 ("무늬가 따로 논다")
- 행/열 라벨이 4부터 시작하는 등 오류
**핵심 원칙**:
> "격자선은 컴포넌트와 같은 좌표계에서 태어나야 한다"
**결정**: SVG 격자 삭제, CSS Grid 기반 통합
→ 상세: [decisions/004-grid-guide-integration.md](./decisions/004-grid-guide-integration.md)
### Breaking Changes
- `GridGuide.tsx` 삭제 (SVG 기반 격자)
### Added
- **CSS Grid 기반 격자 셀** (PopRenderer.tsx)
- `gridCells`: 12x20 = 240개 실제 DOM 셀
- `border-dashed border-blue-300/40` 스타일
- 컴포넌트는 `z-index:10`으로 위에 표시
- `showGridGuide` prop으로 ON/OFF
- **행/열 라벨** (PopCanvas.tsx)
- 열 라벨: 1~12 (캔버스 상단)
- 행 라벨: 1~20 (캔버스 좌측)
- absolute positioning으로 정확한 정렬
- 줌/패닝에 연동
- **그리드 토글 버튼** (PopCanvas.tsx)
- "그리드 ON/OFF" 버튼 추가
- 격자 표시 상태 관리
### Changed
- **컴포넌트 타입 단순화**
- `PopComponentType`: `pop-sample` 1개로 단순화
- `DEFAULT_COMPONENT_GRID_SIZE`: `pop-sample` 전용
- `ComponentPalette.tsx`: 샘플 박스 1개만 표시
- `PopRenderer.tsx`: 샘플 박스 렌더링으로 단순화
### Technical Details
```
역할 분담:
- PopRenderer: 격자 셀(div) + 컴포넌트 (같은 CSS Grid 좌표계)
- PopCanvas: 라벨 + 줌/패닝 + 토글
- GridGuide: 삭제
격자 셀 구조:
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│1,1│2,1│3,1│4,1│5,1│6,1│7,1│8,1│9,1│10│11│12 │ ← col
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│1,2│... │
└───┴───────────────────────────────────────────┘
↑ row
```
---
## [2026-02-05] v5 그리드 시스템 완전 통합
### 배경 (왜 v5로 전환했는가)

View File

@@ -1,6 +1,6 @@
# POP 파일 상세 목록
**최종 업데이트: 2026-02-05 (v5 그리드 시스템 통합)**
**최종 업데이트: 2026-02-05 저녁 (드래그앤드롭 수정)**
이 문서는 POP 화면 시스템과 관련된 모든 파일을 나열하고 각 파일의 역할을 설명합니다.
@@ -151,9 +151,11 @@ const [hasChanges, setHasChanges] = useState(false);
| 항목 | 내용 |
|------|------|
| 역할 | v5 CSS Grid 기반 캔버스 |
| 역할 | v5 CSS Grid 기반 캔버스 + 행/열 라벨 |
| 렌더링 | CSS Grid (4/6/8/12칸) |
| 모드 | 4개 (태블릿/모바일 x 가로/세로) |
| 라벨 | 열 라벨 (1~12), 행 라벨 (1~20) |
| 토글 | 그리드 ON/OFF 버튼 |
**핵심 Props**:
@@ -247,8 +249,9 @@ export { default as ComponentEditorPanel, default } from "./ComponentEditorPanel
| 항목 | 내용 |
|------|------|
| 역할 | v5 레이아웃 CSS Grid 렌더러 |
| 입력 | PopLayoutDataV5, viewportWidth, currentMode |
| 역할 | v5 레이아웃 CSS Grid 렌더러 + 격자 셀 |
| 입력 | PopLayoutDataV5, viewportWidth, currentMode, showGridGuide |
| 격자 | 12x20 = 240개 실제 DOM 셀 (CSS Grid 좌표계) |
**핵심 Props**:
@@ -259,12 +262,37 @@ interface PopRendererProps {
currentMode?: GridMode;
isDesignMode?: boolean;
selectedComponentId?: string | null;
showGridGuide?: boolean; // 격자 표시 여부
onComponentClick?: (componentId: string) => void;
onBackgroundClick?: () => void;
className?: string;
}
```
**격자 셀 렌더링**:
```typescript
// 12x20 = 240개 셀 생성
const gridCells = useMemo(() => {
const cells = [];
for (let row = 1; row <= 20; row++) {
for (let col = 1; col <= 12; col++) {
cells.push({ id: `${col}-${row}`, col, row });
}
}
return cells;
}, []);
// 컴포넌트와 동일한 CSS Grid 좌표계로 렌더링
{showGridGuide && gridCells.map(cell => (
<div
key={cell.id}
className="border border-dashed border-blue-300/40"
style={{ gridColumn: cell.col, gridRow: cell.row }}
/>
))}
```
**CSS Grid 스타일 생성**:
```typescript
@@ -374,6 +402,41 @@ export * from "./pop-layout";
---
## 5.5. Constants 파일 (신규)
### `frontend/components/pop/designer/constants/dnd.ts`
| 항목 | 내용 |
|------|------|
| 역할 | DnD(Drag and Drop) 관련 상수 |
| 생성일 | 2026-02-05 |
**핵심 상수**:
```typescript
export const DND_ITEM_TYPES = {
/** 팔레트에서 새 컴포넌트 드래그 */
COMPONENT: "POP_COMPONENT",
/** 캔버스 내 기존 컴포넌트 이동 */
MOVE_COMPONENT: "POP_MOVE_COMPONENT",
} as const;
```
**사용처**:
- `PopCanvas.tsx` - useDrop accept 타입
- `PopRenderer.tsx` - useDrag type
- `ComponentPalette.tsx` - useDrag type
---
### `frontend/components/pop/designer/constants/index.ts`
```typescript
export * from "./dnd";
```
---
## 6. Utils 파일
### `frontend/components/pop/designer/utils/gridUtils.ts`
@@ -545,12 +608,12 @@ export * from "./dashboard";
| 폴더 | 파일 수 | 설명 |
|------|---------|------|
| `app/(pop)` | 4 | App Router 페이지 |
| `components/pop/designer` | 9 | 디자이너 모듈 (v5) |
| `components/pop/designer` | 11 | 디자이너 모듈 (v5) - constants 포함 |
| `components/pop/management` | 5 | 관리 모듈 |
| `components/pop/dashboard` | 12 | 대시보드 모듈 |
| `components/pop` (루트) | 15 | 루트 컴포넌트 |
| `lib` | 3 | 라이브러리 |
| **총계** | **48** | |
| **총계** | **50** | |
---
@@ -565,6 +628,7 @@ export * from "./dashboard";
| `ComponentEditorPanelV4.tsx` | v4 편집 패널 |
| `PopPanel.tsx` | 레거시 팔레트 패널 |
| `test-v4/page.tsx` | v4 테스트 페이지 |
| `GridGuide.tsx` | SVG 기반 격자 가이드 (좌표 불일치로 삭제, CSS Grid 통합) |
---

View File

@@ -10,10 +10,20 @@
| 기능 | 파일 | 함수/컴포넌트 | 설명 |
|------|------|--------------|------|
| 그리드 렌더링 | PopRenderer.tsx | `PopRenderer` | CSS Grid 기반 v5 렌더링 |
| 격자 셀 렌더링 | PopRenderer.tsx | `gridCells` (useMemo) | 12x20 = 240개 DOM 셀 |
| 위치 변환 | gridUtils.ts | `convertPositionToMode()` | 12칸 → 4/6/8칸 변환 |
| 모드 감지 | pop-layout.ts | `detectGridMode()` | 뷰포트 너비로 모드 판별 |
| 컴포넌트 스타일 | PopRenderer.tsx | `convertPosition()` | 그리드 좌표 → CSS |
## 그리드 가이드
| 기능 | 파일 | 함수/컴포넌트 | 설명 |
|------|------|--------------|------|
| 격자 셀 | PopRenderer.tsx | `gridCells` | CSS Grid 기반 격자선 |
| 열 라벨 | PopCanvas.tsx | `gridLabels.columns` | 1~12 표시 |
| 행 라벨 | PopCanvas.tsx | `gridLabels.rows` | 1~20 표시 |
| 토글 | PopCanvas.tsx | `showGridGuide` 상태 | 격자 ON/OFF |
## 드래그 앤 드롭
| 기능 | 파일 | 함수/컴포넌트 | 설명 |
@@ -72,9 +82,10 @@
| 파일 | 핵심 기능 |
|------|----------|
| PopDesigner.tsx | 레이아웃 로드/저장, 컴포넌트 CRUD, 히스토리 |
| PopCanvas.tsx | DnD, 줌, 패닝, 모드 전환, 그리드 표시 |
| PopRenderer.tsx | CSS Grid 렌더링, 위치 변환, 컴포넌트 표시 |
| PopCanvas.tsx | DnD, 줌, 패닝, 모드 전환, 행/열 라벨, 격자 토글 |
| PopRenderer.tsx | CSS Grid 렌더링, 격자 셀, 위치 변환, 컴포넌트 표시 |
| ComponentEditorPanel.tsx | 속성 편집 (위치, 크기, 설정, 표시) |
| ComponentPalette.tsx | 컴포넌트 팔레트 (드래그 가능한 컴포넌트 목록) |
| pop-layout.ts | 타입 정의, 유틸리티 함수, 상수 |
| gridUtils.ts | 좌표 계산, 겹침 감지, 자동 배치 |

View File

@@ -18,6 +18,11 @@
|------|------|------|--------|
| useDrag 에러 (뷰어에서) | isDesignMode 체크 후 early return | 2026-02-04 | DnD, useDrag, 뷰어 |
| DndProvider 중복 에러 | 최상위에서만 Provider 사용 | 2026-02-04 | DndProvider, react-dnd |
| **컴포넌트 중첩(겹침)** | toast import 누락 → `sonner`에서 import | 2026-02-05 | 겹침, overlap, toast |
| **리사이즈 핸들 작동 안됨** | useDrop 2개 중복 → 단일 useDrop으로 통합 | 2026-02-05 | resize, 핸들, useDrop |
| **드래그 좌표 완전 틀림 (Row 92)** | 캔버스 scale 보정 누락 → `(offset - rect.left) / scale` | 2026-02-05 | scale, 좌표, transform |
| **DND 타입 상수 불일치** | 3개 파일에 중복 정의 → `constants/dnd.ts`로 통합 | 2026-02-05 | 상수, DND, 타입 |
| **컴포넌트 이동 안됨** | useDrop accept 타입 불일치 → 공통 상수 사용 | 2026-02-05 | 이동, useDrop, accept |
## 타입 관련
@@ -50,12 +55,62 @@
---
## 해결 안 된 문제 (진행 중)
## 그리드 가이드 관련
| 문제 | 상태 | 관련 파일 |
| 문제 | 해결 | 날짜 | 키워드 |
|------|------|------|--------|
| SVG 격자와 CSS Grid 좌표 불일치 | GridGuide.tsx 삭제, PopRenderer에서 CSS Grid 셀로 격자 렌더링 | 2026-02-05 | 격자, SVG, CSS Grid, 좌표 |
| 행/열 라벨 위치 오류 | PopCanvas에 absolute positioning 라벨 추가 | 2026-02-05 | 라벨, 행, 열, 정렬 |
| 격자선과 컴포넌트 불일치 | 동일한 CSS Grid 좌표계 사용 | 2026-02-05 | 통합, 정렬, 일체감 |
---
## 해결 완료 (이번 세션)
| 문제 | 상태 | 해결 방법 |
|------|------|----------|
| PopCanvas 타입 오류 | 미해결 | PopCanvas.tsx:76 |
| 팔레트 UI 없음 | 미해결 | PopDesigner.tsx |
| PopCanvas 타입 오류 | **해결** | 임시 타입 가드 추가 |
| 팔레트 UI 없음 | **해결** | ComponentPalette.tsx 신규 추가 |
| SVG 격자 좌표 불일치 | **해결** | CSS Grid 기반 통합 |
| 드래그 좌표 완전 틀림 | **해결** | scale 보정 + calcGridPosition 함수 |
| DND 타입 상수 불일치 | **해결** | constants/dnd.ts 통합 |
| 컴포넌트 이동 안됨 | **해결** | useDrop/useDrag 타입 통일 |
| 컴포넌트 중첩(겹침) | **해결** | toast import 추가 → 겹침 감지 로직 정상 작동 |
| 리사이즈 핸들 작동 안됨 | **해결** | useDrop 통합 (2개 → 1개) |
---
## 드래그 좌표 버그 상세 (2026-02-05)
### 증상
- 컴포넌트를 아래로 드래그 → 위로 올라감
- Row 92 같은 비정상 좌표
- 드래그 이동/리사이즈 전혀 작동 안됨
### 원인
```
캔버스: transform: scale(0.8)
getBoundingClientRect() → 스케일 적용된 크기 (1024px → 819px)
getClientOffset() → 뷰포트 기준 실제 마우스 좌표
이 둘을 그대로 계산하면 좌표 완전 틀림
```
### 해결
```typescript
// 스케일 보정된 상대 좌표 계산
const relX = (offset.x - canvasRect.left) / canvasScale;
const relY = (offset.y - canvasRect.top) / canvasScale;
// 실제 캔버스 크기로 그리드 계산
calcGridPosition(relX, relY, customWidth, ...);
```
### 교훈
> CSS `transform: scale()` 적용된 요소에서 좌표 계산 시,
> `getBoundingClientRect()`는 스케일 적용된 값을 반환하지만
> 마우스 좌표는 뷰포트 기준이므로 **반드시 스케일 보정 필요**
---

View File

@@ -10,17 +10,23 @@
| 항목 | 값 |
|------|-----|
| 버전 | **v5** (CSS Grid 기반) |
| 상태 | **기본 기능 완료** |
| 다음 | 실제 테스트, Phase 4 (실제 컴포넌트 구현) |
| 상태 | **반응형 레이아웃 + 숨김 기능 완료** |
| 다음 | Phase 4 (실제 컴포넌트 구현) |
**마지막 업데이트**: 2026-02-05
**마지막 업데이트**: 2026-02-05 심야
---
## 마지막 대화 요약
> (B)(C)(D) 모두 완료. 팔레트 UI 추가, 타입 오류 수정, 문서 v5 기준 통일.
> 다음: 실제 테스트 후 Phase 4 (실제 컴포넌트 렌더링, 데이터 바인딩) 진행.
> **반응형 레이아웃 시스템 완성**:
> - 모드별 컴포넌트 재배치 (오버라이드) 시스템 구현
> - 화면 밖 컴포넌트 오른쪽 패널 배치 기능
> - 컴포넌트 숨김/숨김해제 기능 (모드별)
> - 리사이즈 겹침 검사 추가
> - H키 단축키로 숨김 처리
>
> 다음: Phase 4 (실제 컴포넌트 구현)
---
@@ -31,6 +37,7 @@
| 지금 뭐 해야 해? | [STATUS.md](./STATUS.md) |
| 저장/조회 규칙 | [SAVE_RULES.md](./SAVE_RULES.md) |
| 왜 v5로 바꿨어? | [decisions/003-v5-grid-system.md](./decisions/003-v5-grid-system.md) |
| 그리드 가이드 설계 | [decisions/004-grid-guide-integration.md](./decisions/004-grid-guide-integration.md) |
| 이전 문제 해결 | [PROBLEMS.md](./PROBLEMS.md) |
| 코드 어디 있어? | [FILES.md](./FILES.md) |
| 기능별 색인 | [INDEX.md](./INDEX.md) |
@@ -43,9 +50,10 @@
| 파일 | 역할 | 경로 |
|------|------|------|
| 타입 정의 | v5 레이아웃 타입 | `frontend/components/pop/designer/types/pop-layout.ts` |
| 캔버스 | 그리드 캔버스 + DnD | `frontend/components/pop/designer/PopCanvas.tsx` |
| 렌더러 | CSS Grid 렌더링 | `frontend/components/pop/designer/renderers/PopRenderer.tsx` |
| 캔버스 | 그리드 캔버스 + DnD + 라벨 | `frontend/components/pop/designer/PopCanvas.tsx` |
| 렌더러 | CSS Grid 렌더링 + 격자 셀 | `frontend/components/pop/designer/renderers/PopRenderer.tsx` |
| 디자이너 | 메인 컴포넌트 | `frontend/components/pop/designer/PopDesigner.tsx` |
| 팔레트 | 컴포넌트 목록 | `frontend/components/pop/designer/panels/ComponentPalette.tsx` |
---
@@ -89,6 +97,8 @@ decisions/, sessions/, archive/
**핵심**: 컴포넌트를 칸 단위로 배치 (col, row, colSpan, rowSpan)
**그리드 가이드**: CSS Grid 기반 격자 셀 + 행/열 라벨 (ON/OFF 토글)
---
*상세: [SPEC.md](./SPEC.md) | 히스토리: [CHANGELOG.md](./CHANGELOG.md)*

View File

@@ -1,6 +1,6 @@
# 현재 상태
> **마지막 업데이트**: 2026-02-05
> **마지막 업데이트**: 2026-02-05 심야
> **담당**: POP 화면 디자이너
---
@@ -15,27 +15,52 @@
| v5 편집 패널 | 완료 | `ComponentEditorPanel.tsx` |
| v5 유틸리티 | 완료 | `gridUtils.ts` |
| 레거시 삭제 | 완료 | v1~v4 코드, 데이터 |
| 문서 정리 | **완료** | popdocs v5 기준 재정비 |
| 컴포넌트 팔레트 | **완료** | `ComponentPalette.tsx` |
| 타입 오류 수정 | **완료** | PopCanvas.tsx:76 |
| 드래그앤드롭 | **완료** | 팔레트 → 캔버스 연결 |
| 문서 정리 | 완료 | popdocs v5 기준 재정비 |
| 컴포넌트 팔레트 | 완료 | `ComponentPalette.tsx` |
| 드래그앤드롭 | 완료 | 스케일 보정, DND 상수 통합 |
| 그리드 가이드 재설계 | 완료 | CSS Grid 기반 통합 |
| **모드별 오버라이드** | **완료** | 위치/크기 모드별 저장 |
| **화면 밖 컴포넌트** | **완료** | 오른쪽 패널 배치, 드래그로 복원 |
| **숨김 기능** | **완료** | 모드별 숨김/숨김해제 |
| **리사이즈 겹침 검사** | **완료** | 실시간 겹침 방지 |
---
## 다음 작업 (우선순위)
1. **실제 테스트**
- 디자이너 페이지에서 컴포넌트 드래그앤드롭 테스트
- 저장/로드 동작 확인
2. **실제 컴포넌트 구현** (Phase 4)
1. **실제 컴포넌트 구현** (Phase 4)
- pop-label, pop-button 등 실제 렌더링
- 데이터 바인딩 연결
3. **추가 기능**
- 컴포넌트 복사/붙여넣기
- 다중 선택
- 정렬 도우미
2. **워크플로우 연동**
- 버튼 액션 연결
- 화면 전환 로직
---
## 최근 주요 변경 (2026-02-05 심야)
### 반응형 레이아웃 시스템
| 기능 | 설명 |
|------|------|
| 모드별 재배치 | 4/6/8/12칸 모드별로 컴포넌트 위치/크기 개별 저장 |
| 자동 레이아웃 고정 | 드래그/리사이즈 시 자동으로 오버라이드 저장 |
| 원본으로 되돌리기 | 오버라이드 삭제하여 자동 재배치로 복원 |
### 화면 밖 컴포넌트 처리
| 기능 | 설명 |
|------|------|
| 오른쪽 패널 표시 | 현재 모드에서 초과하는 컴포넌트 별도 표시 |
| 드래그로 복원 | 패널에서 그리드로 드래그하여 재배치 |
| 위치 자동 조정 | 그리드 범위 초과 시 자동으로 왼쪽으로 밀어서 배치 |
### 숨김 기능
| 기능 | 설명 |
|------|------|
| 모드별 숨김 | 특정 모드에서만 컴포넌트 숨김 가능 |
| 숨김 방법 | 드래그→숨김패널 / H키 / 화면밖 컴포넌트 클릭 |
| 숨김 해제 | 숨김패널에서 그리드로 드래그 |
| 12칸 모드 제한 | 기본 모드(12칸)에서는 숨김 기능 비활성화 |
---
@@ -44,7 +69,11 @@
| 문제 | 상태 | 비고 |
|------|------|------|
| 타입 이름 불일치 | 해결됨 | V5 접미사 제거 |
| 팔레트 없음 | 해결됨 | ComponentPalette.tsx 추가 |
| SVG 격자 좌표 불일치 | 해결됨 | GridGuide 삭제, CSS Grid 통합 |
| 드래그 좌표 계산 오류 | 해결됨 | 스케일 보정 적용 |
| DND 타입 상수 불일치 | 해결됨 | constants/dnd.ts로 통합 |
| 숨김 컴포넌트 드래그 안됨 | 해결됨 | 상태 업데이트 순서 수정 |
| 그리드 범위 초과 에러 | 해결됨 | 드롭 위치 자동 조정 |
---
@@ -52,7 +81,8 @@
| 날짜 | 요약 | 상세 |
|------|------|------|
| 2026-02-05 | v5 통합, 문서 재정비, 팔레트 UI 추가 | [sessions/2026-02-05.md](./sessions/2026-02-05.md) |
| 2026-02-05 심야 | 반응형 레이아웃, 숨김 기능, 겹침 검사 | 이 세션 |
| 2026-02-05 저녁 | v5 통합, 그리드 가이드 재설계 | [sessions/2026-02-05.md](./sessions/2026-02-05.md) |
---
@@ -60,6 +90,8 @@
| ADR | 제목 | 날짜 |
|-----|------|------|
| 005 | 반응형 레이아웃 및 숨김 기능 | 2026-02-05 |
| 004 | 그리드 가이드 CSS Grid 통합 | 2026-02-05 |
| 003 | v5 CSS Grid 채택 | 2026-02-05 |
| 001 | v4 제약조건 기반 | 2026-02-03 |

View File

@@ -0,0 +1,143 @@
# ADR-004: 그리드 가이드 CSS Grid 통합
**상태**: 승인됨
**날짜**: 2026-02-05
**결정자**: 개발팀
---
## 컨텍스트
그리드 가이드는 다음 목적을 가짐:
1. **시각적 기준**: 어디에 배치할지 눈으로 확인 가능
2. **정렬 도움**: 칸에 맞춰 배치하기 쉬움
3. **디자인 일관성**: 규칙적인 배치 유도
기존 구현:
- `GridGuide.tsx`: SVG `<line>` 요소로 격자선 렌더링
- `PopRenderer.tsx`: CSS Grid로 컴포넌트 배치
---
## 문제
### 좌표계 불일치
```
SVG 좌표: 픽셀 기반 (0, 0) ~ (width, height)
CSS Grid 좌표: 칸 기반 (col 1~12, row 1~20)
→ 두 좌표계를 정확히 동기화하기 어려움
→ 격자선과 컴포넌트가 정렬되지 않음 ("무늬가 따로 논다")
```
### 구체적 증상
1. GridGuide의 행/열 라벨이 4부터 시작 (잘못된 계산)
2. 격자선 위치와 실제 CSS Grid 셀 위치 불일치
3. 줌/패닝 시 두 레이어가 다르게 동작
---
## 결정
**GridGuide.tsx를 삭제하고, PopRenderer.tsx에서 CSS Grid 기반으로 격자를 직접 렌더링한다.**
핵심 원칙:
> "격자선은 컴포넌트와 같은 좌표계에서 태어나야 한다"
---
## 대안 검토
### Option A: SVG 계산 수정
- **방법**: GridGuide의 좌표 계산을 정확히 수정
- **장점**: 기존 코드 활용
- **단점**: 근본적으로 두 좌표계가 다름, 유지보수 어려움
- **결정**: 채택 안 함
### Option B: PopRenderer에 CSS 배경 격자
- **방법**: `background-image: linear-gradient()`로 격자 표현
- **장점**: 구현 간단
- **단점**: 라벨 표시 불가, 셀 단위 상호작용 불가
- **결정**: 채택 안 함
### Option C: CSS Grid 셀로 격자 렌더링 (채택)
- **방법**: 실제 `div` 요소를 12x20 = 240개 생성, CSS Grid로 배치
- **장점**:
- 컴포넌트와 100% 동일한 좌표계
- 셀 단위 hover, 클릭 등 상호작용 가능
- 라벨은 캔버스 외부에 별도 렌더링
- **단점**: DOM 요소 증가 (240개)
- **결정**: 채택
---
## 구현 상세
### 역할 분담
| 컴포넌트 | 역할 | 좌표계 |
|----------|------|--------|
| PopRenderer | 격자 셀 + 컴포넌트 | CSS Grid |
| PopCanvas | 라벨 + 줌/패닝 + 토글 | absolute |
| GridGuide | (삭제) | - |
### PopRenderer 변경
```typescript
// gridCells 생성 (useMemo)
const gridCells = useMemo(() => {
const cells = [];
for (let row = 1; row <= 20; row++) {
for (let col = 1; col <= 12; col++) {
cells.push({ id: `${col}-${row}`, col, row });
}
}
return cells;
}, []);
// 렌더링
{showGridGuide && gridCells.map(cell => (
<div
key={cell.id}
className="border border-dashed border-blue-300/40"
style={{
gridColumn: cell.col,
gridRow: cell.row,
}}
/>
))}
```
### PopCanvas 라벨 구조
```
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10][11][12] ← 열 라벨 (캔버스 상단)
┌───────────────────────────────────────────┐
[1] │ │ │ │ │ │ │ │ │ │ │ │
[2] │ │ │ │ │ │ │ │ │ │ │ │
[3] │ │ │ │ ■ │ │ │ │ │ │ │ │ ← 5열 3행
└───────────────────────────────────────────┘
↑ 행 라벨 (캔버스 좌측)
```
---
## 결과
### 기대 효과
1. 격자선과 컴포넌트 100% 정렬
2. 정확한 행/열 번호 표시 (1부터 시작)
3. 줌/패닝 시 일관된 동작
4. 향후 셀 클릭으로 빠른 배치 기능 확장 가능
### 트레이드오프
- DOM 요소 240개 추가 (성능 영향 미미)
- GridGuide 코드 삭제 필요
---
## 관련 문서
- 문제: [PROBLEMS.md](../PROBLEMS.md) > P004
- 변경: [CHANGELOG.md](../CHANGELOG.md) > 2026-02-05 오후
- 세션: [sessions/2026-02-05.md](../sessions/2026-02-05.md)

View File

@@ -1,12 +1,21 @@
# 2026-02-05 작업 기록
## 요약
v5 그리드 시스템 통합 완료, popdocs 문서 구조 재정비
v5 그리드 시스템 통합 완료, 그리드 가이드 재설계, **드래그앤드롭 좌표 버그 수정**, popdocs 문서 구조 재정비
---
## 완료
### 드래그앤드롭 완전 수정 (저녁)
- [x] 스케일 보정 누락 문제 해결
- [x] calcGridPosition 함수 추가
- [x] DND 타입 상수 통합 (constants/dnd.ts)
- [x] 불필요한 toast 메시지 제거
- [x] 컴포넌트 이동/리사이즈 정상 작동 확인
- [x] **컴포넌트 중첩(겹침) 문제 해결** - toast import 누락 수정
- [x] **리사이즈 핸들 작동 문제 해결** - useDrop 훅 통합
### v5 통합 작업
- [x] 레거시 파일 삭제 (PopCanvasV4, PopFlexRenderer, PopLayoutRenderer 등)
- [x] 파일명 정규화 (V5 접미사 제거)
@@ -22,31 +31,64 @@ v5 그리드 시스템 통합 완료, popdocs 문서 구조 재정비
- [x] INDEX.md 생성 (기능별 색인)
- [x] sessions/ 폴더 구조 도입
### 디자이너 완성 작업
- [x] 컴포넌트 팔레트 UI 추가 (ComponentPalette.tsx)
- [x] PopCanvas.tsx 타입 오류 수정
- [x] 드래그앤드롭 연결
### 그리드 가이드 재설계
- [x] GridGuide.tsx 삭제 (SVG 기반 → 좌표 불일치 문제)
- [x] PopRenderer.tsx 격자 셀 렌더링 (CSS Grid 기반, 동일 좌표계)
- [x] PopCanvas.tsx 행/열 라벨 추가 (캔버스 바깥)
- [x] 컴포넌트 타입 단순화 (pop-sample 1개)
### 기반 정리 작업
- [x] pop-layout.ts: PopComponentType을 pop-sample 1개로 단순화
- [x] ComponentPalette.tsx: 샘플 박스 1개만 표시
- [x] PopRenderer.tsx: 샘플 박스 렌더링으로 단순화
---
## 미완료
- [ ] 컴포넌트 팔레트 UI 추가 (PopDesigner.tsx 좌측)
- [ ] PopCanvas.tsx 타입 오류 수정 (line 76)
- [ ] ARCHITECTURE.md v5 기준 업데이트
- [ ] CHANGELOG.md 오늘 작업 추가
- [x] 실제 화면 테스트 (디자이너 페이지) → 완료, 정상 작동
- [ ] 간격 조정 규칙 결정 (전역 고정 vs 화면별 vs 컴포넌트별)
---
## 중단점
## 그리드 가이드 재설계 상세
> **다음 작업자 참고**:
>
> 1. **타입 오류**: PopCanvas.tsx line 76
> - `}: PopCanvasV5Props)` → `}: PopCanvasProps)`로 변경
> - 인터페이스는 이미 `PopCanvasProps`로 정의됨 (line 48)
>
> 2. **팔레트 UI**: PopDesigner.tsx에 컴포넌트 팔레트 추가 필요
> - 위치: 좌측 ResizablePanel (현재 비어있음)
> - 참고: 이전 ComponentPaletteV4.tsx (삭제됨, archive에서 참고 가능)
> - DnD 타입: PopCanvas.tsx에 `DND_ITEM_TYPES` 인라인 정의됨
>
> 3. **문서**: ARCHITECTURE.md가 아직 v3/v4 기준임
### 문제 원인
1. GridGuide.tsx가 SVG로 별도 렌더링 → CSS Grid 기반 컴포넌트와 좌표계 불일치
2. PopRenderer의 그리드 배경이 희미 (rgba 0.2)
3. 행/열 번호 라벨 없음
### 해결 방안 (Option C 하이브리드)
```
역할 분담:
- PopRenderer: 격자선 + 컴포넌트 (같은 좌표계)
- PopCanvas: 라벨 + 줌/패닝 + 드롭존
- GridGuide: 삭제
```
### 핵심 설계
```
SVG 격자 (별도 좌표) → CSS Grid 셀 (동일 좌표)
- gridCells: 12열 × 20행 = 240개 실제 DOM 셀
- border-dashed border-blue-300/40 스타일
- 컴포넌트는 z-index:10으로 위에 표시
```
### 라벨 구조
```
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10][11][12] ← 열 라벨 (캔버스 상단)
┌───────────────────────────────────────────┐
[1] │ │ │ │ │ │ │ │ │ │ │ │
[2] │ │ │ │ │ │ │ │ │ │ │ │
[3] │ │ │ │ ■ │ │ │ │ │ │ │ │ ← 5열 3행
└───────────────────────────────────────────┘
↑ 행 라벨 (캔버스 좌측)
```
---
@@ -58,28 +100,78 @@ v5 그리드 시스템 통합 완료, popdocs 문서 구조 재정비
- **연구**: Softr, Ant Design, Material Design 분석
- **결정**: CSS Grid 기반 그리드 시스템 채택
### 그리드 가이드 재설계 배경
- **문제**: SVG GridGuide와 CSS Grid PopRenderer가 좌표계 불일치
- **원칙**: "격자선은 컴포넌트와 같은 좌표계에서 태어나야 한다"
- **결정**: CSS Grid 기반 실제 DOM 셀로 격자 렌더링
### popdocs 재정비 배경
- **문제**: 문서 구조가 AI 에이전트 진입점 역할 못함
- **해결**: Progressive Disclosure 적용, 저장/조회 규칙 명시화
- **참고**: 2025-2026 AI 컨텍스트 엔지니어링 최신 기법
### 핵심 결정
- Layer 1 (진입점): README, STATUS, SAVE_RULES
- Layer 2 (상세): CHANGELOG, PROBLEMS, INDEX 등
- Layer 3 (심화): decisions/, sessions/, archive/
---
## 빌드 결과
```
exit_code: 0
popScreenMngList: 29.4 kB (311 KB First Load)
총 변경: 8,453줄 삭제, 1,819줄 추가 (순감 6,634줄)
```
---
## 관련 링크
- ADR: [decisions/003-v5-grid-system.md](../decisions/003-v5-grid-system.md)
- CHANGELOG: 오늘 작업 추가 필요
- 삭제된 파일 목록: FILES.md 하단 "삭제된 파일" 섹션
---
## 메모
## 드래그앤드롭 좌표 버그 수정 상세
- POPUPDATE.md (루트)는 별도로 유지 (전체 프로젝트 기록용)
- popdocs/는 POP 디자이너 개발 전용
- rangraph 연동 고려 (장기 기억 검색용)
### 문제 현상
- 컴포넌트를 아래로 드래그해도 위로 올라감
- Row 92 같은 비정상적인 좌표로 배치됨
- 드래그 이동/리사이즈가 전혀 작동하지 않음
### 핵심 원인
캔버스에 `transform: scale(0.8)` 적용 시 좌표 계산 불일치:
```
getBoundingClientRect() → 스케일 적용된 크기 (1024px → 819px)
getClientOffset() → 뷰포트 기준 실제 마우스 좌표
이 둘을 그대로 계산하면 좌표가 완전히 틀림
```
### 해결 방법
단순한 상대 좌표 + 스케일 보정:
```typescript
// 캔버스 내 상대 좌표 (스케일 보정)
const relX = (offset.x - canvasRect.left) / canvasScale;
const relY = (offset.y - canvasRect.top) / canvasScale;
// 그리드 좌표 계산 (실제 캔버스 크기 사용)
calcGridPosition(relX, relY, customWidth, breakpoint.columns, ...);
```
### 추가 수정
- DND 타입 상수를 3개 파일에서 중복 정의 → `constants/dnd.ts`로 통합
- 불필요한 "컴포넌트가 이동되었습니다" toast 메시지 제거
---
## 다음 작업자 참고
1. **테스트 완료**
- 디자이너 페이지에서 그리드 가이드 확인 ✅
- 컴포넌트 드래그앤드롭 테스트 ✅
- 4가지 모드 전환 테스트 (추가 확인 필요)
2. **향후 결정 필요**
- 간격 조정: 전역 고정 vs 화면별 vs 컴포넌트별
- 행 수: 현재 20행 고정, 동적 변경 여부
3. **Phase 4 준비**
- 실제 컴포넌트 구현 (pop-label, pop-button 등)
- 데이터 바인딩 연결