232 lines
7.4 KiB
TypeScript
232 lines
7.4 KiB
TypeScript
/**
|
|
* POP 컴포넌트 설정 스키마 및 유틸리티
|
|
*
|
|
* POP(모바일/태블릿) 컴포넌트의 overrides 스키마 및 기본값을 관리
|
|
* - 공통 요소는 componentConfig.ts에서 import하여 재사용
|
|
* - POP 전용 컴포넌트의 overrides 스키마만 새로 정의
|
|
*/
|
|
import { z } from "zod";
|
|
|
|
// ============================================
|
|
// 공통 요소 재사용 (componentConfig.ts에서 import)
|
|
// ============================================
|
|
export {
|
|
// 공통 스키마
|
|
customConfigSchema,
|
|
componentV2Schema,
|
|
layoutV2Schema,
|
|
// 공통 유틸리티 함수
|
|
deepMerge,
|
|
mergeComponentConfig,
|
|
extractCustomConfig,
|
|
isDeepEqual,
|
|
getComponentTypeFromUrl,
|
|
} from "./componentConfig";
|
|
|
|
// ============================================
|
|
// POP 전용 URL 생성 함수
|
|
// ============================================
|
|
export function getPopComponentUrl(componentType: string): string {
|
|
return `@/lib/registry/pop-components/${componentType}`;
|
|
}
|
|
|
|
// ============================================
|
|
// POP 전용 컴포넌트 기본값
|
|
// ============================================
|
|
|
|
// POP 카드 리스트 기본값
|
|
export const popCardListDefaults = {
|
|
displayMode: "card" as const,
|
|
cardStyle: "compact" as const,
|
|
showHeader: true,
|
|
showFooter: false,
|
|
pageSize: 10,
|
|
enablePullToRefresh: true,
|
|
enableInfiniteScroll: false,
|
|
cardColumns: 1,
|
|
gap: 8,
|
|
padding: 16,
|
|
// 터치 최적화
|
|
touchFeedback: true,
|
|
swipeActions: false,
|
|
};
|
|
|
|
// POP 터치 버튼 기본값
|
|
export const popTouchButtonDefaults = {
|
|
variant: "primary" as const,
|
|
size: "lg" as const,
|
|
text: "확인",
|
|
icon: null,
|
|
iconPosition: "left" as const,
|
|
fullWidth: true,
|
|
// 터치 최적화
|
|
minHeight: 48, // 최소 터치 영역 48px
|
|
hapticFeedback: true,
|
|
pressDelay: 0,
|
|
};
|
|
|
|
// POP 스캐너 입력 기본값
|
|
export const popScannerInputDefaults = {
|
|
placeholder: "바코드를 스캔하세요",
|
|
showKeyboard: false,
|
|
autoFocus: true,
|
|
autoSubmit: true,
|
|
submitDelay: 300,
|
|
// 스캐너 설정
|
|
scannerMode: "auto" as const,
|
|
beepOnScan: true,
|
|
vibrationOnScan: true,
|
|
clearOnSubmit: true,
|
|
};
|
|
|
|
// POP 상태 배지 기본값
|
|
export const popStatusBadgeDefaults = {
|
|
variant: "default" as const,
|
|
size: "md" as const,
|
|
text: "",
|
|
icon: null,
|
|
// 스타일
|
|
rounded: true,
|
|
pulse: false,
|
|
};
|
|
|
|
// ============================================
|
|
// POP 전용 overrides 스키마
|
|
// ============================================
|
|
|
|
// POP 카드 리스트 overrides 스키마
|
|
export const popCardListOverridesSchema = z
|
|
.object({
|
|
displayMode: z.enum(["card", "list", "grid"]).default("card"),
|
|
cardStyle: z.enum(["compact", "default", "expanded"]).default("compact"),
|
|
showHeader: z.boolean().default(true),
|
|
showFooter: z.boolean().default(false),
|
|
pageSize: z.number().default(10),
|
|
enablePullToRefresh: z.boolean().default(true),
|
|
enableInfiniteScroll: z.boolean().default(false),
|
|
cardColumns: z.number().default(1),
|
|
gap: z.number().default(8),
|
|
padding: z.number().default(16),
|
|
touchFeedback: z.boolean().default(true),
|
|
swipeActions: z.boolean().default(false),
|
|
// 데이터 바인딩
|
|
tableName: z.string().optional(),
|
|
columns: z.array(z.string()).optional(),
|
|
titleField: z.string().optional(),
|
|
subtitleField: z.string().optional(),
|
|
statusField: z.string().optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
// POP 터치 버튼 overrides 스키마
|
|
export const popTouchButtonOverridesSchema = z
|
|
.object({
|
|
variant: z.enum(["primary", "secondary", "success", "warning", "danger", "ghost"]).default("primary"),
|
|
size: z.enum(["sm", "md", "lg", "xl"]).default("lg"),
|
|
text: z.string().default("확인"),
|
|
icon: z.string().nullable().default(null),
|
|
iconPosition: z.enum(["left", "right", "top", "bottom"]).default("left"),
|
|
fullWidth: z.boolean().default(true),
|
|
minHeight: z.number().default(48),
|
|
hapticFeedback: z.boolean().default(true),
|
|
pressDelay: z.number().default(0),
|
|
// 액션
|
|
actionType: z.string().optional(),
|
|
actionParams: z.record(z.string(), z.any()).optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
// POP 스캐너 입력 overrides 스키마
|
|
export const popScannerInputOverridesSchema = z
|
|
.object({
|
|
placeholder: z.string().default("바코드를 스캔하세요"),
|
|
showKeyboard: z.boolean().default(false),
|
|
autoFocus: z.boolean().default(true),
|
|
autoSubmit: z.boolean().default(true),
|
|
submitDelay: z.number().default(300),
|
|
scannerMode: z.enum(["auto", "camera", "external"]).default("auto"),
|
|
beepOnScan: z.boolean().default(true),
|
|
vibrationOnScan: z.boolean().default(true),
|
|
clearOnSubmit: z.boolean().default(true),
|
|
// 데이터 바인딩
|
|
tableName: z.string().optional(),
|
|
columnName: z.string().optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
// POP 상태 배지 overrides 스키마
|
|
export const popStatusBadgeOverridesSchema = z
|
|
.object({
|
|
variant: z.enum(["default", "success", "warning", "danger", "info"]).default("default"),
|
|
size: z.enum(["sm", "md", "lg"]).default("md"),
|
|
text: z.string().default(""),
|
|
icon: z.string().nullable().default(null),
|
|
rounded: z.boolean().default(true),
|
|
pulse: z.boolean().default(false),
|
|
// 조건부 스타일
|
|
conditionField: z.string().optional(),
|
|
conditionMapping: z.record(z.string(), z.string()).optional(),
|
|
})
|
|
.passthrough();
|
|
|
|
// ============================================
|
|
// POP 컴포넌트 overrides 스키마 레지스트리
|
|
// ============================================
|
|
export const popComponentOverridesSchemaRegistry: Record<string, z.ZodTypeAny> = {
|
|
"pop-card-list": popCardListOverridesSchema,
|
|
"pop-touch-button": popTouchButtonOverridesSchema,
|
|
"pop-scanner-input": popScannerInputOverridesSchema,
|
|
"pop-status-badge": popStatusBadgeOverridesSchema,
|
|
};
|
|
|
|
// ============================================
|
|
// POP 컴포넌트 기본값 레지스트리
|
|
// ============================================
|
|
export const popComponentDefaultsRegistry: Record<string, Record<string, any>> = {
|
|
"pop-card-list": popCardListDefaults,
|
|
"pop-touch-button": popTouchButtonDefaults,
|
|
"pop-scanner-input": popScannerInputDefaults,
|
|
"pop-status-badge": popStatusBadgeDefaults,
|
|
};
|
|
|
|
// ============================================
|
|
// POP 기본값 조회 함수
|
|
// ============================================
|
|
export function getPopComponentDefaults(componentType: string): Record<string, any> {
|
|
return popComponentDefaultsRegistry[componentType] || {};
|
|
}
|
|
|
|
// ============================================
|
|
// POP URL로 기본값 조회
|
|
// ============================================
|
|
export function getPopDefaultsByUrl(componentUrl: string): Record<string, any> {
|
|
// "@/lib/registry/pop-components/pop-card-list" → "pop-card-list"
|
|
const parts = componentUrl.split("/");
|
|
const componentType = parts[parts.length - 1];
|
|
return getPopComponentDefaults(componentType);
|
|
}
|
|
|
|
// ============================================
|
|
// POP overrides 파싱 및 검증
|
|
// ============================================
|
|
export function parsePopOverridesByUrl(
|
|
componentUrl: string,
|
|
overrides: Record<string, any>,
|
|
): Record<string, any> {
|
|
const parts = componentUrl.split("/");
|
|
const componentType = parts[parts.length - 1];
|
|
const schema = popComponentOverridesSchemaRegistry[componentType];
|
|
|
|
if (!schema) {
|
|
// 스키마 없으면 그대로 반환
|
|
return overrides || {};
|
|
}
|
|
|
|
try {
|
|
return schema.parse(overrides || {});
|
|
} catch {
|
|
// 파싱 실패 시 기본값 반환
|
|
return getPopComponentDefaults(componentType);
|
|
}
|
|
}
|