"use client"; import { ComponentDefinition, CreateComponentDefinitionOptions, ComponentValidation, DEFAULT_COMPONENT_SIZE, } from "@/types/component"; /** * 컴포넌트 정의 생성 헬퍼 함수 * 타입 안전성을 보장하면서 컴포넌트 정의를 쉽게 생성할 수 있도록 도와줍니다 * 레이아웃 시스템의 createLayoutDefinition과 동일한 패턴 */ export function createComponentDefinition(options: CreateComponentDefinitionOptions): ComponentDefinition { const { id, name, nameEng, description, category, webType, component, renderer, defaultConfig = {}, defaultSize = DEFAULT_COMPONENT_SIZE, configPanel, icon, previewImage, tags = [], version = "1.0.0", author, documentation, validation, dependencies = [], } = options; // 기본 검증 if (!id) { throw new Error("컴포넌트 ID는 필수입니다"); } if (!name) { throw new Error("컴포넌트 이름은 필수입니다"); } if (!description) { throw new Error("컴포넌트 설명은 필수입니다"); } if (!component) { throw new Error("React 컴포넌트는 필수입니다"); } // ID 형식 검증 (kebab-case) if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(id)) { throw new Error("컴포넌트 ID는 kebab-case 형식이어야 합니다 (예: button-primary)"); } // 기본 태그 자동 추가 const enhancedTags = [ ...tags, category, // 카테고리를 태그로 추가 webType, // 웹타입을 태그로 추가 ...name.split(" "), // 이름을 단어별로 분리하여 태그로 추가 ] .filter((tag) => tag && typeof tag === 'string' && tag.trim().length > 0) // undefined, null, 빈 문자열 제거 .filter((tag, index, array) => array.indexOf(tag) === index); // 중복 제거 // 컴포넌트 정의 생성 const definition: ComponentDefinition = { id, name, nameEng, description, category, webType, component, renderer, defaultConfig, defaultSize, configPanel, icon, previewImage, tags: enhancedTags, version, author, documentation, validation, dependencies, createdAt: new Date(), }; // 정의 검증 const validationResult = validateComponentDefinition(definition); if (!validationResult.isValid) { throw new Error(`컴포넌트 정의 검증 실패: ${validationResult.errors.join(", ")}`); } // 경고사항 출력 (개발 모드에서만) - 로그 제거 return definition; } /** * 컴포넌트 정의 유효성 검사 */ export function validateComponentDefinition(definition: ComponentDefinition): { isValid: boolean; errors: string[]; warnings: string[]; } { const errors: string[] = []; const warnings: string[] = []; // 필수 필드 검사 if (!definition.id) errors.push("id는 필수입니다"); if (!definition.name) errors.push("name은 필수입니다"); if (!definition.description) errors.push("description은 필수입니다"); if (!definition.category) errors.push("category는 필수입니다"); if (!definition.webType) errors.push("webType은 필수입니다"); if (!definition.component) errors.push("component는 필수입니다"); if (!definition.defaultSize) errors.push("defaultSize는 필수입니다"); // ID 형식 검사 if (definition.id && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(definition.id)) { errors.push("id는 kebab-case 형식이어야 합니다 (예: button-primary)"); } // ID 길이 검사 if (definition.id && definition.id.length > 50) { errors.push("id는 50자를 초과할 수 없습니다"); } // 이름 길이 검사 if (definition.name && definition.name.length > 100) { errors.push("name은 100자를 초과할 수 없습니다"); } // 설명 길이 검사 if (definition.description && definition.description.length > 500) { errors.push("description은 500자를 초과할 수 없습니다"); } // 크기 유효성 검사 if (definition.defaultSize) { if (definition.defaultSize.width <= 0) { errors.push("defaultSize.width는 0보다 커야 합니다"); } if (definition.defaultSize.height <= 0) { errors.push("defaultSize.height는 0보다 커야 합니다"); } if (definition.defaultSize.width > 2000) { warnings.push("defaultSize.width가 매우 큽니다 (2000px 초과)"); } if (definition.defaultSize.height > 1000) { warnings.push("defaultSize.height가 매우 큽니다 (1000px 초과)"); } } // 태그 검사 if (definition.tags) { if (definition.tags.length > 20) { warnings.push("태그가 너무 많습니다 (20개 초과)"); } definition.tags.forEach((tag) => { if (tag && typeof tag === 'string' && tag.length > 30) { warnings.push(`태그가 너무 깁니다: ${tag}`); } }); } // 버전 형식 검사 if (definition.version && !/^\d+\.\d+\.\d+/.test(definition.version)) { warnings.push("버전 형식은 semantic versioning을 권장합니다 (예: 1.0.0)"); } // 의존성 검사 if (definition.dependencies && definition.dependencies.length > 10) { warnings.push("의존성이 너무 많습니다 (10개 초과)"); } // 권장사항 검사 if (!definition.icon) { warnings.push("검색 및 식별을 위해 아이콘 설정을 권장합니다"); } if (!definition.tags || definition.tags.length === 0) { warnings.push("검색을 위한 태그 설정을 권장합니다"); } if (!definition.author) { warnings.push("작성자 정보 설정을 권장합니다"); } if (!definition.documentation) { warnings.push("사용법 안내를 위한 문서 URL 설정을 권장합니다"); } return { isValid: errors.length === 0, errors, warnings, }; } /** * 컴포넌트 정의 복제 헬퍼 */ export function cloneComponentDefinition( original: ComponentDefinition, overrides: Partial, ): ComponentDefinition { return createComponentDefinition({ id: original.id, name: original.name, nameEng: original.nameEng, description: original.description, category: original.category, webType: original.webType, component: original.component, renderer: original.renderer, defaultConfig: original.defaultConfig, defaultSize: original.defaultSize, configPanel: original.configPanel, icon: original.icon, previewImage: original.previewImage, tags: original.tags, version: original.version, author: original.author, documentation: original.documentation, validation: original.validation, dependencies: original.dependencies, ...overrides, }); } /** * 웹타입에 따른 기본 설정 생성 */ export function getDefaultConfigByWebType(webType: string): Record { switch (webType) { case "text": return { placeholder: "텍스트를 입력하세요", maxLength: 255, }; case "number": return { min: 0, max: 999999, step: 1, }; case "email": return { placeholder: "이메일을 입력하세요", }; case "password": return { placeholder: "비밀번호를 입력하세요", minLength: 8, }; case "date": return { format: "YYYY-MM-DD", }; case "datetime": return { format: "YYYY-MM-DD HH:mm", }; case "textarea": return { placeholder: "내용을 입력하세요", rows: 3, maxLength: 1000, }; case "select": case "dropdown": return { options: [], placeholder: "선택하세요", }; case "checkbox": return { checked: false, }; case "radio": return { checked: false, group: "radio-group", }; case "button": return { text: "버튼", actionType: "button", variant: "primary", }; case "file": return { multiple: false, accept: "*/*", }; case "range": return { min: 0, max: 100, step: 1, value: 50, }; case "color": return { value: "#000000", }; default: return {}; } } /** * 카테고리에 따른 기본 아이콘 추천 */ export function getDefaultIconByCategory(category: string): string { switch (category) { case "ui": return "Square"; case "input": return "Edit"; case "display": return "Eye"; case "action": return "MousePointer"; case "layout": return "Layout"; case "chart": return "BarChart"; case "form": return "FormInput"; case "media": return "Image"; case "navigation": return "Menu"; case "feedback": return "Bell"; case "utility": return "Settings"; case "container": return "Package"; case "system": return "Cpu"; case "admin": return "Shield"; default: return "Component"; } }