POP v4 제약조건 기반 레이아웃 타입 정의 및 문서 구조 재정리
- v4 타입 추가: PopLayoutDataV4, PopContainerV4, PopSizeConstraintV4 - 핵심 변경: 4모드 위치 지정 → 단일 소스 규칙 기반 (fill/fixed/hug) - 생성 함수: createEmptyPopLayoutV4(), isV4Layout() 타입 가드 - 문서 정리: popdocs/ 폴더로 통합, 기존 문서 archive/로 이동
This commit is contained in:
parent
de2163bcef
commit
223f5c0251
|
|
@ -1,9 +1,173 @@
|
||||||
// POP 디자이너 레이아웃 타입 정의
|
// POP 디자이너 레이아웃 타입 정의
|
||||||
// v3.0: 섹션 제거, 컴포넌트 직접 배치
|
// v4.0: 제약조건 기반, 단일 소스 → 자동 적응 (Flexbox)
|
||||||
// 그리드 기반 반응형 레이아웃 (픽셀 좌표 없음, 그리드 셀 기반)
|
// v3.0: 4모드 그리드 기반 (CSS Grid)
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// 레이아웃 모드 키 (4가지)
|
// v4.0 제약조건 기반 레이아웃
|
||||||
|
// ========================================
|
||||||
|
// 핵심: 1번 설계 → 모든 화면 자동 적응
|
||||||
|
// - 위치(col/row) 대신 규칙(fill/fixed/hug) 설정
|
||||||
|
// - Flexbox로 렌더링, 자동 줄바꿈
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v4 레이아웃 (반응형)
|
||||||
|
* - 단일 소스: 4모드 따로 설계 X
|
||||||
|
* - 규칙 기반: 컴포넌트가 어떻게 반응할지 정의
|
||||||
|
*/
|
||||||
|
export interface PopLayoutDataV4 {
|
||||||
|
version: "pop-4.0";
|
||||||
|
|
||||||
|
// 루트 컨테이너 (스택)
|
||||||
|
root: PopContainerV4;
|
||||||
|
|
||||||
|
// 컴포넌트 정의 (ID → 정의)
|
||||||
|
components: Record<string, PopComponentDefinitionV4>;
|
||||||
|
|
||||||
|
// 데이터 흐름 (기존과 동일)
|
||||||
|
dataFlow: PopDataFlow;
|
||||||
|
|
||||||
|
// 전역 설정
|
||||||
|
settings: PopGlobalSettingsV4;
|
||||||
|
|
||||||
|
// 메타데이터
|
||||||
|
metadata?: PopLayoutMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v4 컨테이너 (스택)
|
||||||
|
* - Flexbox 기반
|
||||||
|
* - 자식: 컴포넌트 ID 또는 중첩 컨테이너
|
||||||
|
*/
|
||||||
|
export interface PopContainerV4 {
|
||||||
|
id: string;
|
||||||
|
type: "stack";
|
||||||
|
|
||||||
|
// 방향: 가로 또는 세로
|
||||||
|
direction: "horizontal" | "vertical";
|
||||||
|
|
||||||
|
// 줄바꿈 허용
|
||||||
|
wrap: boolean;
|
||||||
|
|
||||||
|
// 간격 (px)
|
||||||
|
gap: number;
|
||||||
|
|
||||||
|
// 정렬
|
||||||
|
alignItems: "start" | "center" | "end" | "stretch";
|
||||||
|
justifyContent: "start" | "center" | "end" | "space-between";
|
||||||
|
|
||||||
|
// 패딩 (px)
|
||||||
|
padding?: number;
|
||||||
|
|
||||||
|
// 반응형 규칙 (선택)
|
||||||
|
responsive?: PopResponsiveRuleV4[];
|
||||||
|
|
||||||
|
// 자식 요소
|
||||||
|
children: (string | PopContainerV4)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 반응형 규칙
|
||||||
|
* - 특정 너비 이하에서 속성 변경
|
||||||
|
*/
|
||||||
|
export interface PopResponsiveRuleV4 {
|
||||||
|
// 이 너비 이하에서 적용 (px)
|
||||||
|
breakpoint: number;
|
||||||
|
// 변경할 속성들
|
||||||
|
direction?: "horizontal" | "vertical";
|
||||||
|
gap?: number;
|
||||||
|
hidden?: boolean; // 컨테이너 숨김
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v4 컴포넌트 정의
|
||||||
|
* - 크기 제약 기반
|
||||||
|
*/
|
||||||
|
export interface PopComponentDefinitionV4 {
|
||||||
|
id: string;
|
||||||
|
type: PopComponentType;
|
||||||
|
label?: string;
|
||||||
|
|
||||||
|
// 크기 제약 (핵심)
|
||||||
|
size: PopSizeConstraintV4;
|
||||||
|
|
||||||
|
// 개별 정렬 (컨테이너 설정 덮어쓰기)
|
||||||
|
alignSelf?: "start" | "center" | "end" | "stretch";
|
||||||
|
|
||||||
|
// 반응형 숨김
|
||||||
|
hideBelow?: number; // 이 너비 이하에서 숨김
|
||||||
|
|
||||||
|
// 기존 속성
|
||||||
|
dataBinding?: PopDataBinding;
|
||||||
|
style?: PopStylePreset;
|
||||||
|
config?: PopComponentConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 크기 제약
|
||||||
|
* - fixed: 고정 px
|
||||||
|
* - fill: 남은 공간 채움
|
||||||
|
* - hug: 내용에 맞춤
|
||||||
|
*/
|
||||||
|
export interface PopSizeConstraintV4 {
|
||||||
|
width: "fixed" | "fill" | "hug";
|
||||||
|
height: "fixed" | "fill" | "hug";
|
||||||
|
|
||||||
|
// fixed일 때 값
|
||||||
|
fixedWidth?: number;
|
||||||
|
fixedHeight?: number;
|
||||||
|
|
||||||
|
// 최소/최대
|
||||||
|
minWidth?: number;
|
||||||
|
maxWidth?: number;
|
||||||
|
minHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* v4 전역 설정
|
||||||
|
*/
|
||||||
|
export interface PopGlobalSettingsV4 {
|
||||||
|
// 터치 최소 크기 (px)
|
||||||
|
touchTargetMin: number; // 기본 48
|
||||||
|
|
||||||
|
// 모드
|
||||||
|
mode: "normal" | "industrial"; // industrial: 터치 60px
|
||||||
|
|
||||||
|
// 기본 간격
|
||||||
|
defaultGap: number; // 기본 8
|
||||||
|
|
||||||
|
// 기본 패딩
|
||||||
|
defaultPadding: number; // 기본 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// v4 생성 함수
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
export const createEmptyPopLayoutV4 = (): PopLayoutDataV4 => ({
|
||||||
|
version: "pop-4.0",
|
||||||
|
root: {
|
||||||
|
id: "root",
|
||||||
|
type: "stack",
|
||||||
|
direction: "vertical",
|
||||||
|
wrap: false,
|
||||||
|
gap: 8,
|
||||||
|
alignItems: "stretch",
|
||||||
|
justifyContent: "start",
|
||||||
|
padding: 16,
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
dataFlow: { connections: [] },
|
||||||
|
settings: {
|
||||||
|
touchTargetMin: 48,
|
||||||
|
mode: "normal",
|
||||||
|
defaultGap: 8,
|
||||||
|
defaultPadding: 16,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 레이아웃 모드 키 (v3용)
|
||||||
// ========================================
|
// ========================================
|
||||||
export type PopLayoutModeKey =
|
export type PopLayoutModeKey =
|
||||||
| "tablet_landscape" // 태블릿 가로 (1024x768)
|
| "tablet_landscape" // 태블릿 가로 (1024x768)
|
||||||
|
|
@ -127,7 +291,11 @@ export interface PopLayoutDataV1 {
|
||||||
// 통합 타입
|
// 통합 타입
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
export type PopLayoutData = PopLayoutDataV1 | PopLayoutDataV2 | PopLayoutDataV3;
|
export type PopLayoutData = PopLayoutDataV1 | PopLayoutDataV2 | PopLayoutDataV3 | PopLayoutDataV4;
|
||||||
|
|
||||||
|
export function isV4Layout(data: PopLayoutData): data is PopLayoutDataV4 {
|
||||||
|
return data.version === "pop-4.0";
|
||||||
|
}
|
||||||
|
|
||||||
export function isV3Layout(data: PopLayoutData): data is PopLayoutDataV3 {
|
export function isV3Layout(data: PopLayoutData): data is PopLayoutDataV3 {
|
||||||
return data.version === "pop-3.0";
|
return data.version === "pop-3.0";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
# POP 변경 이력
|
||||||
|
|
||||||
|
형식: [Keep a Changelog](https://keepachangelog.com/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [미출시]
|
||||||
|
|
||||||
|
- v4 디자이너 UI 연결
|
||||||
|
- Tier 2, 3 컴포넌트
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-02-04]
|
||||||
|
|
||||||
|
### 오늘 목표
|
||||||
|
POP 화면을 만들 수 있는 환경 완성 (타입 + 렌더러 + 기본 컴포넌트)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **v4 타입 정의** (간결 버전)
|
||||||
|
- `PopLayoutDataV4` - 단일 소스 레이아웃
|
||||||
|
- `PopContainerV4` - 스택 컨테이너 (direction, wrap, gap, alignItems)
|
||||||
|
- `PopComponentDefinitionV4` - 크기 제약 기반 (size: fixed/fill/hug)
|
||||||
|
- `PopSizeConstraintV4` - 크기 규칙
|
||||||
|
- `PopResponsiveRuleV4` - 반응형 규칙 (breakpoint별 변경)
|
||||||
|
- `PopGlobalSettingsV4` - 전역 설정
|
||||||
|
- `createEmptyPopLayoutV4()` - 생성 함수
|
||||||
|
- `isV4Layout()` - 타입 가드
|
||||||
|
|
||||||
|
### 진행중
|
||||||
|
- v4 렌더러 (`PopFlexRenderer`)
|
||||||
|
- 기본 컴포넌트 (PopButton, PopInput, PopLabel)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-02-04] (earlier)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 저장/조회 시스템 구축
|
||||||
|
- rangraph: AI 장기 기억 (시맨틱 검색, 요약)
|
||||||
|
- popdocs: 상세 기록 (파일 기반, 히스토리)
|
||||||
|
- 이중 저장 체계로 검색 + 기록 분리
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- popdocs 문서 구조 정리
|
||||||
|
- README.md: 저장/조회 규칙 추가
|
||||||
|
- 기존 문서 archive/로 이동
|
||||||
|
- 문서 관리 전략 확정
|
||||||
|
- 저장 시: 파일 형식 자동 파악 → 형식 맞춰 추가 → rangraph 요약
|
||||||
|
- 조회 시: rangraph 시맨틱 검색
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- .cursorrules 변경 계획 철회 (Git 커밋 영향)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-02-03]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- v4 제약조건 기반 레이아웃 계획
|
||||||
|
- 단일 소스 + 자동 적응
|
||||||
|
- 3가지 규칙 (크기, 배치, 반응형)
|
||||||
|
- ADR: `decisions/001-v4-constraint-based.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-02-02]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 캔버스 rowSpan 문제
|
||||||
|
- 원인: gridTemplateRows 고정 px
|
||||||
|
- 해결: `1fr` 사용
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-02-01]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 4모드 자동 전환 문제
|
||||||
|
- 해결: useResponsiveMode 훅 추가
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-01-31]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- v3 섹션 제거, 순수 그리드 구조
|
||||||
|
- 4개 모드 독립 그리드
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-01-30]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- POP 디자이너 기본 구조
|
||||||
|
- PopDesigner, PopCanvas 컴포넌트
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2026-01-29]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- screen_layouts_pop 테이블
|
||||||
|
- POP 레이아웃 API (CRUD)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최신이 위, 시간순 역순*
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
# POP 개발 계획
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 오늘 목표 (2026-02-04)
|
||||||
|
|
||||||
|
**POP 화면을 만들 수 있는 환경 완성**
|
||||||
|
|
||||||
|
### 필요한 것
|
||||||
|
|
||||||
|
1. **v4 타입 정의** - 완료
|
||||||
|
2. **v4 렌더러** - Flexbox로 화면에 표시
|
||||||
|
3. **기본 컴포넌트** - 실제 배치할 요소들
|
||||||
|
4. **디자이너 UI 연결** - v4 모드로 설계 가능
|
||||||
|
|
||||||
|
### 작업 순서
|
||||||
|
|
||||||
|
```
|
||||||
|
[v4 타입] → [렌더러] → [컴포넌트] → [디자이너 연결]
|
||||||
|
완료 진행 대기 대기
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 현재 진행
|
||||||
|
|
||||||
|
### v4 타입 정의 (완료)
|
||||||
|
|
||||||
|
- [x] `PopLayoutDataV4` - 단일 소스 레이아웃
|
||||||
|
- [x] `PopContainerV4` - 스택 컨테이너
|
||||||
|
- [x] `PopSizeConstraintV4` - 크기 규칙 (fixed/fill/hug)
|
||||||
|
- [x] `PopResponsiveRuleV4` - 반응형 규칙
|
||||||
|
- [x] `createEmptyPopLayoutV4()` - 생성 함수
|
||||||
|
- [x] `isV4Layout()` - 타입 가드
|
||||||
|
|
||||||
|
### v4 렌더러 (진행)
|
||||||
|
|
||||||
|
- [ ] `PopFlexRenderer` - Flexbox 기반 렌더링
|
||||||
|
- [ ] 컨테이너 재귀 렌더링
|
||||||
|
- [ ] 반응형 규칙 적용 (breakpoint)
|
||||||
|
- [ ] 컴포넌트 숨김 처리 (hideBelow)
|
||||||
|
|
||||||
|
### 기본 컴포넌트 (대기)
|
||||||
|
|
||||||
|
- [ ] `PopButton` - 터치 버튼
|
||||||
|
- [ ] `PopInput` - 텍스트 입력
|
||||||
|
- [ ] `PopLabel` - 텍스트 표시
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3 vs v4 비교
|
||||||
|
|
||||||
|
| | v3 (기존) | v4 (새로운) |
|
||||||
|
|---|---|---|
|
||||||
|
| 설계 | 4모드 각각 | 1번만 |
|
||||||
|
| 데이터 | col, row 위치 | 규칙 (fill/fixed/hug) |
|
||||||
|
| 렌더링 | CSS Grid | Flexbox |
|
||||||
|
| 반응형 | 수동 | 자동 + 규칙 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 컴포넌트 로드맵
|
||||||
|
|
||||||
|
### Tier 1: Primitive (기본)
|
||||||
|
|
||||||
|
| 컴포넌트 | 용도 | 상태 |
|
||||||
|
|----------|------|------|
|
||||||
|
| PopButton | 터치 버튼 | 대기 |
|
||||||
|
| PopInput | 텍스트 입력 | 대기 |
|
||||||
|
| PopLabel | 텍스트 표시 | 대기 |
|
||||||
|
| PopBadge | 상태 배지 | 계획 |
|
||||||
|
|
||||||
|
### Tier 2: Compound (조합)
|
||||||
|
|
||||||
|
| 컴포넌트 | 용도 | 상태 |
|
||||||
|
|----------|------|------|
|
||||||
|
| PopFormField | 라벨 + 입력 | 계획 |
|
||||||
|
| PopCard | 카드 컨테이너 | 계획 |
|
||||||
|
| PopListItem | 목록 항목 | 계획 |
|
||||||
|
|
||||||
|
### Tier 3: Complex (복합)
|
||||||
|
|
||||||
|
| 컴포넌트 | 용도 | 상태 |
|
||||||
|
|----------|------|------|
|
||||||
|
| PopScanner | 바코드/QR 스캔 | 계획 |
|
||||||
|
| PopNumpad | 숫자 키패드 | 계획 |
|
||||||
|
| PopDataList | 페이징 목록 | 계획 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-02-04*
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
# POP 화면 시스템
|
||||||
|
|
||||||
|
> Point of Production - 현장 작업자용 모바일/태블릿 화면
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### 주요 경로
|
||||||
|
|
||||||
|
| 용도 | 경로 |
|
||||||
|
|------|------|
|
||||||
|
| 뷰어 | `/pop/screens/{screenId}` |
|
||||||
|
| 관리 | `/admin/screenMng/popScreenMngList` |
|
||||||
|
| API | `/api/screen-management/layout-pop/:screenId` |
|
||||||
|
|
||||||
|
### 핵심 파일
|
||||||
|
|
||||||
|
| 작업 | 파일 |
|
||||||
|
|------|------|
|
||||||
|
| 타입 | `frontend/components/pop/designer/types/pop-layout.ts` |
|
||||||
|
| 렌더러 | `frontend/components/pop/designer/renderers/` |
|
||||||
|
| 디자이너 | `frontend/components/pop/designer/PopDesigner.tsx` |
|
||||||
|
|
||||||
|
### 현재 상태
|
||||||
|
|
||||||
|
- **버전**: v3.0 (4모드 그리드)
|
||||||
|
- **다음**: v4.0 (제약조건 기반) - 계획
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 문서 구조
|
||||||
|
|
||||||
|
| 파일 | 용도 |
|
||||||
|
|------|------|
|
||||||
|
| [SPEC.md](./SPEC.md) | 기술 스펙 |
|
||||||
|
| [PLAN.md](./PLAN.md) | 계획/로드맵 |
|
||||||
|
| [CHANGELOG.md](./CHANGELOG.md) | 변경 이력 |
|
||||||
|
| [decisions/](./decisions/) | 중요 결정 기록 (ADR) |
|
||||||
|
| [components-spec.md](./components-spec.md) | 컴포넌트 상세 |
|
||||||
|
| [archive/](./archive/) | 이전 문서 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 저장/조회 시스템
|
||||||
|
|
||||||
|
### 역할 분담
|
||||||
|
|
||||||
|
| 저장소 | 역할 | 특징 |
|
||||||
|
|--------|------|------|
|
||||||
|
| **rangraph** | AI 장기 기억 | 시맨틱 검색, 요약 저장 |
|
||||||
|
| **popdocs** | 상세 기록 | 파일 기반, 히스토리 |
|
||||||
|
|
||||||
|
### 저장 요청
|
||||||
|
|
||||||
|
| 요청 예시 | AI 행동 |
|
||||||
|
|----------|--------|
|
||||||
|
| "@CHANGELOG.md 오늘 작업 정리해줘" | 파일 형식 맞춰 추가 + rangraph 요약 |
|
||||||
|
| "이 결정 저장해줘" | rangraph save_decision + decisions/ ADR |
|
||||||
|
| "해결됐어" | rangraph save_lesson + CHANGELOG Fixed |
|
||||||
|
| "작업 완료" | rangraph workflow_submit + CHANGELOG Added |
|
||||||
|
|
||||||
|
### 조회 요청
|
||||||
|
|
||||||
|
| 요청 예시 | AI 행동 |
|
||||||
|
|----------|--------|
|
||||||
|
| "어제 뭐했지?" | rangraph 검색 |
|
||||||
|
| "지금 뭐하는 중이었지?" | rangraph workflow_status |
|
||||||
|
| "이 버그 전에도 있었어?" | rangraph search_memory |
|
||||||
|
| "v4 관련 작업들" | rangraph search_memory "v4" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v4 핵심 (요약)
|
||||||
|
|
||||||
|
```
|
||||||
|
v3: 4개 모드 각각 위치 설정 → 4배 작업량
|
||||||
|
v4: 3가지 규칙만 설정 → 자동 적응
|
||||||
|
|
||||||
|
규칙:
|
||||||
|
1. 크기: fixed(고정) / fill(채움) / hug(맞춤)
|
||||||
|
2. 배치: direction, wrap, gap
|
||||||
|
3. 반응형: breakpoint별 변경
|
||||||
|
```
|
||||||
|
|
||||||
|
상세: [SPEC.md](./SPEC.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-02-04*
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
# POP 기술 스펙
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v4 핵심 규칙 (3가지)
|
||||||
|
|
||||||
|
### 1. 크기 규칙 (Size)
|
||||||
|
|
||||||
|
| 모드 | 설명 | 예시 |
|
||||||
|
|------|------|------|
|
||||||
|
| `fixed` | 고정 px | 버튼 48px |
|
||||||
|
| `fill` | 부모 채움 | 입력창 100% |
|
||||||
|
| `hug` | 내용 맞춤 | 라벨 |
|
||||||
|
|
||||||
|
### 2. 배치 규칙 (Layout)
|
||||||
|
|
||||||
|
| 항목 | 옵션 |
|
||||||
|
|------|------|
|
||||||
|
| direction | horizontal / vertical |
|
||||||
|
| wrap | true / false |
|
||||||
|
| gap | 8 / 16 / 24 px |
|
||||||
|
| alignItems | start / center / end / stretch |
|
||||||
|
|
||||||
|
### 3. 반응형 규칙 (Responsive)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
direction: "horizontal",
|
||||||
|
responsive: [{ breakpoint: 768, direction: "vertical" }]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 크기 프리셋
|
||||||
|
|
||||||
|
### 터치 요소
|
||||||
|
|
||||||
|
| 요소 | 일반 | 산업 |
|
||||||
|
|------|-----|------|
|
||||||
|
| 버튼 높이 | 48px | 60px |
|
||||||
|
| 입력창 높이 | 48px | 56px |
|
||||||
|
| 터치 영역 | 48px | 60px |
|
||||||
|
|
||||||
|
### 폰트 (clamp)
|
||||||
|
|
||||||
|
| 용도 | 범위 | CSS |
|
||||||
|
|------|-----|-----|
|
||||||
|
| 본문 | 14-18px | `clamp(14px, 1.5vw, 18px)` |
|
||||||
|
| 제목 | 18-28px | `clamp(18px, 2.5vw, 28px)` |
|
||||||
|
|
||||||
|
### 간격
|
||||||
|
|
||||||
|
| 이름 | 값 | 용도 |
|
||||||
|
|------|---|-----|
|
||||||
|
| sm | 8px | 요소 내부 |
|
||||||
|
| md | 16px | 컴포넌트 간 |
|
||||||
|
| lg | 24px | 섹션 간 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 반응형 원칙
|
||||||
|
|
||||||
|
```
|
||||||
|
누르는 것 → 고정 (48px)
|
||||||
|
읽는 것 → 범위 (clamp)
|
||||||
|
담는 것 → 비율 (%)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 데이터 구조
|
||||||
|
|
||||||
|
### v3 (현재)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopLayoutDataV3 {
|
||||||
|
version: "pop-3.0";
|
||||||
|
layouts: {
|
||||||
|
tablet_landscape: { componentPositions: Record<string, GridPosition> };
|
||||||
|
// ... 4개 모드
|
||||||
|
};
|
||||||
|
components: Record<string, PopComponentDefinition>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### v4 (계획)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopLayoutDataV4 {
|
||||||
|
version: "pop-4.0";
|
||||||
|
root: PopContainer;
|
||||||
|
components: Record<string, PopComponentDefinitionV4>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PopContainer {
|
||||||
|
type: "stack";
|
||||||
|
direction: "horizontal" | "vertical";
|
||||||
|
children: (string | PopContainer)[];
|
||||||
|
responsive?: { breakpoint: number; direction?: string }[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### 캔버스 rowSpan 문제
|
||||||
|
|
||||||
|
- **증상**: 컴포넌트가 얇게 보임
|
||||||
|
- **원인**: gridTemplateRows 고정 px
|
||||||
|
- **해결**: `1fr` 사용
|
||||||
|
|
||||||
|
### 4모드 전환 안 됨
|
||||||
|
|
||||||
|
- **증상**: 크기 줄여도 레이아웃 유지
|
||||||
|
- **해결**: useResponsiveMode 훅 사용
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*상세 archive 참조: `archive/V4_CORE_RULES.md`, `archive/SIZE_PRESETS.md`*
|
||||||
|
|
@ -0,0 +1,389 @@
|
||||||
|
# POP 컴포넌트 로드맵
|
||||||
|
|
||||||
|
## 큰 그림: 3단계 접근
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 1단계: 기초 블록 2단계: 조합 블록 3단계: 완성 화면 │
|
||||||
|
│ ─────────────── ─────────────── ─────────────── │
|
||||||
|
│ │
|
||||||
|
│ [버튼] [입력창] [폼 그룹] [작업지시 화면] │
|
||||||
|
│ [아이콘] [라벨] → [카드] → [실적입력 화면] │
|
||||||
|
│ [뱃지] [로딩] [리스트] [모니터링 대시보드] │
|
||||||
|
│ [테이블] │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1단계: 기초 블록 (Primitive)
|
||||||
|
|
||||||
|
가장 작은 단위. 다른 곳에서 재사용됩니다.
|
||||||
|
|
||||||
|
### 필수 기초 블록
|
||||||
|
|
||||||
|
| 컴포넌트 | 역할 | 우선순위 |
|
||||||
|
|---------|------|---------|
|
||||||
|
| `PopButton` | 모든 버튼 | 1 |
|
||||||
|
| `PopInput` | 텍스트 입력 | 1 |
|
||||||
|
| `PopLabel` | 라벨/제목 | 1 |
|
||||||
|
| `PopIcon` | 아이콘 표시 | 1 |
|
||||||
|
| `PopBadge` | 상태 뱃지 | 2 |
|
||||||
|
| `PopLoading` | 로딩 스피너 | 2 |
|
||||||
|
| `PopDivider` | 구분선 | 3 |
|
||||||
|
|
||||||
|
### PopButton 예시
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopButtonProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
variant: "primary" | "secondary" | "danger" | "success";
|
||||||
|
size: "sm" | "md" | "lg" | "xl";
|
||||||
|
disabled?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
fullWidth?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용
|
||||||
|
<PopButton variant="primary" size="lg">
|
||||||
|
작업 완료
|
||||||
|
</PopButton>
|
||||||
|
```
|
||||||
|
|
||||||
|
### PopInput 예시
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopInputProps {
|
||||||
|
type: "text" | "number" | "date" | "time";
|
||||||
|
value: string | number;
|
||||||
|
onChange: (value: string | number) => void;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
required?: boolean;
|
||||||
|
error?: string;
|
||||||
|
size: "md" | "lg"; // POP은 lg 기본
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용
|
||||||
|
<PopInput
|
||||||
|
type="number"
|
||||||
|
label="수량"
|
||||||
|
size="lg"
|
||||||
|
value={qty}
|
||||||
|
onChange={setQty}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2단계: 조합 블록 (Compound)
|
||||||
|
|
||||||
|
기초 블록을 조합한 중간 단위.
|
||||||
|
|
||||||
|
### 조합 블록 목록
|
||||||
|
|
||||||
|
| 컴포넌트 | 구성 | 용도 |
|
||||||
|
|---------|------|-----|
|
||||||
|
| `PopFormField` | Label + Input + Error | 폼 입력 그룹 |
|
||||||
|
| `PopCard` | Container + Header + Body | 정보 카드 |
|
||||||
|
| `PopListItem` | Container + Content + Action | 리스트 항목 |
|
||||||
|
| `PopNumberPad` | Grid + Buttons | 숫자 입력 |
|
||||||
|
| `PopStatusBox` | Icon + Label + Value | 상태 표시 |
|
||||||
|
|
||||||
|
### PopFormField 예시
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 기초 블록 조합
|
||||||
|
function PopFormField({ label, required, error, children }) {
|
||||||
|
return (
|
||||||
|
<div className="pop-form-field">
|
||||||
|
<PopLabel required={required}>{label}</PopLabel>
|
||||||
|
{children}
|
||||||
|
{error && <span className="error">{error}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용
|
||||||
|
<PopFormField label="품번" required error={errors.itemCode}>
|
||||||
|
<PopInput type="text" value={itemCode} onChange={setItemCode} />
|
||||||
|
</PopFormField>
|
||||||
|
```
|
||||||
|
|
||||||
|
### PopCard 예시
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function PopCard({ title, badge, children, onClick }) {
|
||||||
|
return (
|
||||||
|
<div className="pop-card" onClick={onClick}>
|
||||||
|
<div className="pop-card-header">
|
||||||
|
<PopLabel size="lg">{title}</PopLabel>
|
||||||
|
{badge && <PopBadge>{badge}</PopBadge>}
|
||||||
|
</div>
|
||||||
|
<div className="pop-card-body">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용
|
||||||
|
<PopCard title="작업지시 #1234" badge="진행중">
|
||||||
|
<p>목표 수량: 100개</p>
|
||||||
|
<p>완료 수량: 45개</p>
|
||||||
|
</PopCard>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3단계: 복합 컴포넌트 (Complex)
|
||||||
|
|
||||||
|
비즈니스 로직이 포함된 완성형.
|
||||||
|
|
||||||
|
### 복합 컴포넌트 목록
|
||||||
|
|
||||||
|
| 컴포넌트 | 기능 | 데이터 |
|
||||||
|
|---------|------|-------|
|
||||||
|
| `PopDataTable` | 대량 데이터 표시/편집 | API 연동 |
|
||||||
|
| `PopCardList` | 카드 형태 리스트 | API 연동 |
|
||||||
|
| `PopBarcodeScanner` | 바코드/QR 스캔 | 카메라/외부장치 |
|
||||||
|
| `PopKpiGauge` | KPI 게이지 | 실시간 데이터 |
|
||||||
|
| `PopAlarmList` | 알람 목록 | 웹소켓 |
|
||||||
|
| `PopProcessFlow` | 공정 흐름도 | 공정 데이터 |
|
||||||
|
|
||||||
|
### PopDataTable 예시
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopDataTableProps {
|
||||||
|
// 데이터
|
||||||
|
data: any[];
|
||||||
|
columns: Column[];
|
||||||
|
|
||||||
|
// 기능
|
||||||
|
selectable?: boolean;
|
||||||
|
editable?: boolean;
|
||||||
|
sortable?: boolean;
|
||||||
|
|
||||||
|
// 반응형 (자동)
|
||||||
|
responsiveColumns?: {
|
||||||
|
tablet: string[];
|
||||||
|
mobile: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이벤트
|
||||||
|
onRowClick?: (row: any) => void;
|
||||||
|
onSelectionChange?: (selected: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사용
|
||||||
|
<PopDataTable
|
||||||
|
data={workOrders}
|
||||||
|
columns={[
|
||||||
|
{ key: "orderNo", label: "지시번호" },
|
||||||
|
{ key: "itemName", label: "품명" },
|
||||||
|
{ key: "qty", label: "수량", align: "right" },
|
||||||
|
{ key: "status", label: "상태" },
|
||||||
|
]}
|
||||||
|
responsiveColumns={{
|
||||||
|
tablet: ["orderNo", "itemName", "qty", "status"],
|
||||||
|
mobile: ["orderNo", "qty"], // 모바일은 2개만
|
||||||
|
}}
|
||||||
|
onRowClick={(row) => openDetail(row.id)}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 개발 순서 제안
|
||||||
|
|
||||||
|
### Phase 1: 기초 (1-2주)
|
||||||
|
|
||||||
|
```
|
||||||
|
Week 1:
|
||||||
|
- PopButton (모든 버튼의 기반)
|
||||||
|
- PopInput (모든 입력의 기반)
|
||||||
|
- PopLabel
|
||||||
|
- PopIcon
|
||||||
|
|
||||||
|
Week 2:
|
||||||
|
- PopBadge
|
||||||
|
- PopLoading
|
||||||
|
- PopDivider
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: 조합 (2-3주)
|
||||||
|
|
||||||
|
```
|
||||||
|
Week 3:
|
||||||
|
- PopFormField (폼의 기본 단위)
|
||||||
|
- PopCard (카드의 기본 단위)
|
||||||
|
|
||||||
|
Week 4:
|
||||||
|
- PopListItem
|
||||||
|
- PopStatusBox
|
||||||
|
- PopNumberPad
|
||||||
|
|
||||||
|
Week 5:
|
||||||
|
- PopModal
|
||||||
|
- PopToast
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: 복합 (3-4주)
|
||||||
|
|
||||||
|
```
|
||||||
|
Week 6-7:
|
||||||
|
- PopDataTable (가장 복잡)
|
||||||
|
- PopCardList
|
||||||
|
|
||||||
|
Week 8-9:
|
||||||
|
- PopBarcodeScanner
|
||||||
|
- PopKpiGauge
|
||||||
|
- PopAlarmList
|
||||||
|
- PopProcessFlow
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 컴포넌트 설계 원칙
|
||||||
|
|
||||||
|
### 1. 크기는 외부에서 제어
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 좋음: 크기를 props로 받음
|
||||||
|
<PopButton size="lg">확인</PopButton>
|
||||||
|
|
||||||
|
// 나쁨: 내부에서 크기 고정
|
||||||
|
<button style={{ height: "48px" }}>확인</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 최소 크기는 내부에서 보장
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 컴포넌트 내부
|
||||||
|
const styles = {
|
||||||
|
minHeight: 48, // 터치 최소 크기 보장
|
||||||
|
minWidth: 80,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 반응형은 자동
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 좋음: 화면 크기에 따라 자동 조절
|
||||||
|
<PopFormField label="이름">
|
||||||
|
<PopInput />
|
||||||
|
</PopFormField>
|
||||||
|
|
||||||
|
// 나쁨: 모드별로 다른 컴포넌트
|
||||||
|
{isMobile ? <MobileInput /> : <TabletInput />}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 데이터와 UI 분리
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 좋음: 데이터 로직은 훅으로
|
||||||
|
const { data, loading, error } = useWorkOrders();
|
||||||
|
|
||||||
|
<PopDataTable data={data} loading={loading} />
|
||||||
|
|
||||||
|
// 나쁨: 컴포넌트 안에서 fetch
|
||||||
|
function PopDataTable() {
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/work-orders')...
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 폴더 구조 제안
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/components/pop/
|
||||||
|
├── primitives/ # 1단계: 기초 블록
|
||||||
|
│ ├── PopButton.tsx
|
||||||
|
│ ├── PopInput.tsx
|
||||||
|
│ ├── PopLabel.tsx
|
||||||
|
│ ├── PopIcon.tsx
|
||||||
|
│ ├── PopBadge.tsx
|
||||||
|
│ ├── PopLoading.tsx
|
||||||
|
│ └── index.ts
|
||||||
|
│
|
||||||
|
├── compounds/ # 2단계: 조합 블록
|
||||||
|
│ ├── PopFormField.tsx
|
||||||
|
│ ├── PopCard.tsx
|
||||||
|
│ ├── PopListItem.tsx
|
||||||
|
│ ├── PopNumberPad.tsx
|
||||||
|
│ ├── PopStatusBox.tsx
|
||||||
|
│ └── index.ts
|
||||||
|
│
|
||||||
|
├── complex/ # 3단계: 복합 컴포넌트
|
||||||
|
│ ├── PopDataTable/
|
||||||
|
│ │ ├── PopDataTable.tsx
|
||||||
|
│ │ ├── PopTableHeader.tsx
|
||||||
|
│ │ ├── PopTableRow.tsx
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ ├── PopCardList/
|
||||||
|
│ ├── PopBarcodeScanner/
|
||||||
|
│ └── index.ts
|
||||||
|
│
|
||||||
|
├── hooks/ # 공용 훅
|
||||||
|
│ ├── usePopTheme.ts
|
||||||
|
│ ├── useResponsiveSize.ts
|
||||||
|
│ └── useTouchFeedback.ts
|
||||||
|
│
|
||||||
|
└── styles/ # 공용 스타일
|
||||||
|
├── pop-variables.css
|
||||||
|
└── pop-base.css
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 스타일 변수
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* pop-variables.css */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* 터치 크기 */
|
||||||
|
--pop-touch-min: 48px;
|
||||||
|
--pop-touch-industrial: 60px;
|
||||||
|
|
||||||
|
/* 폰트 크기 */
|
||||||
|
--pop-font-body: clamp(14px, 1.5vw, 18px);
|
||||||
|
--pop-font-heading: clamp(18px, 2.5vw, 28px);
|
||||||
|
--pop-font-caption: clamp(12px, 1vw, 14px);
|
||||||
|
|
||||||
|
/* 간격 */
|
||||||
|
--pop-gap-sm: 8px;
|
||||||
|
--pop-gap-md: 16px;
|
||||||
|
--pop-gap-lg: 24px;
|
||||||
|
|
||||||
|
/* 색상 */
|
||||||
|
--pop-primary: #2563eb;
|
||||||
|
--pop-success: #16a34a;
|
||||||
|
--pop-warning: #f59e0b;
|
||||||
|
--pop-danger: #dc2626;
|
||||||
|
|
||||||
|
/* 고대비 (야외용) */
|
||||||
|
--pop-high-contrast-bg: #000000;
|
||||||
|
--pop-high-contrast-fg: #ffffff;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 다음 단계
|
||||||
|
|
||||||
|
1. **기초 블록부터 시작**: PopButton, PopInput 먼저 만들기
|
||||||
|
2. **스토리북 설정**: 컴포넌트별 문서화
|
||||||
|
3. **테스트**: 터치 크기, 반응형 확인
|
||||||
|
4. **디자이너 연동**: v4 레이아웃 시스템과 통합
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-02-03*
|
||||||
|
|
@ -0,0 +1,760 @@
|
||||||
|
# POP v4.0 제약조건 기반 시스템 구현 계획
|
||||||
|
|
||||||
|
## 1. 현재 시스템 분석
|
||||||
|
|
||||||
|
### 1.1 현재 구조 (v3.0)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 4개 모드별 그리드 위치 기반
|
||||||
|
interface PopLayoutDataV3 {
|
||||||
|
version: "pop-3.0";
|
||||||
|
layouts: {
|
||||||
|
tablet_landscape: { componentPositions: Record<string, GridPosition> };
|
||||||
|
tablet_portrait: { componentPositions: Record<string, GridPosition> };
|
||||||
|
mobile_landscape: { componentPositions: Record<string, GridPosition> };
|
||||||
|
mobile_portrait: { componentPositions: Record<string, GridPosition> };
|
||||||
|
};
|
||||||
|
components: Record<string, PopComponentDefinition>;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GridPosition {
|
||||||
|
col: number; // 1-based
|
||||||
|
row: number; // 1-based
|
||||||
|
colSpan: number;
|
||||||
|
rowSpan: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 현재 문제점
|
||||||
|
|
||||||
|
1. **4배 작업량**: 4개 모드 각각 설계 필요
|
||||||
|
2. **수동 동기화**: 컴포넌트 추가/삭제 시 4모드 수동 동기화
|
||||||
|
3. **그리드 한계**: col/row 기반이라 자동 재배치 불가
|
||||||
|
4. **반응형 미흡**: 화면 크기 변화에 자동 적응 불가
|
||||||
|
5. **디바이스 차이 무시**: 태블릿/모바일 물리적 크기 차이 고려 안됨
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 새로운 시스템 설계 (v4.0)
|
||||||
|
|
||||||
|
### 2.1 핵심 철학
|
||||||
|
|
||||||
|
```
|
||||||
|
"하나의 레이아웃 설계 → 제약조건 설정 → 모든 화면 자동 적응"
|
||||||
|
```
|
||||||
|
|
||||||
|
- **단일 소스**: 1개 레이아웃만 설계
|
||||||
|
- **제약조건 기반**: 컴포넌트가 "어떻게 반응할지" 규칙 정의
|
||||||
|
- **Flexbox 렌더링**: CSS Grid에서 Flexbox 기반으로 전환
|
||||||
|
- **자동 줄바꿈**: 공간 부족 시 자동 재배치
|
||||||
|
|
||||||
|
### 2.2 새로운 데이터 구조
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// v4.0 레이아웃
|
||||||
|
interface PopLayoutDataV4 {
|
||||||
|
version: "pop-4.0";
|
||||||
|
|
||||||
|
// 루트 컨테이너
|
||||||
|
root: PopContainer;
|
||||||
|
|
||||||
|
// 컴포넌트 정의 (ID → 정의)
|
||||||
|
components: Record<string, PopComponentDefinitionV4>;
|
||||||
|
|
||||||
|
// 데이터 흐름
|
||||||
|
dataFlow: PopDataFlow;
|
||||||
|
|
||||||
|
// 전역 설정
|
||||||
|
settings: PopGlobalSettingsV4;
|
||||||
|
|
||||||
|
// 메타데이터
|
||||||
|
metadata?: PopLayoutMetadata;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 컨테이너 (스택)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 컨테이너: 컴포넌트들을 담는 그룹
|
||||||
|
interface PopContainer {
|
||||||
|
id: string;
|
||||||
|
type: "stack";
|
||||||
|
|
||||||
|
// 스택 방향
|
||||||
|
direction: "horizontal" | "vertical";
|
||||||
|
|
||||||
|
// 줄바꿈 허용
|
||||||
|
wrap: boolean;
|
||||||
|
|
||||||
|
// 요소 간 간격
|
||||||
|
gap: number;
|
||||||
|
|
||||||
|
// 정렬
|
||||||
|
alignItems: "start" | "center" | "end" | "stretch";
|
||||||
|
justifyContent: "start" | "center" | "end" | "space-between" | "space-around";
|
||||||
|
|
||||||
|
// 패딩
|
||||||
|
padding?: {
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
bottom: number;
|
||||||
|
left: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 반응형 규칙 (선택)
|
||||||
|
responsive?: {
|
||||||
|
// 브레이크포인트 (이 너비 이하에서 적용)
|
||||||
|
breakpoint: number;
|
||||||
|
// 변경할 방향
|
||||||
|
direction?: "horizontal" | "vertical";
|
||||||
|
// 변경할 간격
|
||||||
|
gap?: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
// 자식 요소 (컴포넌트 ID 또는 중첩 컨테이너)
|
||||||
|
children: (string | PopContainer)[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 컴포넌트 제약조건
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopComponentDefinitionV4 {
|
||||||
|
id: string;
|
||||||
|
type: PopComponentType;
|
||||||
|
label?: string;
|
||||||
|
|
||||||
|
// ===== 크기 제약 (핵심) =====
|
||||||
|
size: {
|
||||||
|
// 너비 모드
|
||||||
|
width: "fixed" | "fill" | "hug";
|
||||||
|
// 높이 모드
|
||||||
|
height: "fixed" | "fill" | "hug";
|
||||||
|
|
||||||
|
// 고정 크기 (width/height가 fixed일 때)
|
||||||
|
fixedWidth?: number;
|
||||||
|
fixedHeight?: number;
|
||||||
|
|
||||||
|
// 최소/최대 크기
|
||||||
|
minWidth?: number;
|
||||||
|
maxWidth?: number;
|
||||||
|
minHeight?: number;
|
||||||
|
maxHeight?: number;
|
||||||
|
|
||||||
|
// 비율 (fill일 때, 같은 컨테이너 내 다른 요소와의 비율)
|
||||||
|
flexGrow?: number; // 기본 1
|
||||||
|
flexShrink?: number; // 기본 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== 정렬 =====
|
||||||
|
alignSelf?: "start" | "center" | "end" | "stretch";
|
||||||
|
|
||||||
|
// ===== 여백 =====
|
||||||
|
margin?: {
|
||||||
|
top: number;
|
||||||
|
right: number;
|
||||||
|
bottom: number;
|
||||||
|
left: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== 모바일 스케일 (선택) =====
|
||||||
|
// 모바일에서 컴포넌트를 더 크게 표시
|
||||||
|
mobileScale?: number; // 기본 1.0, 예: 1.2 = 20% 더 크게
|
||||||
|
|
||||||
|
// ===== 기존 속성 =====
|
||||||
|
dataBinding?: PopDataBinding;
|
||||||
|
style?: PopStylePreset;
|
||||||
|
config?: PopComponentConfig;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 크기 모드 설명
|
||||||
|
|
||||||
|
| 모드 | 설명 | CSS 변환 |
|
||||||
|
|------|------|----------|
|
||||||
|
| `fixed` | 고정 크기 (px) | `width: {fixedWidth}px` |
|
||||||
|
| `fill` | 부모 공간 채우기 | `flex: {flexGrow} {flexShrink} 0` |
|
||||||
|
| `hug` | 내용에 맞춤 | `flex: 0 0 auto` |
|
||||||
|
|
||||||
|
### 2.6 전역 설정
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopGlobalSettingsV4 {
|
||||||
|
// 기본 터치 타겟 크기
|
||||||
|
touchTargetMin: number; // 48px
|
||||||
|
|
||||||
|
// 모드 (일반/산업현장)
|
||||||
|
mode: "normal" | "industrial";
|
||||||
|
|
||||||
|
// 기본 간격
|
||||||
|
defaultGap: number; // 8px
|
||||||
|
|
||||||
|
// 기본 패딩
|
||||||
|
defaultPadding: number; // 16px
|
||||||
|
|
||||||
|
// 반응형 브레이크포인트 (전역)
|
||||||
|
breakpoints: {
|
||||||
|
tablet: number; // 768px
|
||||||
|
mobile: number; // 480px
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 디자이너 UI 변경
|
||||||
|
|
||||||
|
### 3.1 기존 디자이너 vs 새 디자이너
|
||||||
|
|
||||||
|
```
|
||||||
|
기존 (그리드 기반):
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ [태블릿 가로] [태블릿 세로] [모바일 가로] [모바일 세로] │
|
||||||
|
│ │
|
||||||
|
│ 24x24 그리드에 컴포넌트 드래그 배치 │
|
||||||
|
│ │
|
||||||
|
└──────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
새로운 (제약조건 기반):
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ [단일 캔버스] 미리보기: [태블릿▼] │
|
||||||
|
│ │
|
||||||
|
│ 스택(컨테이너)에 컴포넌트 배치 │
|
||||||
|
│ + 우측 패널에서 제약조건 설정 │
|
||||||
|
│ │
|
||||||
|
└──────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 새로운 디자이너 레이아웃
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ POP 화면 디자이너 v4 [저장] [미리보기] │
|
||||||
|
├────────────────┬────────────────────────┬───────────────────────┤
|
||||||
|
│ │ │ │
|
||||||
|
│ 컴포넌트 │ 캔버스 │ 속성 패널 │
|
||||||
|
│ │ │ │
|
||||||
|
│ ▼ 기본 │ ┌──────────────────┐ │ ▼ 선택됨: 입력창 │
|
||||||
|
│ [필드] │ │ ┌──────────────┐ │ │ │
|
||||||
|
│ [버튼] │ │ │입력창 │ │ │ ▼ 크기 │
|
||||||
|
│ [리스트] │ │ └──────────────┘ │ │ 너비: [채우기 ▼] │
|
||||||
|
│ [인디케이터] │ │ │ │ 최소: [100] px │
|
||||||
|
│ │ │ ┌─────┐ ┌─────┐ │ │ 최대: [없음] │
|
||||||
|
│ ▼ 입력 │ │ │버튼1│ │버튼2│ │ │ │
|
||||||
|
│ [스캐너] │ │ └─────┘ └─────┘ │ │ 높이: [고정 ▼] │
|
||||||
|
│ [숫자패드] │ │ │ │ 값: [48] px │
|
||||||
|
│ │ └──────────────────┘ │ │
|
||||||
|
│ ───────── │ │ ▼ 정렬 │
|
||||||
|
│ │ 미리보기: │ [늘이기 ▼] │
|
||||||
|
│ ▼ 레이아웃 │ ┌──────────────────┐ │ │
|
||||||
|
│ [스택 (가로)] │ │[태블릿 가로 ▼] │ │ ▼ 여백 │
|
||||||
|
│ [스택 (세로)] │ │[768px] │ │ 상[8] 우[0] 하[8] 좌[0]│
|
||||||
|
│ │ └──────────────────┘ │ │
|
||||||
|
│ │ │ ▼ 반응형 │
|
||||||
|
│ │ │ 모바일 스케일: [1.2] │
|
||||||
|
│ │ │ │
|
||||||
|
└────────────────┴────────────────────────┴───────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 컨테이너(스택) 편집
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─ 스택 속성 ─────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ 방향: [가로 ▼] │
|
||||||
|
│ 줄바꿈: [허용 ☑] │
|
||||||
|
│ 간격: [8] px │
|
||||||
|
│ │
|
||||||
|
│ 정렬 (가로): [가운데 ▼] │
|
||||||
|
│ 정렬 (세로): [늘이기 ▼] │
|
||||||
|
│ │
|
||||||
|
│ ▼ 반응형 규칙 │
|
||||||
|
│ ┌─────────────────────────────┐ │
|
||||||
|
│ │ 768px 이하: 세로 방향 │ │
|
||||||
|
│ │ [+ 규칙 추가] │ │
|
||||||
|
│ └─────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 렌더링 로직 변경
|
||||||
|
|
||||||
|
### 4.1 기존 렌더링 (CSS Grid)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// v3: CSS Grid 기반
|
||||||
|
<div style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: `repeat(24, 1fr)`,
|
||||||
|
gridTemplateRows: `repeat(24, 1fr)`,
|
||||||
|
gap: "4px",
|
||||||
|
}}>
|
||||||
|
{componentIds.map(id => (
|
||||||
|
<div style={{
|
||||||
|
gridColumn: `${pos.col} / span ${pos.colSpan}`,
|
||||||
|
gridRow: `${pos.row} / span ${pos.rowSpan}`,
|
||||||
|
}}>
|
||||||
|
<Component />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 새로운 렌더링 (Flexbox)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// v4: Flexbox 기반
|
||||||
|
function renderContainer(container: PopContainer, components: Record<string, PopComponentDefinitionV4>) {
|
||||||
|
const direction = useResponsiveValue(container, 'direction');
|
||||||
|
const gap = useResponsiveValue(container, 'gap');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: direction === "horizontal" ? "row" : "column",
|
||||||
|
flexWrap: container.wrap ? "wrap" : "nowrap",
|
||||||
|
gap: `${gap}px`,
|
||||||
|
alignItems: container.alignItems,
|
||||||
|
justifyContent: container.justifyContent,
|
||||||
|
padding: container.padding ?
|
||||||
|
`${container.padding.top}px ${container.padding.right}px ${container.padding.bottom}px ${container.padding.left}px`
|
||||||
|
: undefined,
|
||||||
|
}}>
|
||||||
|
{container.children.map(child => {
|
||||||
|
if (typeof child === "string") {
|
||||||
|
// 컴포넌트 렌더링
|
||||||
|
return renderComponent(components[child]);
|
||||||
|
} else {
|
||||||
|
// 중첩 컨테이너 렌더링
|
||||||
|
return renderContainer(child, components);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderComponent(component: PopComponentDefinitionV4) {
|
||||||
|
const { size, margin, mobileScale } = component;
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const scale = isMobile && mobileScale ? mobileScale : 1;
|
||||||
|
|
||||||
|
// 크기 계산
|
||||||
|
let width: string;
|
||||||
|
let flex: string;
|
||||||
|
|
||||||
|
if (size.width === "fixed") {
|
||||||
|
width = `${(size.fixedWidth || 100) * scale}px`;
|
||||||
|
flex = "0 0 auto";
|
||||||
|
} else if (size.width === "fill") {
|
||||||
|
width = "auto";
|
||||||
|
flex = `${size.flexGrow || 1} ${size.flexShrink || 1} 0`;
|
||||||
|
} else { // hug
|
||||||
|
width = "auto";
|
||||||
|
flex = "0 0 auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
flex,
|
||||||
|
width,
|
||||||
|
minWidth: size.minWidth ? `${size.minWidth * scale}px` : undefined,
|
||||||
|
maxWidth: size.maxWidth ? `${size.maxWidth * scale}px` : undefined,
|
||||||
|
height: size.height === "fixed" ? `${(size.fixedHeight || 48) * scale}px` : "auto",
|
||||||
|
minHeight: size.minHeight ? `${size.minHeight * scale}px` : undefined,
|
||||||
|
maxHeight: size.maxHeight ? `${size.maxHeight * scale}px` : undefined,
|
||||||
|
margin: margin ?
|
||||||
|
`${margin.top}px ${margin.right}px ${margin.bottom}px ${margin.left}px`
|
||||||
|
: undefined,
|
||||||
|
alignSelf: component.alignSelf,
|
||||||
|
}}>
|
||||||
|
<ActualComponent {...component} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 반응형 훅
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function useResponsiveValue<T>(
|
||||||
|
container: PopContainer,
|
||||||
|
property: keyof PopContainer
|
||||||
|
): T {
|
||||||
|
const windowWidth = useWindowWidth();
|
||||||
|
|
||||||
|
// 기본값
|
||||||
|
let value = container[property] as T;
|
||||||
|
|
||||||
|
// 반응형 규칙 적용 (작은 브레이크포인트 우선)
|
||||||
|
if (container.responsive) {
|
||||||
|
const sortedRules = [...container.responsive].sort((a, b) => b.breakpoint - a.breakpoint);
|
||||||
|
for (const rule of sortedRules) {
|
||||||
|
if (windowWidth <= rule.breakpoint && rule[property] !== undefined) {
|
||||||
|
value = rule[property] as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 구현 단계
|
||||||
|
|
||||||
|
### Phase 1: 데이터 구조 (1-2일)
|
||||||
|
|
||||||
|
**파일**: `frontend/components/pop/designer/types/pop-layout.ts`
|
||||||
|
|
||||||
|
1. `PopLayoutDataV4` 인터페이스 정의
|
||||||
|
2. `PopContainer` 인터페이스 정의
|
||||||
|
3. `PopComponentDefinitionV4` 인터페이스 정의
|
||||||
|
4. `createEmptyPopLayoutV4()` 함수
|
||||||
|
5. `migrateV3ToV4()` 마이그레이션 함수
|
||||||
|
6. `ensureV4Layout()` 함수
|
||||||
|
7. 타입 가드 함수들
|
||||||
|
|
||||||
|
### Phase 2: 렌더러 (2-3일)
|
||||||
|
|
||||||
|
**파일**: `frontend/components/pop/designer/renderers/PopLayoutRendererV4.tsx`
|
||||||
|
|
||||||
|
1. `renderContainer()` 함수
|
||||||
|
2. `renderComponent()` 함수
|
||||||
|
3. `useResponsiveValue()` 훅
|
||||||
|
4. `useWindowWidth()` 훅
|
||||||
|
5. CSS 스타일 계산 로직
|
||||||
|
6. 반응형 브레이크포인트 처리
|
||||||
|
|
||||||
|
### Phase 3: 디자이너 UI (3-4일)
|
||||||
|
|
||||||
|
**파일**: `frontend/components/pop/designer/PopDesignerV4.tsx`
|
||||||
|
|
||||||
|
1. 캔버스 영역 (드래그 앤 드롭)
|
||||||
|
2. 컴포넌트 팔레트 (기존 + 스택)
|
||||||
|
3. 속성 패널
|
||||||
|
- 크기 제약 편집
|
||||||
|
- 정렬 편집
|
||||||
|
- 여백 편집
|
||||||
|
- 반응형 규칙 편집
|
||||||
|
4. 미리보기 모드 (다양한 화면 크기)
|
||||||
|
5. 컨테이너(스택) 관리
|
||||||
|
- 컨테이너 추가/삭제
|
||||||
|
- 컨테이너 설정 편집
|
||||||
|
- 컴포넌트 이동 (컨테이너 간)
|
||||||
|
|
||||||
|
### Phase 4: 뷰어 통합 (1-2일)
|
||||||
|
|
||||||
|
**파일**: `frontend/app/(pop)/pop/screens/[screenId]/page.tsx`
|
||||||
|
|
||||||
|
1. v4 레이아웃 감지 및 렌더링
|
||||||
|
2. 기존 v3 호환 유지
|
||||||
|
3. 반응형 모드 감지 연동
|
||||||
|
4. 성능 최적화
|
||||||
|
|
||||||
|
### Phase 5: 백엔드 수정 (1일)
|
||||||
|
|
||||||
|
**파일**: `backend-node/src/services/screenManagementService.ts`
|
||||||
|
|
||||||
|
1. `saveLayoutPop` - v4 버전 감지 및 저장
|
||||||
|
2. `getLayoutPop` - v4 버전 반환
|
||||||
|
3. 버전 마이그레이션 로직
|
||||||
|
|
||||||
|
### Phase 6: 테스트 및 마이그레이션 (2-3일)
|
||||||
|
|
||||||
|
1. 단위 테스트
|
||||||
|
2. 통합 테스트
|
||||||
|
3. 기존 v3 레이아웃 마이그레이션 도구
|
||||||
|
4. 크로스 디바이스 테스트
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 마이그레이션 전략
|
||||||
|
|
||||||
|
### 6.1 v3 → v4 자동 변환
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function migrateV3ToV4(v3: PopLayoutDataV3): PopLayoutDataV4 {
|
||||||
|
// 태블릿 가로 모드 기준으로 변환
|
||||||
|
const baseLayout = v3.layouts.tablet_landscape;
|
||||||
|
const componentIds = Object.keys(baseLayout.componentPositions);
|
||||||
|
|
||||||
|
// 컴포넌트를 row, col 순으로 정렬
|
||||||
|
const sortedIds = componentIds.sort((a, b) => {
|
||||||
|
const posA = baseLayout.componentPositions[a];
|
||||||
|
const posB = baseLayout.componentPositions[b];
|
||||||
|
if (posA.row !== posB.row) return posA.row - posB.row;
|
||||||
|
return posA.col - posB.col;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 같은 row에 있는 컴포넌트들을 가로 스택으로 그룹화
|
||||||
|
const rowGroups = groupByRow(sortedIds, baseLayout.componentPositions);
|
||||||
|
|
||||||
|
// 루트 컨테이너 (세로 스택)
|
||||||
|
const rootContainer: PopContainer = {
|
||||||
|
id: "root",
|
||||||
|
type: "stack",
|
||||||
|
direction: "vertical",
|
||||||
|
wrap: false,
|
||||||
|
gap: v3.settings.canvasGrid.gap,
|
||||||
|
alignItems: "stretch",
|
||||||
|
justifyContent: "start",
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 각 행을 가로 스택으로 변환
|
||||||
|
for (const [row, ids] of rowGroups) {
|
||||||
|
if (ids.length === 1) {
|
||||||
|
// 단일 컴포넌트면 직접 추가
|
||||||
|
rootContainer.children.push(ids[0]);
|
||||||
|
} else {
|
||||||
|
// 여러 컴포넌트면 가로 스택으로 감싸기
|
||||||
|
const rowStack: PopContainer = {
|
||||||
|
id: `row-${row}`,
|
||||||
|
type: "stack",
|
||||||
|
direction: "horizontal",
|
||||||
|
wrap: true,
|
||||||
|
gap: v3.settings.canvasGrid.gap,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "start",
|
||||||
|
children: ids,
|
||||||
|
};
|
||||||
|
rootContainer.children.push(rowStack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 컴포넌트 정의 변환
|
||||||
|
const components: Record<string, PopComponentDefinitionV4> = {};
|
||||||
|
for (const id of componentIds) {
|
||||||
|
const v3Comp = v3.components[id];
|
||||||
|
const pos = baseLayout.componentPositions[id];
|
||||||
|
|
||||||
|
components[id] = {
|
||||||
|
...v3Comp,
|
||||||
|
size: {
|
||||||
|
// colSpan을 기반으로 크기 모드 결정
|
||||||
|
width: pos.colSpan >= 20 ? "fill" : "fixed",
|
||||||
|
height: "fixed",
|
||||||
|
fixedWidth: pos.colSpan * (1024 / 24), // 대략적인 픽셀 변환
|
||||||
|
fixedHeight: pos.rowSpan * (768 / 24),
|
||||||
|
minWidth: 100,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: "pop-4.0",
|
||||||
|
root: rootContainer,
|
||||||
|
components,
|
||||||
|
dataFlow: v3.dataFlow,
|
||||||
|
settings: {
|
||||||
|
touchTargetMin: v3.settings.touchTargetMin,
|
||||||
|
mode: v3.settings.mode,
|
||||||
|
defaultGap: v3.settings.canvasGrid.gap,
|
||||||
|
defaultPadding: 16,
|
||||||
|
breakpoints: {
|
||||||
|
tablet: 768,
|
||||||
|
mobile: 480,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata: v3.metadata,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 하위 호환
|
||||||
|
|
||||||
|
- v3 레이아웃은 계속 지원
|
||||||
|
- 디자이너에서 v3 → v4 업그레이드 버튼 제공
|
||||||
|
- 새로 생성하는 레이아웃은 v4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 예상 효과
|
||||||
|
|
||||||
|
### 7.1 사용자 경험
|
||||||
|
|
||||||
|
| 항목 | 기존 (v3) | 새로운 (v4) |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| 설계 개수 | 4개 | 1개 |
|
||||||
|
| 작업 시간 | 4배 | 1배 |
|
||||||
|
| 반응형 | 수동 | 자동 |
|
||||||
|
| 디바이스 대응 | 각각 설정 | mobileScale |
|
||||||
|
|
||||||
|
### 7.2 개발자 경험
|
||||||
|
|
||||||
|
| 항목 | 기존 (v3) | 새로운 (v4) |
|
||||||
|
|------|-----------|-------------|
|
||||||
|
| 렌더링 | CSS Grid | Flexbox |
|
||||||
|
| 위치 계산 | col/row | 자동 |
|
||||||
|
| 반응형 로직 | 4모드 분기 | 브레이크포인트 |
|
||||||
|
| 유지보수 | 복잡 | 단순 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 일정 (예상)
|
||||||
|
|
||||||
|
| Phase | 내용 | 기간 |
|
||||||
|
|-------|------|------|
|
||||||
|
| 1 | 데이터 구조 | 1-2일 |
|
||||||
|
| 2 | 렌더러 | 2-3일 |
|
||||||
|
| 3 | 디자이너 UI | 3-4일 |
|
||||||
|
| 4 | 뷰어 통합 | 1-2일 |
|
||||||
|
| 5 | 백엔드 수정 | 1일 |
|
||||||
|
| 6 | 테스트/마이그레이션 | 2-3일 |
|
||||||
|
| **총계** | | **10-15일** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 리스크 및 대응
|
||||||
|
|
||||||
|
### 9.1 기존 레이아웃 호환성
|
||||||
|
|
||||||
|
- **리스크**: v3 → v4 자동 변환이 완벽하지 않을 수 있음
|
||||||
|
- **대응**:
|
||||||
|
- 마이그레이션 미리보기 기능
|
||||||
|
- 수동 조정 도구 제공
|
||||||
|
- v3 유지 옵션
|
||||||
|
|
||||||
|
### 9.2 학습 곡선
|
||||||
|
|
||||||
|
- **리스크**: 제약조건 개념이 익숙하지 않을 수 있음
|
||||||
|
- **대응**:
|
||||||
|
- 프리셋 제공 (예: "화면 전체 채우기", "고정 크기")
|
||||||
|
- 툴팁/도움말
|
||||||
|
- 예제 템플릿
|
||||||
|
|
||||||
|
### 9.3 성능
|
||||||
|
|
||||||
|
- **리스크**: Flexbox 중첩으로 렌더링 성능 저하
|
||||||
|
- **대응**:
|
||||||
|
- 컨테이너 중첩 깊이 제한 (최대 3-4)
|
||||||
|
- React.memo 활용
|
||||||
|
- 가상화 (리스트 컴포넌트)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 결론
|
||||||
|
|
||||||
|
v4.0 제약조건 기반 시스템은 업계 표준(Figma, Flutter, SwiftUI)을 따르며, 사용자의 작업량을 75% 줄이고 자동 반응형을 제공합니다.
|
||||||
|
|
||||||
|
구현 후 POP 디자이너는:
|
||||||
|
- **1개 레이아웃**만 설계
|
||||||
|
- **모든 화면 크기**에 자동 적응
|
||||||
|
- **모바일 특화 설정** (mobileScale)으로 세밀한 제어 가능
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 추가 설정 (2026-02-03 업데이트)
|
||||||
|
|
||||||
|
### 11.1 확장된 전역 설정
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopGlobalSettingsV4 {
|
||||||
|
// 기존
|
||||||
|
touchTargetMin: number; // 48 (normal) / 60 (industrial)
|
||||||
|
mode: "normal" | "industrial";
|
||||||
|
defaultGap: number;
|
||||||
|
defaultPadding: number;
|
||||||
|
breakpoints: {
|
||||||
|
tablet: number; // 768
|
||||||
|
mobile: number; // 480
|
||||||
|
};
|
||||||
|
|
||||||
|
// 신규 추가
|
||||||
|
environment: "indoor" | "outdoor"; // 야외면 대비 높임
|
||||||
|
|
||||||
|
typography: {
|
||||||
|
body: { min: number; max: number }; // 14-18px
|
||||||
|
heading: { min: number; max: number }; // 18-28px
|
||||||
|
caption: { min: number; max: number }; // 12-14px
|
||||||
|
};
|
||||||
|
|
||||||
|
contrast: "normal" | "high"; // outdoor면 자동 high
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.2 컴포넌트 기본값 프리셋
|
||||||
|
|
||||||
|
컴포넌트 추가 시 자동 적용되는 안전한 기본값:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const COMPONENT_DEFAULTS = {
|
||||||
|
"pop-button": {
|
||||||
|
minWidth: 80,
|
||||||
|
minHeight: 48,
|
||||||
|
height: "fixed",
|
||||||
|
fixedHeight: 48,
|
||||||
|
},
|
||||||
|
"pop-field": {
|
||||||
|
minWidth: 120,
|
||||||
|
minHeight: 40,
|
||||||
|
height: "fixed",
|
||||||
|
fixedHeight: 48,
|
||||||
|
},
|
||||||
|
"pop-list": {
|
||||||
|
minHeight: 200,
|
||||||
|
itemHeight: 48,
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.3 리스트 반응형 컬럼
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopListConfig {
|
||||||
|
// 기존
|
||||||
|
listType: PopListType;
|
||||||
|
displayColumns?: string[];
|
||||||
|
|
||||||
|
// 신규 추가
|
||||||
|
responsiveColumns?: {
|
||||||
|
tablet: string[]; // 전체 컬럼
|
||||||
|
mobile: string[]; // 주요 컬럼만
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.4 라벨 배치 자동화
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface PopContainer {
|
||||||
|
// 기존
|
||||||
|
direction: "horizontal" | "vertical";
|
||||||
|
|
||||||
|
// 신규 추가
|
||||||
|
labelPlacement?: "auto" | "above" | "beside";
|
||||||
|
// auto: 모바일 세로=위, 태블릿 가로=옆
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 관련 문서
|
||||||
|
|
||||||
|
- [v4 핵심 규칙 가이드](./V4_CORE_RULES.md) - **3가지 핵심 규칙 (필독)**
|
||||||
|
- [반응형 디자인 가이드](./RESPONSIVE_DESIGN_GUIDE.md)
|
||||||
|
- [컴포넌트 로드맵](./COMPONENT_ROADMAP.md)
|
||||||
|
- [크기 프리셋 가이드](./SIZE_PRESETS.md)
|
||||||
|
- [컴포넌트 상세 스펙](./components-spec.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 현재 상태 (2026-02-03)
|
||||||
|
|
||||||
|
**구현 대기**: 컴포넌트가 아직 없어서 레이아웃 시스템보다 컴포넌트 개발이 선행되어야 함.
|
||||||
|
|
||||||
|
**권장 진행 순서**:
|
||||||
|
1. 기초 컴포넌트 개발 (PopButton, PopInput 등)
|
||||||
|
2. 조합 컴포넌트 개발 (PopFormField, PopCard 등)
|
||||||
|
3. 복합 컴포넌트 개발 (PopDataTable, PopCardList 등)
|
||||||
|
4. v4 레이아웃 시스템 구현
|
||||||
|
5. 디자이너 UI 개발
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-02-03*
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
# POP 반응형 디자인 가이드
|
||||||
|
|
||||||
|
## 쉬운 요약
|
||||||
|
|
||||||
|
### 핵심 원칙: 3가지만 기억하세요
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 누르는 것 → 크기 고정 (최소 48px)
|
||||||
|
2. 읽는 것 → 범위 안에서 자동 조절
|
||||||
|
3. 담는 것 → 화면에 맞춰 늘어남
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 터치 요소 (고정 크기)
|
||||||
|
|
||||||
|
손가락 크기는 화면이 커져도 변하지 않습니다.
|
||||||
|
|
||||||
|
| 요소 | 일반 | 산업현장(장갑) |
|
||||||
|
|------|-----|--------------|
|
||||||
|
| 버튼 | 48px | 60px |
|
||||||
|
| 아이콘 (누르는 용) | 48px | 60px |
|
||||||
|
| 체크박스 | 24px (터치영역 48px) | 24px (터치영역 60px) |
|
||||||
|
| 리스트 한 줄 높이 | 48px | 56px |
|
||||||
|
| 입력창 높이 | 40px | 48px |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 텍스트 (범위 조절)
|
||||||
|
|
||||||
|
화면 크기에 따라 자동으로 커지거나 작아집니다.
|
||||||
|
|
||||||
|
| 용도 | 최소 | 최대 |
|
||||||
|
|------|-----|-----|
|
||||||
|
| 본문 | 14px | 18px |
|
||||||
|
| 제목 | 18px | 28px |
|
||||||
|
| 설명 | 12px | 14px |
|
||||||
|
|
||||||
|
**CSS 예시**:
|
||||||
|
```css
|
||||||
|
font-size: clamp(14px, 1.5vw, 18px);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 레이아웃 (비율 기반)
|
||||||
|
|
||||||
|
컨테이너는 화면에 맞춰 늘어납니다.
|
||||||
|
|
||||||
|
| 요소 | 방식 | 예시 |
|
||||||
|
|------|-----|-----|
|
||||||
|
| 컨테이너 | 100% | 화면 전체 채움 |
|
||||||
|
| 카드 2열 | 48% + 48% | 화면 반씩 |
|
||||||
|
| 입력창 너비 | fill | 부모 채움 |
|
||||||
|
| 여백 | 8/16/24px | 화면 크기별 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 환경별 설정
|
||||||
|
|
||||||
|
### 일반 (실내)
|
||||||
|
- 터치: 48px
|
||||||
|
- 대비: 4.5:1
|
||||||
|
- 폰트: 14-18px
|
||||||
|
|
||||||
|
### 산업현장 (야외/장갑)
|
||||||
|
- 터치: 60px (+25%)
|
||||||
|
- 대비: 7:1 이상
|
||||||
|
- 폰트: 18-22px (+25%)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 리스트/테이블 반응형
|
||||||
|
|
||||||
|
화면이 좁아지면 컬럼을 줄입니다.
|
||||||
|
|
||||||
|
```
|
||||||
|
태블릿 (넓음) 모바일 (좁음)
|
||||||
|
┌──────┬──────┬──────┬──────┐ ┌──────┬──────┐
|
||||||
|
│품번 │품명 │수량 │상태 │ │품번 │수량 │
|
||||||
|
├──────┼──────┼──────┼──────┤ ├──────┼──────┤
|
||||||
|
│A001 │나사 │100 │완료 │ │A001 │100 │
|
||||||
|
└──────┴──────┴──────┴──────┘ └──────┴──────┘
|
||||||
|
↳ 터치하면 상세보기
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 폼 라벨 배치
|
||||||
|
|
||||||
|
| 화면 | 라벨 위치 | 이유 |
|
||||||
|
|------|----------|-----|
|
||||||
|
| 모바일 세로 | 위 | 입력창 너비 확보 |
|
||||||
|
| 태블릿 가로 | 옆 | 공간 여유 |
|
||||||
|
|
||||||
|
```
|
||||||
|
모바일 태블릿
|
||||||
|
┌─────────────────┐ ┌─────────────────────────┐
|
||||||
|
│ 이름 │ │ 이름: [입력____________] │
|
||||||
|
│ [입력__________]│ └─────────────────────────┘
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 그림으로 보는 반응형
|
||||||
|
|
||||||
|
```
|
||||||
|
8인치 태블릿 12인치 태블릿
|
||||||
|
┌─────────────────┐ ┌───────────────────────┐
|
||||||
|
│ [버튼 48px] │ │ [버튼 48px] │ ← 버튼 크기 동일!
|
||||||
|
│ │ │ │
|
||||||
|
│ ┌─────────────┐ │ │ ┌─────────────────┐ │
|
||||||
|
│ │ 입력창 │ │ │ │ 입력창 │ │ ← 너비만 늘어남
|
||||||
|
│ └─────────────┘ │ │ └─────────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ 글자 14px │ │ 글자 18px │ ← 글자만 커짐
|
||||||
|
└─────────────────┘ └───────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 색상 대비 (야외용)
|
||||||
|
|
||||||
|
| 환경 | 최소 대비 | 권장 |
|
||||||
|
|------|----------|-----|
|
||||||
|
| 실내 | 4.5:1 | 7:1 |
|
||||||
|
| 야외 | 7:1 | 10:1+ |
|
||||||
|
|
||||||
|
**좋은 조합**:
|
||||||
|
- 흰 배경 + 검정 글자
|
||||||
|
- 검정 배경 + 흰 글자
|
||||||
|
- 노랑 경고 + 검정 글자
|
||||||
|
|
||||||
|
**피해야 할 조합**:
|
||||||
|
- 연한 회색 + 밝은 회색
|
||||||
|
- 빨강 + 녹색 (색맹 고려)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 체크리스트
|
||||||
|
|
||||||
|
### 컴포넌트 만들 때 확인
|
||||||
|
|
||||||
|
- [ ] 버튼/터치 요소 최소 48px인가?
|
||||||
|
- [ ] 폰트에 clamp() 적용했나?
|
||||||
|
- [ ] 색상 대비 4.5:1 이상인가?
|
||||||
|
- [ ] 모바일에서 라벨이 위에 있나?
|
||||||
|
- [ ] 리스트가 좁은 화면에서 컬럼 줄어드나?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-02-03*
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
# POP 크기 프리셋 가이드
|
||||||
|
|
||||||
|
## 컴포넌트별 기본 크기
|
||||||
|
|
||||||
|
컴포넌트를 만들면 자동으로 적용되는 크기입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 버튼 (PopButton)
|
||||||
|
|
||||||
|
| 사이즈 | 높이 | 최소 너비 | 폰트 | 용도 |
|
||||||
|
|-------|------|----------|------|-----|
|
||||||
|
| sm | 32px | 60px | 12px | 보조 버튼 |
|
||||||
|
| md | 40px | 80px | 14px | 일반 버튼 |
|
||||||
|
| **lg** | **48px** | **100px** | **16px** | **POP 기본** |
|
||||||
|
| xl | 56px | 120px | 18px | 주요 액션 |
|
||||||
|
| industrial | 60px | 140px | 20px | 장갑 착용 |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// POP에서는 lg가 기본
|
||||||
|
<PopButton size="lg">확인</PopButton>
|
||||||
|
|
||||||
|
// 산업현장
|
||||||
|
<PopButton size="industrial">작업 완료</PopButton>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 입력창 (PopInput)
|
||||||
|
|
||||||
|
| 사이즈 | 높이 | 폰트 | 용도 |
|
||||||
|
|-------|------|------|-----|
|
||||||
|
| md | 40px | 14px | 일반 |
|
||||||
|
| **lg** | **48px** | **16px** | **POP 기본** |
|
||||||
|
| xl | 56px | 18px | 강조 입력 |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 입력창 너비는 항상 부모 채움 (fill)
|
||||||
|
<PopInput size="lg" />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 리스트 행 (PopListItem)
|
||||||
|
|
||||||
|
| 사이즈 | 높이 | 폰트 | 용도 |
|
||||||
|
|-------|------|------|-----|
|
||||||
|
| compact | 40px | 14px | 많은 데이터 |
|
||||||
|
| **normal** | **48px** | **16px** | **POP 기본** |
|
||||||
|
| spacious | 56px | 18px | 여유로운 |
|
||||||
|
| industrial | 64px | 20px | 장갑 착용 |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<PopListItem size="normal">
|
||||||
|
<span>작업지시 #1234</span>
|
||||||
|
</PopListItem>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 아이콘 (PopIcon)
|
||||||
|
|
||||||
|
| 사이즈 | 크기 | 터치 영역 | 용도 |
|
||||||
|
|-------|-----|----------|-----|
|
||||||
|
| sm | 16px | 32px | 뱃지 안 |
|
||||||
|
| md | 20px | 40px | 텍스트 옆 |
|
||||||
|
| **lg** | **24px** | **48px** | **POP 기본** |
|
||||||
|
| xl | 32px | 56px | 강조 |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 아이콘만 있는 버튼
|
||||||
|
<PopButton icon="check" size="lg" />
|
||||||
|
|
||||||
|
// 텍스트 + 아이콘
|
||||||
|
<PopButton icon="save" size="lg">저장</PopButton>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 카드 (PopCard)
|
||||||
|
|
||||||
|
| 요소 | 크기 |
|
||||||
|
|------|-----|
|
||||||
|
| 패딩 | 16px |
|
||||||
|
| 제목 폰트 | 18px (heading) |
|
||||||
|
| 본문 폰트 | 16px (body) |
|
||||||
|
| 모서리 | 8px |
|
||||||
|
| 최소 높이 | 100px |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<PopCard>
|
||||||
|
<PopCard.Header>작업지시</PopCard.Header>
|
||||||
|
<PopCard.Body>내용</PopCard.Body>
|
||||||
|
</PopCard>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 숫자패드 (PopNumberPad)
|
||||||
|
|
||||||
|
| 요소 | 크기 |
|
||||||
|
|------|-----|
|
||||||
|
| 버튼 크기 | 60px x 60px |
|
||||||
|
| 버튼 간격 | 8px |
|
||||||
|
| 전체 너비 | 240px |
|
||||||
|
| 폰트 | 24px |
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ [ 123 ] │ ← 디스플레이 48px
|
||||||
|
├─────┬─────┬─────────┤
|
||||||
|
│ 7 │ 8 │ 9 │ ← │ ← 각 버튼 60x60
|
||||||
|
├─────┼─────┼─────────┤
|
||||||
|
│ 4 │ 5 │ 6 │ C │
|
||||||
|
├─────┼─────┼─────────┤
|
||||||
|
│ 1 │ 2 │ 3 │ │
|
||||||
|
├─────┼─────┼─────│ OK│
|
||||||
|
│ 0 │ . │ +- │ │
|
||||||
|
└─────┴─────┴─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 상태 표시 (PopStatusBox)
|
||||||
|
|
||||||
|
| 사이즈 | 너비 | 높이 | 아이콘 | 용도 |
|
||||||
|
|-------|-----|------|-------|-----|
|
||||||
|
| sm | 80px | 60px | 24px | 여러 개 나열 |
|
||||||
|
| **md** | **120px** | **80px** | **32px** | **POP 기본** |
|
||||||
|
| lg | 160px | 100px | 40px | 강조 |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
<PopStatusBox
|
||||||
|
label="설비 상태"
|
||||||
|
value="가동중"
|
||||||
|
status="success"
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## KPI 게이지 (PopKpiGauge)
|
||||||
|
|
||||||
|
| 사이즈 | 너비 | 높이 | 용도 |
|
||||||
|
|-------|-----|------|-----|
|
||||||
|
| sm | 120px | 120px | 여러 개 나열 |
|
||||||
|
| **md** | **180px** | **180px** | **POP 기본** |
|
||||||
|
| lg | 240px | 240px | 강조 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 간격 (Gap/Padding)
|
||||||
|
|
||||||
|
| 이름 | 값 | 용도 |
|
||||||
|
|------|---|-----|
|
||||||
|
| xs | 4px | 아이콘-텍스트 |
|
||||||
|
| sm | 8px | 요소 내부 |
|
||||||
|
| **md** | **16px** | **컴포넌트 간** |
|
||||||
|
| lg | 24px | 섹션 간 |
|
||||||
|
| xl | 32px | 영역 구분 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 반응형 조절
|
||||||
|
|
||||||
|
화면 크기에 따라 자동 조절되는 값들:
|
||||||
|
|
||||||
|
| 요소 | 8인치 태블릿 | 12인치 태블릿 |
|
||||||
|
|------|------------|--------------|
|
||||||
|
| 본문 폰트 | 14px | 18px |
|
||||||
|
| 제목 폰트 | 18px | 28px |
|
||||||
|
| 컨테이너 패딩 | 12px | 24px |
|
||||||
|
| 카드 간격 | 12px | 16px |
|
||||||
|
|
||||||
|
**고정되는 값들 (변하지 않음)**:
|
||||||
|
- 버튼 높이: 48px
|
||||||
|
- 입력창 높이: 48px
|
||||||
|
- 리스트 행 높이: 48px
|
||||||
|
- 터치 최소 영역: 48px
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 적용 예시
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 컴포넌트 내부에서 자동 적용
|
||||||
|
function PopButton({ size = "lg", ...props }) {
|
||||||
|
const sizeStyles = {
|
||||||
|
sm: { height: 32, minWidth: 60, fontSize: 12 },
|
||||||
|
md: { height: 40, minWidth: 80, fontSize: 14 },
|
||||||
|
lg: { height: 48, minWidth: 100, fontSize: 16 }, // POP 기본
|
||||||
|
xl: { height: 56, minWidth: 120, fontSize: 18 },
|
||||||
|
industrial: { height: 60, minWidth: 140, fontSize: 20 },
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button style={sizeStyles[size]} {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-02-03*
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
# POP v4 핵심 규칙 가이드
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
v4에서는 **"위치"를 설정하는 게 아니라 "규칙"을 설정**합니다.
|
||||||
|
|
||||||
|
```
|
||||||
|
v3 (기존): 4개 모드 각각 컴포넌트 위치 설정 → 4배 작업량
|
||||||
|
v4 (신규): 3가지 규칙만 설정 → 모든 화면 자동 적응
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 핵심 규칙 3가지
|
||||||
|
|
||||||
|
### 1. 크기 규칙 (Size Rules)
|
||||||
|
|
||||||
|
각 컴포넌트의 **너비**와 **높이**를 어떻게 결정할지 정합니다.
|
||||||
|
|
||||||
|
| 모드 | 설명 | 예시 |
|
||||||
|
|------|------|------|
|
||||||
|
| **fixed** | 고정 px | 버튼 높이 48px |
|
||||||
|
| **fill** | 부모 공간 채움 | 입력창 너비 = 화면 너비 |
|
||||||
|
| **hug** | 내용에 맞춤 | 라벨 너비 = 텍스트 길이 |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 예시: 버튼
|
||||||
|
{
|
||||||
|
width: "fill", // 화면 너비에 맞춤
|
||||||
|
height: "fixed", // 고정
|
||||||
|
fixedHeight: 48 // 48px
|
||||||
|
}
|
||||||
|
|
||||||
|
// 예시: 라벨
|
||||||
|
{
|
||||||
|
width: "hug", // 텍스트 길이만큼
|
||||||
|
height: "hug" // 텍스트 높이만큼
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 크기 모드 시각화
|
||||||
|
|
||||||
|
```
|
||||||
|
fixed (고정):
|
||||||
|
├────48px────┤
|
||||||
|
┌────────────┐
|
||||||
|
│ 버튼 │ ← 화면 커져도 48px 유지
|
||||||
|
└────────────┘
|
||||||
|
|
||||||
|
fill (채움):
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ 입력창 │ ← 화면 크기에 맞춤
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
|
||||||
|
hug (맞춤):
|
||||||
|
├──────┤
|
||||||
|
┌──────┐
|
||||||
|
│라벨 │ ← 내용 길이만큼만
|
||||||
|
└──────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 배치 규칙 (Layout Rules)
|
||||||
|
|
||||||
|
컴포넌트들을 **어떻게 나열할지** 정합니다.
|
||||||
|
|
||||||
|
#### 스택 방향
|
||||||
|
|
||||||
|
```
|
||||||
|
가로 스택 (horizontal): 세로 스택 (vertical):
|
||||||
|
┌─────┬─────┬─────┐ ┌─────────────┐
|
||||||
|
│ A │ B │ C │ │ A │
|
||||||
|
└─────┴─────┴─────┘ ├─────────────┤
|
||||||
|
│ B │
|
||||||
|
├─────────────┤
|
||||||
|
│ C │
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 설정 항목
|
||||||
|
|
||||||
|
| 항목 | 설명 | 옵션 |
|
||||||
|
|------|------|------|
|
||||||
|
| **direction** | 스택 방향 | horizontal / vertical |
|
||||||
|
| **wrap** | 줄바꿈 허용 | true / false |
|
||||||
|
| **gap** | 요소 간 간격 | 8 / 16 / 24 px |
|
||||||
|
| **alignItems** | 교차축 정렬 | start / center / end / stretch |
|
||||||
|
| **justifyContent** | 주축 정렬 | start / center / end / space-between |
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 예시: 버튼 그룹 (가로 배치)
|
||||||
|
{
|
||||||
|
direction: "horizontal",
|
||||||
|
wrap: true, // 공간 부족하면 줄바꿈
|
||||||
|
gap: 16, // 버튼 간격 16px
|
||||||
|
alignItems: "center" // 세로 중앙 정렬
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 반응형 규칙 (Responsive Rules)
|
||||||
|
|
||||||
|
**화면이 좁아지면** 어떻게 바꿀지 정합니다.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 예시: 768px 이하면 가로→세로 전환
|
||||||
|
{
|
||||||
|
direction: "horizontal",
|
||||||
|
responsive: [
|
||||||
|
{
|
||||||
|
breakpoint: 768, // 768px 이하일 때
|
||||||
|
direction: "vertical" // 세로로 바꿈
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 시각화
|
||||||
|
|
||||||
|
```
|
||||||
|
768px 이상 (태블릿): 768px 이하 (모바일):
|
||||||
|
┌─────┬─────┬─────┐ ┌─────────────┐
|
||||||
|
│ A │ B │ C │ → │ A │
|
||||||
|
└─────┴─────┴─────┘ │ B │
|
||||||
|
│ C │
|
||||||
|
└─────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 실제 예시: 작업지시 화면
|
||||||
|
|
||||||
|
### 규칙 설정
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
root: {
|
||||||
|
type: "stack",
|
||||||
|
direction: "vertical",
|
||||||
|
gap: 16,
|
||||||
|
padding: { top: 16, right: 16, bottom: 16, left: 16 },
|
||||||
|
children: ["header", "form", "buttons"]
|
||||||
|
},
|
||||||
|
|
||||||
|
containers: {
|
||||||
|
"header": {
|
||||||
|
type: "stack",
|
||||||
|
direction: "horizontal",
|
||||||
|
alignItems: "center",
|
||||||
|
children: ["title", "status"]
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
type: "stack",
|
||||||
|
direction: "vertical",
|
||||||
|
gap: 12,
|
||||||
|
children: ["field1", "field2", "field3"]
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
type: "stack",
|
||||||
|
direction: "horizontal",
|
||||||
|
gap: 12,
|
||||||
|
responsive: [
|
||||||
|
{ breakpoint: 480, direction: "vertical" }
|
||||||
|
],
|
||||||
|
children: ["cancelBtn", "submitBtn"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
"title": { width: "hug", height: "hug" },
|
||||||
|
"status": { width: "hug", height: "hug" },
|
||||||
|
"field1": { width: "fill", height: "fixed", fixedHeight: 48 },
|
||||||
|
"field2": { width: "fill", height: "fixed", fixedHeight: 48 },
|
||||||
|
"field3": { width: "fill", height: "fixed", fixedHeight: 48 },
|
||||||
|
"cancelBtn": { width: "fill", height: "fixed", fixedHeight: 48 },
|
||||||
|
"submitBtn": { width: "fill", height: "fixed", fixedHeight: 48 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 결과
|
||||||
|
|
||||||
|
```
|
||||||
|
태블릿 가로 (1024px) 모바일 세로 (375px)
|
||||||
|
┌──────────────────────────┐ ┌─────────────────┐
|
||||||
|
│ 작업지시 #1234 [진행중]│ │작업지시 [진행]│
|
||||||
|
├──────────────────────────┤ ├─────────────────┤
|
||||||
|
│ [품번____________] │ │[품번_________] │
|
||||||
|
│ [품명____________] │ │[품명_________] │
|
||||||
|
│ [수량____________] │ │[수량_________] │
|
||||||
|
├──────────────────────────┤ ├─────────────────┤
|
||||||
|
│ [취소] [작업완료] │ │[취소] │
|
||||||
|
└──────────────────────────┘ │[작업완료] │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3 vs v4 비교
|
||||||
|
|
||||||
|
| 항목 | v3 (기존) | v4 (신규) |
|
||||||
|
|------|----------|----------|
|
||||||
|
| **설계 방식** | 4개 모드 각각 위치 설정 | 3가지 규칙 설정 |
|
||||||
|
| **작업량** | 4배 | 1배 |
|
||||||
|
| **데이터** | col, row, colSpan, rowSpan | width, height, direction, gap |
|
||||||
|
| **반응형** | 수동 (모드별 설정) | 자동 (브레이크포인트) |
|
||||||
|
| **유지보수** | 4곳 수정 | 1곳 수정 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 규칙 설계 체크리스트
|
||||||
|
|
||||||
|
### 크기 규칙
|
||||||
|
- [ ] 터치 요소(버튼, 입력창) 높이: fixed 48px
|
||||||
|
- [ ] 너비가 화면에 맞아야 하는 요소: fill
|
||||||
|
- [ ] 내용 길이에 맞아야 하는 요소: hug
|
||||||
|
|
||||||
|
### 배치 규칙
|
||||||
|
- [ ] 컴포넌트 나열 방향 결정 (가로/세로)
|
||||||
|
- [ ] 간격 설정 (8/16/24px)
|
||||||
|
- [ ] 정렬 방식 결정 (start/center/stretch)
|
||||||
|
|
||||||
|
### 반응형 규칙
|
||||||
|
- [ ] 768px 이하에서 가로→세로 전환 필요한 곳
|
||||||
|
- [ ] 480px 이하에서 추가 조정 필요한 곳
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
|
||||||
|
- [반응형 디자인 가이드](./RESPONSIVE_DESIGN_GUIDE.md) - 크기 기준
|
||||||
|
- [크기 프리셋](./SIZE_PRESETS.md) - 컴포넌트별 기본값
|
||||||
|
- [v4 구현 계획](./POP_V4_CONSTRAINT_SYSTEM_PLAN.md) - 전체 계획
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 업데이트: 2026-02-04*
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
# ADR-001: v4 제약조건 기반 레이아웃 채택
|
||||||
|
|
||||||
|
**날짜**: 2026-02-03
|
||||||
|
**상태**: 채택됨
|
||||||
|
**의사결정자**: 프로젝트 담당자
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
|
||||||
|
v3에서는 4개 모드(tablet_landscape, tablet_portrait, mobile_landscape, mobile_portrait)에 대해 각각 컴포넌트 위치를 설정해야 했습니다.
|
||||||
|
|
||||||
|
**문제점**:
|
||||||
|
1. 같은 컴포넌트를 4번 배치해야 함 (4배 작업량)
|
||||||
|
2. 모드 간 일관성 유지 어려움
|
||||||
|
3. 새 모드 추가 시 또 다른 배치 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 결정
|
||||||
|
|
||||||
|
**"단일 소스 + 자동 적응" 방식 채택**
|
||||||
|
|
||||||
|
Figma, Framer, Flutter, SwiftUI에서 사용하는 업계 표준 접근법:
|
||||||
|
- 하나의 레이아웃 정의
|
||||||
|
- 제약조건(constraints) 설정
|
||||||
|
- 모든 화면에 자동 적응
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 핵심 규칙 3가지
|
||||||
|
|
||||||
|
### 1. 크기 규칙 (Size Rules)
|
||||||
|
|
||||||
|
| 모드 | 설명 | 예시 |
|
||||||
|
|------|------|------|
|
||||||
|
| fixed | 고정 px | 버튼 높이 48px |
|
||||||
|
| fill | 부모 채움 | 입력창 100% |
|
||||||
|
| hug | 내용 맞춤 | 라벨 = 텍스트 길이 |
|
||||||
|
|
||||||
|
### 2. 배치 규칙 (Layout Rules)
|
||||||
|
|
||||||
|
- 스택 방향: horizontal / vertical
|
||||||
|
- 줄바꿈: wrap / nowrap
|
||||||
|
- 간격: gap (8/16/24px)
|
||||||
|
- 정렬: start / center / end / stretch
|
||||||
|
|
||||||
|
### 3. 반응형 규칙 (Responsive Rules)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
direction: "horizontal",
|
||||||
|
responsive: [
|
||||||
|
{ breakpoint: 768, direction: "vertical" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 구현 방식
|
||||||
|
|
||||||
|
**Flexbox 기반** (CSS Grid 아님)
|
||||||
|
|
||||||
|
이유:
|
||||||
|
- 1차원 배치에 최적화
|
||||||
|
- 자동 크기 계산 (hug)
|
||||||
|
- 반응형 전환 간단
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 대안 검토
|
||||||
|
|
||||||
|
### A. 기존 4모드 유지 (기각)
|
||||||
|
|
||||||
|
장점: 기존 코드 변경 없음
|
||||||
|
단점: 근본 문제 해결 안 됨
|
||||||
|
|
||||||
|
### B. CSS Grid 기반 (기각)
|
||||||
|
|
||||||
|
장점: 2차원 배치 가능
|
||||||
|
단점: hug 구현 복잡, 학습 곡선
|
||||||
|
|
||||||
|
### C. 제약조건 기반 (채택)
|
||||||
|
|
||||||
|
장점: 업계 표준, 1회 설계
|
||||||
|
단점: 기존 v3와 호환성 고려 필요
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 영향
|
||||||
|
|
||||||
|
### 변경 필요
|
||||||
|
|
||||||
|
- 타입 정의 (PopLayoutDataV4)
|
||||||
|
- 렌더러 (Flexbox 기반)
|
||||||
|
- 디자이너 UI (제약조건 편집)
|
||||||
|
|
||||||
|
### 호환성
|
||||||
|
|
||||||
|
- v3 레이아웃은 기존 방식으로 계속 작동
|
||||||
|
- v4는 새로운 레이아웃에만 적용
|
||||||
|
- 점진적 마이그레이션 가능
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 참조
|
||||||
|
|
||||||
|
- Figma Auto Layout: https://help.figma.com/hc/en-us/articles/5731482952599-Using-auto-layout
|
||||||
|
- Flutter Flex: https://docs.flutter.dev/development/ui/layout
|
||||||
|
- SwiftUI Stacks: https://developer.apple.com/documentation/swiftui/hstack
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 관련
|
||||||
|
|
||||||
|
- rangraph 검색: "v4 constraint", "layout system"
|
||||||
|
- SPEC.md: 상세 규칙
|
||||||
|
- PLAN.md: 구현 로드맵
|
||||||
Loading…
Reference in New Issue