feat(pop): 뷰포트 감지 및 비율 스케일링 시스템 추가

- PopFlexRenderer에 뷰포트 너비 감지 기능 추가 (최대 1366px 제한)
- 비율 스케일링 시스템 구현: 컴포넌트 크기, gap, padding에 스케일 적용
- 디자인 모드와 뷰어 모드에서의 스케일 차별화
- 문서 업데이트: 비율 스케일링 시스템 및 적용 위치 설명 추가
This commit is contained in:
SeongHyun Kim 2026-02-04 14:19:32 +09:00
parent 63c00174e1
commit 760e545444
2 changed files with 112 additions and 14 deletions

View File

@ -68,12 +68,31 @@ POP 메인 대시보드. 메뉴 그리드, KPI, 공지사항 등을 표시.
- v3/v4 레이아웃 자동 감지 및 렌더링
- 반응형 모드 감지 (태블릿/모바일, 가로/세로)
- 프리뷰 모드 지원 (`?preview=true`)
- **뷰포트 감지 및 비율 스케일링**
```typescript
// 뷰포트 너비 감지 (최대 1366px 제한)
const [viewportWidth, setViewportWidth] = useState(1024);
useEffect(() => {
const updateViewportWidth = () => {
setViewportWidth(Math.min(window.innerWidth, 1366));
};
updateViewportWidth();
window.addEventListener("resize", updateViewportWidth);
return () => window.removeEventListener("resize", updateViewportWidth);
}, []);
// 레이아웃 버전 감지 및 렌더링
if (popLayoutV4) {
// v4: PopFlexRenderer 사용
<PopFlexRenderer layout={popLayoutV4} viewportWidth={...} />
// v4: PopFlexRenderer 사용 (비율 스케일링 적용)
<div className="mx-auto h-full" style={{ maxWidth: 1366 }}>
<PopFlexRenderer
layout={popLayoutV4}
viewportWidth={viewportWidth}
isDesignMode={false} // 뷰어에서 스케일 적용
/>
</div>
} else if (popLayoutV3) {
// v3: PopLayoutRenderer 사용
<PopLayoutV3Renderer layout={popLayoutV3} modeKey={currentModeKey} />
@ -228,37 +247,77 @@ type SizeMode = "fixed" | "fill" | "hug";
#### `PopFlexRenderer.tsx` (v4용)
**역할**: v4 레이아웃을 Flexbox로 렌더링
**역할**: v4 레이아웃을 Flexbox로 렌더링 + 비율 스케일링
**핵심 기능**:
- 컨테이너 재귀 렌더링
- 반응형 규칙 적용 (breakpoint)
- 크기 제약 → CSS 스타일 변환
- 컴포넌트 숨김 처리 (hideBelow)
- **비율 스케일링** (뷰어 모드)
**크기 제약 변환 로직**:
**비율 스케일링 시스템**:
```typescript
function calculateSizeStyle(size: PopSizeConstraintV4): React.CSSProperties {
// 기준 너비 (10인치 태블릿 가로)
const BASE_VIEWPORT_WIDTH = 1024;
// 스케일 계산 (디자인 모드: 1, 뷰어 모드: 실제 비율)
const scale = isDesignMode ? 1 : viewportWidth / BASE_VIEWPORT_WIDTH;
// 예시: 12인치(1366px) 화면
// scale = 1366 / 1024 = 1.33
// 200px 컴포넌트 → 266px
```
**크기 제약 변환 로직** (스케일 적용):
```typescript
function calculateSizeStyle(
size: PopSizeConstraintV4,
settings: PopGlobalSettingsV4,
scale: number = 1 // 스케일 파라미터 추가
): React.CSSProperties {
const style: React.CSSProperties = {};
// 너비
// 너비 (스케일 적용)
switch (size.width) {
case "fixed": style.width = `${size.fixedWidth}px`; style.flexShrink = 0; break;
case "fill": style.flex = 1; style.minWidth = size.minWidth || 0; break;
case "hug": style.width = "auto"; style.flexShrink = 0; break;
case "fixed":
style.width = `${size.fixedWidth * scale}px`;
style.flexShrink = 0;
break;
case "fill":
style.flex = 1;
style.minWidth = size.minWidth ? `${size.minWidth * scale}px` : 0;
break;
case "hug":
style.width = "auto";
style.flexShrink = 0;
break;
}
// 높이
// 높이 (스케일 적용)
switch (size.height) {
case "fixed": style.height = `${size.fixedHeight}px`; break;
case "fill": style.flexGrow = 1; break;
case "hug": style.height = "auto"; break;
case "fixed":
style.height = `${size.fixedHeight * scale}px`;
break;
case "fill":
style.flexGrow = 1;
break;
case "hug":
style.height = "auto";
break;
}
return style;
}
```
**컨테이너 스케일 적용**:
```typescript
// gap, padding도 스케일 적용
const scaledGap = gap * scale;
const scaledPadding = padding ? padding * scale : undefined;
```
#### `ComponentRenderer.tsx`
**역할**: 개별 컴포넌트 렌더링 (디자인 모드용 플레이스홀더)

View File

@ -250,10 +250,49 @@ interface PopComponentDefinitionV4 {
|------|------|------|
| `PopDesigner.tsx` | v3/v4 통합 디자이너 | 완료 |
| `PopCanvasV4.tsx` | v4 캔버스 (4개 프리셋 + 슬라이더) | 완료 |
| `PopFlexRenderer.tsx` | v4 Flexbox 렌더러 | 완료 |
| `PopFlexRenderer.tsx` | v4 Flexbox 렌더러 + 비율 스케일링 | 완료 |
| `ComponentPaletteV4.tsx` | v4 컴포넌트 팔레트 | 완료 |
| `ComponentEditorPanelV4.tsx` | v4 속성 편집 패널 | 완료 |
| `pop-layout.ts` | v3/v4 타입 정의 | 완료, Phase 2-3에서 수정 예정 |
| `page.tsx` (뷰어) | v4 뷰어 + viewportWidth 감지 | 완료 |
---
## 비율 스케일링 시스템
### 업계 표준
Rockwell Automation HMI의 "Scale with Fixed Aspect Ratio" 방식 적용
### 원리
10인치(1024px) 기준으로 디자인 → 8~12인치에서 배치 유지, 크기만 비례 조정
### 계산
```
scale = viewportWidth / 1024
scaledWidth = originalWidth * scale
scaledHeight = originalHeight * scale
scaledGap = originalGap * scale
scaledPadding = originalPadding * scale
```
### 화면별 결과
| 화면 | scale | 200px 컴포넌트 | 8px gap |
|------|-------|----------------|---------|
| 8인치 (800px) | 0.78 | 156px | 6px |
| 10인치 (1024px) | 1.00 | 200px | 8px |
| 12인치 (1366px) | 1.33 | 266px | 11px |
| 14인치+ | 1.33 (max) | 266px + 여백 | 11px |
### 적용 위치
| 파일 | 함수/변수 | 역할 |
|------|----------|------|
| `PopFlexRenderer.tsx` | `BASE_VIEWPORT_WIDTH` | 기준 너비 상수 (1024) |
| `PopFlexRenderer.tsx` | `calculateSizeStyle(size, settings, scale)` | 크기 스케일 적용 |
| `PopFlexRenderer.tsx` | `ContainerRenderer.containerStyle` | gap, padding 스케일 적용 |
| `page.tsx` | `viewportWidth` state | 뷰포트 너비 감지 |
| `page.tsx` | `Math.min(window.innerWidth, 1366)` | 최대 너비 제한 |
---