12 KiB
12 KiB
Phase 3 완료 요약
날짜: 2026-02-04
상태: 완료 ✅
버전: v4.0 Phase 3
🎯 달성 목표
Phase 2의 배치 고정 기능 이후, 다음 3가지 핵심 기능 추가:
- ✅ 모드별 컴포넌트 표시/숨김 (visibility)
- ✅ 강제 줄바꿈 컴포넌트 (pop-break)
- ✅ 컴포넌트 오버라이드 병합 (모드별 설정 변경)
📦 구현 내용
1. 타입 정의
파일: frontend/components/pop/designer/types/pop-layout.ts
// pop-break 추가
export type PopComponentType =
| "pop-field"
| "pop-button"
| "pop-list"
| "pop-indicator"
| "pop-scanner"
| "pop-numpad"
| "pop-spacer"
| "pop-break"; // 🆕
// visibility 속성 추가
export interface PopComponentDefinitionV4 {
id: string;
type: PopComponentType;
size: PopSizeConstraintV4;
visibility?: {
tablet_landscape?: boolean;
tablet_portrait?: boolean;
mobile_landscape?: boolean;
mobile_portrait?: boolean;
};
// ...
}
// 기본 크기
defaultSizes["pop-break"] = {
width: "fill", // 100% 너비
height: "fixed",
fixedHeight: 0, // 높이 0
};
2. 렌더러 로직
파일: frontend/components/pop/designer/renderers/PopFlexRenderer.tsx
visibility 체크
const isComponentVisible = (component: PopComponentDefinitionV4): boolean => {
if (!component.visibility) return true; // 기본값: 표시
const modeVisibility = component.visibility[currentMode];
return modeVisibility !== false; // undefined도 true로 취급
};
컴포넌트 오버라이드 병합
const getMergedComponent = (baseComponent: PopComponentDefinitionV4) => {
if (currentMode === "tablet_landscape") return baseComponent;
const override = overrides?.[currentMode]?.components?.[baseComponent.id];
if (!override) return baseComponent;
// 깊은 병합 (config, size)
return {
...baseComponent,
...override,
size: { ...baseComponent.size, ...override.size },
config: { ...baseComponent.config, ...override.config },
};
};
pop-break 렌더링
if (mergedComponent.type === "pop-break") {
return (
<div
style={{ flexBasis: "100%" }} // 핵심: 100% 너비로 줄바꿈 강제
className={isDesignMode
? "h-4 border-2 border-dashed border-gray-300"
: "h-0"
}
>
{isDesignMode && <span>줄바꿈</span>}
</div>
);
}
3. 삭제 함수 개선
파일: frontend/components/pop/designer/types/pop-layout.ts
export const removeComponentFromV4Layout = (
layout: PopLayoutDataV4,
componentId: string
): PopLayoutDataV4 => {
// 1. components에서 삭제
const { [componentId]: _, ...remainingComponents } = layout.components;
// 2. root.children에서 제거
const newRoot = removeChildFromContainer(layout.root, componentId);
// 3. 🆕 모든 오버라이드에서 제거
const newOverrides = cleanupOverridesAfterDelete(layout.overrides, componentId);
return {
...layout,
root: newRoot,
components: remainingComponents,
overrides: newOverrides,
};
};
오버라이드 정리 로직
function cleanupOverridesAfterDelete(
overrides: PopLayoutDataV4["overrides"],
componentId: string
) {
// 각 모드별로:
// 1. containers.root.children에서 componentId 제거
// 2. components[componentId] 제거
// 3. 빈 오버라이드 자동 삭제
}
4. 속성 패널 UI
파일: frontend/components/pop/designer/panels/ComponentEditorPanelV4.tsx
"표시" 탭 추가
<TabsList>
<TabsTrigger value="size">크기</TabsTrigger>
<TabsTrigger value="settings">설정</TabsTrigger>
<TabsTrigger value="visibility">
<Eye className="h-3 w-3" />
표시
</TabsTrigger>
<TabsTrigger value="data">데이터</TabsTrigger>
</TabsList>
VisibilityForm 컴포넌트
function VisibilityForm({ component, onUpdate }) {
const modes = [
{ key: "tablet_landscape", label: "태블릿 가로" },
{ key: "tablet_portrait", label: "태블릿 세로" },
{ key: "mobile_landscape", label: "모바일 가로" },
{ key: "mobile_portrait", label: "모바일 세로" },
];
return (
<div>
{modes.map(({ key, label }) => (
<input
type="checkbox"
checked={component.visibility?.[key] !== false}
onChange={(e) => {
onUpdate?.({
visibility: {
...component.visibility,
[key]: e.target.checked,
},
});
}}
/>
))}
</div>
);
}
5. 팔레트 업데이트
파일: frontend/components/pop/designer/panels/ComponentPaletteV4.tsx
const COMPONENT_PALETTE = [
// ... 기존 컴포넌트들
{
type: "pop-break",
label: "줄바꿈",
icon: WrapText,
description: "강제 줄바꿈 (flex-basis: 100%)",
},
];
🎨 UI 변경사항
컴포넌트 팔레트
컴포넌트
├─ 필드
├─ 버튼
├─ 리스트
├─ 인디케이터
├─ 스캐너
├─ 숫자패드
├─ 스페이서
└─ 줄바꿈 🆕
속성 패널
┌─────────────────────┐
│ 탭: [크기][설정] │
│ [표시📍][데이터] │
├─────────────────────┤
│ 모드별 표시 설정 │
│ ☑ 태블릿 가로 │
│ ☑ 태블릿 세로 │
│ ☐ 모바일 가로 (숨김)│
│ ☑ 모바일 세로 │
├─────────────────────┤
│ 반응형 숨김 │
│ [500] px 이하 숨김 │
└─────────────────────┘
📖 사용 예시
예시 1: 모바일 전용 버튼
{
id: "call-button",
type: "pop-button",
label: "전화 걸기",
visibility: {
tablet_landscape: false, // 태블릿: 숨김
tablet_portrait: false,
mobile_landscape: true, // 모바일: 표시
mobile_portrait: true,
},
}
결과:
- 태블릿 화면: "전화 걸기" 버튼 안 보임
- 모바일 화면: "전화 걸기" 버튼 보임
예시 2: 모드별 줄바꿈
레이아웃: [A] [B] [줄바꿈] [C] [D]
줄바꿈 설정:
{
id: "break-1",
type: "pop-break",
visibility: {
tablet_landscape: false, // 태블릿: 줄바꿈 숨김
mobile_portrait: true, // 모바일: 줄바꿈 표시
}
}
결과:
태블릿 가로 (1024px):
┌───────────────────────────┐
│ [A] [B] [C] [D] │ ← 한 줄
└───────────────────────────┘
모바일 세로 (375px):
┌─────────────────┐
│ [A] [B] │ ← 첫 줄
│ [C] [D] │ ← 둘째 줄 (줄바꿈 적용)
└─────────────────┘
예시 3: 리스트 컬럼 수 변경 (확장 가능)
// 기본 (태블릿 가로)
{
id: "product-list",
type: "pop-list",
config: {
columns: 7, // 7개 컬럼
}
}
// 오버라이드 (모바일 세로)
overrides: {
mobile_portrait: {
components: {
"product-list": {
config: {
columns: 3, // 3개 컬럼
}
}
}
}
}
결과:
- 태블릿: 7개 컬럼 표시
- 모바일: 3개 컬럼 표시 (자동 병합)
🧪 테스트 시나리오
✅ 테스트 1: 줄바꿈 기본 동작
- 팔레트에서 "줄바꿈" 드래그
- 컴포넌트 사이에 드롭
- 디자인 모드에서 점선 "줄바꿈" 표시 확인
- 미리보기에서 줄바꿈이 안 보이는지 확인
✅ 테스트 2: 모드별 줄바꿈
- 줄바꿈 컴포넌트 추가
- "표시" 탭 → 태블릿 모드 체크 해제
- 태블릿 가로: 한 줄
- 모바일 세로: 두 줄
✅ 테스트 3: 삭제 시 오버라이드 정리
- 모바일 세로에서 배치 고정
- 컴포넌트 삭제
- 저장 후 로드
- DB 확인: overrides에서도 제거되었는지
✅ 테스트 4: 컴포넌트 숨김
- 컴포넌트 선택
- "표시" 탭 → 태블릿 모드 체크 해제
- 태블릿: 컴포넌트 안 보임
- 모바일: 컴포넌트 보임
✅ 테스트 5: 속성 패널 UI
- 컴포넌트 선택
- "표시" 탭 클릭
- 4개 체크박스 확인
- 체크 해제 시 "(숨김)" 표시
- 저장 후 로드 → 상태 유지
📝 수정된 파일
코드 파일 (5개)
✅ frontend/components/pop/designer/types/pop-layout.ts
- PopComponentType 확장 (pop-break)
- PopComponentDefinitionV4.visibility 추가
- cleanupOverridesAfterDelete() 추가
✅ frontend/components/pop/designer/renderers/PopFlexRenderer.tsx
- isComponentVisible() 추가
- getMergedComponent() 추가
- pop-break 렌더링 추가
- ContainerRenderer props 확장
✅ frontend/components/pop/designer/panels/ComponentEditorPanelV4.tsx
- "표시" 탭 추가
- VisibilityForm 컴포넌트 추가
- COMPONENT_TYPE_LABELS 업데이트
✅ frontend/components/pop/designer/panels/ComponentPaletteV4.tsx
- "줄바꿈" 컴포넌트 추가
✅ frontend/components/pop/designer/PopDesigner.tsx
- (기존 Phase 2 변경사항 유지)
문서 파일 (6개)
✅ popdocs/CHANGELOG.md
- Phase 3 완료 기록
✅ popdocs/PLAN.md
- Phase 3 체크 완료
- Phase 4 계획 추가
✅ popdocs/V4_UNIFIED_DESIGN_SPEC.md
- Phase 3 섹션 추가
✅ popdocs/components-spec.md
- pop-break 상세 스펙 추가
- Phase 3 업데이트 노트
✅ popdocs/README.md
- 현재 상태 업데이트
- Phase 3 요약 추가
✅ popdocs/decisions/002-phase3-visibility-break.md (신규)
- 상세 설계 문서
✅ popdocs/PHASE3_SUMMARY.md (신규)
- 이 문서
🎓 핵심 개념
Flexbox 줄바꿈 원리
/* 컨테이너 */
.container {
display: flex;
flex-direction: row;
flex-wrap: wrap; /* 필수 */
}
/* pop-break */
.pop-break {
flex-basis: 100%; /* 전체 너비 차지 → 다음 요소는 새 줄로 */
height: 0; /* 실제로는 안 보임 */
}
visibility vs hideBelow
| 속성 | 제어 방식 | 용도 |
|---|---|---|
visibility |
모드별 명시적 | 특정 모드에서만 표시 (예: 모바일 전용) |
hideBelow |
픽셀 기반 자동 | 화면 너비에 따라 자동 숨김 (예: 500px 이하) |
예시:
{
visibility: {
tablet_landscape: false, // 태블릿 가로: 무조건 숨김
},
hideBelow: 500, // 500px 이하: 자동 숨김 (다른 모드에서도)
}
🚀 다음 단계
Phase 4: 실제 컴포넌트 구현
우선순위:
1. pop-field (입력/표시 필드)
2. pop-button (액션 버튼)
3. pop-list (데이터 리스트)
4. pop-indicator (KPI 표시)
5. pop-scanner (바코드/QR)
6. pop-numpad (숫자 입력)
추가 개선 사항
1. 컴포넌트 오버라이드 UI
- 리스트 컬럼 수 조정 UI
- 버튼 스타일 변경 UI
- 필드 표시 형식 변경 UI
2. "모든 모드에 적용" 기능
- 한 번에 모든 모드 체크/해제
3. 오버라이드 비교 뷰
- 기본값 vs 오버라이드 차이 시각화
✨ 주요 성과
- ✅ 모드별 컴포넌트 제어: visibility 속성으로 유연한 표시/숨김
- ✅ Flexbox 줄바꿈 해결: pop-break 컴포넌트로 업계 표준 달성
- ✅ 확장 가능한 구조: 컴포넌트 오버라이드 병합으로 추후 기능 추가 용이
- ✅ 데이터 일관성: 삭제 시 오버라이드 자동 정리로 데이터 무결성 유지
- ✅ 직관적인 UI: 체크박스 기반 visibility 제어
📚 참고 문서
- decisions/002-phase3-visibility-break.md - 상세 설계
- V4_UNIFIED_DESIGN_SPEC.md - v4 통합 설계
- CHANGELOG.md - 변경 이력
- PLAN.md - 로드맵
Phase 3 완료 - 2026-02-04 다음: Phase 4 (실제 컴포넌트 구현)