"use client"; import React from "react"; /** * POP 컴포넌트 정의 인터페이스 */ export interface PopComponentDefinition { id: string; name: string; description: string; category: PopComponentCategory; icon?: string; component: React.ComponentType; configPanel?: React.ComponentType; preview?: React.ComponentType<{ config?: any }>; // 디자이너 미리보기용 defaultProps?: Record; // POP 전용 속성 touchOptimized?: boolean; minTouchArea?: number; supportedDevices?: ("mobile" | "tablet")[]; createdAt?: Date; updatedAt?: Date; } /** * POP 컴포넌트 카테고리 */ export type PopComponentCategory = | "display" // 데이터 표시 (카드, 리스트, 배지) | "input" // 입력 (스캐너, 터치 입력) | "action" // 액션 (버튼, 스와이프) | "layout" // 레이아웃 (컨테이너, 그리드) | "feedback"; // 피드백 (토스트, 로딩) /** * POP 컴포넌트 레지스트리 이벤트 */ export interface PopComponentRegistryEvent { type: "component_registered" | "component_unregistered"; data: PopComponentDefinition; timestamp: Date; } /** * POP 컴포넌트 레지스트리 클래스 * 모바일/태블릿 전용 컴포넌트를 등록, 관리, 조회할 수 있는 중앙 레지스트리 */ export class PopComponentRegistry { private static components = new Map(); private static eventListeners: Array<(event: PopComponentRegistryEvent) => void> = []; /** * 컴포넌트 등록 */ static registerComponent(definition: PopComponentDefinition): void { // 유효성 검사 if (!definition.id || !definition.name || !definition.component) { throw new Error( `POP 컴포넌트 등록 실패 (${definition.id || "unknown"}): 필수 필드 누락` ); } // 중복 등록 체크 if (this.components.has(definition.id)) { console.warn(`[POP Registry] 컴포넌트 중복 등록: ${definition.id} - 기존 정의를 덮어씁니다.`); } // 타임스탬프 추가 const enhancedDefinition: PopComponentDefinition = { ...definition, touchOptimized: definition.touchOptimized ?? true, minTouchArea: definition.minTouchArea ?? 44, supportedDevices: definition.supportedDevices ?? ["mobile", "tablet"], createdAt: definition.createdAt || new Date(), updatedAt: new Date(), }; this.components.set(definition.id, enhancedDefinition); // 이벤트 발생 this.emitEvent({ type: "component_registered", data: enhancedDefinition, timestamp: new Date(), }); // 개발 모드에서만 로깅 if (process.env.NODE_ENV === "development") { console.log(`[POP Registry] 컴포넌트 등록: ${definition.id}`); } } /** * 컴포넌트 등록 해제 */ static unregisterComponent(id: string): void { const definition = this.components.get(id); if (!definition) { console.warn(`[POP Registry] 등록되지 않은 컴포넌트 해제 시도: ${id}`); return; } this.components.delete(id); // 이벤트 발생 this.emitEvent({ type: "component_unregistered", data: definition, timestamp: new Date(), }); console.log(`[POP Registry] 컴포넌트 해제: ${id}`); } /** * 특정 컴포넌트 조회 */ static getComponent(id: string): PopComponentDefinition | undefined { return this.components.get(id); } /** * URL로 컴포넌트 조회 */ static getComponentByUrl(url: string): PopComponentDefinition | undefined { // "@/lib/registry/pop-components/pop-card-list" → "pop-card-list" const parts = url.split("/"); const componentId = parts[parts.length - 1]; return this.getComponent(componentId); } /** * 모든 컴포넌트 조회 */ static getAllComponents(): PopComponentDefinition[] { return Array.from(this.components.values()).sort((a, b) => { // 카테고리별 정렬, 그 다음 이름순 if (a.category !== b.category) { return a.category.localeCompare(b.category); } return a.name.localeCompare(b.name); }); } /** * 카테고리별 컴포넌트 조회 */ static getComponentsByCategory(category: PopComponentCategory): PopComponentDefinition[] { return Array.from(this.components.values()) .filter((def) => def.category === category) .sort((a, b) => a.name.localeCompare(b.name)); } /** * 디바이스별 컴포넌트 조회 */ static getComponentsByDevice(device: "mobile" | "tablet"): PopComponentDefinition[] { return Array.from(this.components.values()) .filter((def) => def.supportedDevices?.includes(device)) .sort((a, b) => a.name.localeCompare(b.name)); } /** * 컴포넌트 검색 */ static searchComponents(query: string): PopComponentDefinition[] { const lowerQuery = query.toLowerCase(); return Array.from(this.components.values()).filter( (def) => def.id.toLowerCase().includes(lowerQuery) || def.name.toLowerCase().includes(lowerQuery) || def.description?.toLowerCase().includes(lowerQuery) ); } /** * 등록된 컴포넌트 개수 */ static getComponentCount(): number { return this.components.size; } /** * 카테고리별 통계 */ static getStatsByCategory(): Record { const stats: Record = { display: 0, input: 0, action: 0, layout: 0, feedback: 0, }; for (const def of this.components.values()) { stats[def.category]++; } return stats; } /** * 이벤트 리스너 등록 */ static addEventListener(callback: (event: PopComponentRegistryEvent) => void): void { this.eventListeners.push(callback); } /** * 이벤트 리스너 해제 */ static removeEventListener(callback: (event: PopComponentRegistryEvent) => void): void { const index = this.eventListeners.indexOf(callback); if (index > -1) { this.eventListeners.splice(index, 1); } } /** * 이벤트 발생 */ private static emitEvent(event: PopComponentRegistryEvent): void { for (const listener of this.eventListeners) { try { listener(event); } catch (error) { console.error("[POP Registry] 이벤트 리스너 오류:", error); } } } /** * 레지스트리 초기화 (테스트용) */ static clear(): void { this.components.clear(); console.log("[POP Registry] 레지스트리 초기화됨"); } /** * 컴포넌트 존재 여부 확인 */ static hasComponent(id: string): boolean { return this.components.has(id); } /** * 디버그 정보 출력 */ static debug(): void { console.group("[POP Registry] 등록된 컴포넌트"); console.log(`총 ${this.components.size}개 컴포넌트`); console.table( Array.from(this.components.values()).map((c) => ({ id: c.id, name: c.name, category: c.category, touchOptimized: c.touchOptimized, devices: c.supportedDevices?.join(", "), })) ); console.groupEnd(); } } // 기본 export export default PopComponentRegistry;