390 lines
8.9 KiB
Markdown
390 lines
8.9 KiB
Markdown
|
|
# 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*
|