ERP-node/popdocs/SPEC.md

5.1 KiB

POP 기술 스펙

버전: v5 (CSS Grid 기반)


v5 핵심 규칙

1. 그리드 시스템

모드 화면 너비 칸 수 대상 기기
mobile_portrait ~479px 4칸 아이폰 SE ~ 갤럭시 S (세로)
mobile_landscape 480~767px 6칸 스마트폰 가로, 작은 태블릿
tablet_portrait 768~1023px 8칸 iPad Mini ~ iPad Pro (세로)
tablet_landscape 1024px~ 12칸 10~14인치 태블릿 가로 (기본)

브레이크포인트 기준: 실제 기기 CSS 뷰포트 너비 기반 (2026-02-06 재설계)

2. 위치 지정

interface PopGridPosition {
  col: number;      // 시작 열 (1부터)
  row: number;      // 시작 행 (1부터)
  colSpan: number;  // 열 크기 (1~12)
  rowSpan: number;  // 행 크기 (1~)
}

3. 브레이크포인트 설정

const GRID_BREAKPOINTS = {
  mobile_portrait: { 
    columns: 4, 
    rowHeight: 48, 
    gap: 8, 
    padding: 12,
    maxWidth: 479,    // 아이폰 SE (375px) ~ 갤럭시 S (360px)
  },
  mobile_landscape: { 
    columns: 6, 
    rowHeight: 44, 
    gap: 8, 
    padding: 16,
    minWidth: 480,
    maxWidth: 767,    // 스마트폰 가로
  },
  tablet_portrait: { 
    columns: 8, 
    rowHeight: 52, 
    gap: 12, 
    padding: 20,
    minWidth: 768,    // iPad Mini 세로 (768px)
    maxWidth: 1023,
  },
  tablet_landscape: { 
    columns: 12, 
    rowHeight: 56, 
    gap: 12, 
    padding: 24,
    minWidth: 1024,   // iPad Pro 11 가로 (1194px), 12.9 가로 (1366px)
  },
};

4. 세로 자동 확장

// 캔버스 높이 동적 계산
const MIN_CANVAS_HEIGHT = 600;  // 최소 높이 (px)
const CANVAS_EXTRA_ROWS = 3;    // 항상 유지되는 여유 행 수

const dynamicCanvasHeight = useMemo(() => {
  // 가장 아래 컴포넌트 위치 계산
  const maxRowEnd = visibleComps.reduce((max, comp) => {
    const rowEnd = pos.row + pos.rowSpan;
    return Math.max(max, rowEnd);
  }, 1);

  // 여유 행 추가하여 높이 계산
  const totalRows = maxRowEnd + CANVAS_EXTRA_ROWS;
  return Math.max(MIN_CANVAS_HEIGHT, totalRows * rowHeight + padding);
}, [layout.components, ...]);

특징:

  • 디자이너: 세로 무한 확장 (컴포넌트 추가에 제한 없음)
  • 뷰어: 터치 스크롤로 아래 컴포넌트 접근 가능

데이터 구조

v5 레이아웃

interface PopLayoutDataV5 {
  version: "pop-5.0";
  metadata: {
    screenId: number;
    createdAt: string;
    updatedAt: string;
  };
  gridConfig: {
    defaultMode: GridMode;
    maxRows: number;
  };
  components: PopComponentDefinitionV5[];
  globalSettings: {
    backgroundColor: string;
    padding: number;
  };
}

v5 컴포넌트

interface PopComponentDefinitionV5 {
  id: string;
  type: PopComponentType;  // "pop-label" | "pop-button" | ...
  label: string;
  gridPosition: PopGridPosition;
  config: PopComponentConfig;
  visibility: Record<GridMode, boolean>;  // 모드별 표시/숨김
  modeOverrides?: Record<GridMode, PopModeOverrideV5>;  // 모드별 오버라이드
}

컴포넌트 타입

type PopComponentType = 
  | "pop-label"      // 텍스트 라벨
  | "pop-button"     // 버튼
  | "pop-input"      // 입력 필드
  | "pop-select"     // 선택 박스
  | "pop-grid"       // 데이터 그리드
  | "pop-container"; // 컨테이너

크기 프리셋

터치 요소

요소 일반 산업용
버튼 높이 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) - 텍스트
담는 것 → 칸 (colSpan) - 컨테이너

위치 변환

12칸 기준으로 설계 → 다른 모드에서 자동 변환

// 12칸 → 4칸 변환 예시
const ratio = 4 / 12; // = 0.333

original: { col: 1, colSpan: 6 }  // 12칸에서 절반
converted: { col: 1, colSpan: 2 } // 4칸에서 절반

Troubleshooting

컴포넌트가 얇게 보임

  • 증상: rowSpan이 적용 안됨
  • 원인: gridTemplateRows 고정 px
  • 해결: 1fr 사용

모드 전환 안 됨

  • 증상: 화면 크기 변경해도 레이아웃 유지
  • 해결: detectGridMode() 사용

겹침 발생

  • 증상: 컴포넌트끼리 겹침
  • 해결: resolveOverlaps() 호출

타입 가드

// v5 레이아웃 판별
function isV5Layout(data: any): data is PopLayoutDataV5 {
  return data?.version === "pop-5.0";
}

// 사용 예시
if (isV5Layout(savedData)) {
  setLayout(savedData);
} else {
  setLayout(createEmptyPopLayoutV5());
}

상세 아키텍처: ARCHITECTURE.md 파일 목록: FILES.md