/** * IconPickerWidget.tsx — sirsoft-basic 템플릿 소유 아이콘 피커 위젯 * * 레이아웃 편집기 [속성] 탭에서 아이콘명 prop(`Icon.name`/`IconButton.iconName` 등)을 * **검색 가능한 그리드 드롭다운**으로 고른다. 값은 아이콘명 문자열(propValue 로 * `node.props[propKey]` 기록). * * **소유 경계**: 아이콘 검색 그리드 UI 는 라이브러리(Font Awesome) 종속 * 지식이므로 **템플릿이 소유**한다. 코어는 icon-picker 위젯을 제공하지 않으며, 본 위젯을 * `G7Core.layoutEditor.registerWidget('icon-picker', ...)` 로 등록해 사용한다(코어 예약 * 접수함이 편집기 로드 전 등록도 큐로 보존 → 편집기 로드 시 flush). 카탈로그(`control.icons`) * 도 템플릿 editor-spec 이 공급한다. 본 위젯은 Font Awesome 클래스(`fas fa-{value}`)로 * 프리뷰를 직접 렌더한다(템플릿이 자기 라이브러리를 알기 때문). * * 카탈로그 미공급 시 자유입력(text) 폴백. */ import React from 'react'; /** 코어 WidgetProps 와 호환되는 최소 형태 — 코어 타입 import 회피(IIFE 번들 분리 안전) */ interface IconCatalogEntry { value: string; label?: string; keywords?: string[]; group?: string; preview?: { html?: string; className?: string }; } interface IconPickerWidgetProps { control: { icons?: unknown; iconColumns?: number; iconSearchPlaceholder?: string } & Record< string, unknown >; value: unknown; onChange: (value: unknown) => void; t: (key: string, params?: Record) => string; } function readCatalog(control: IconPickerWidgetProps['control']): IconCatalogEntry[] { const icons = control.icons; if (!Array.isArray(icons)) return []; return icons.filter( (e): e is IconCatalogEntry => !!e && typeof e === 'object' && typeof (e as IconCatalogEntry).value === 'string', ); } function entryLabel(entry: IconCatalogEntry, t: IconPickerWidgetProps['t']): string { const label = entry.label; if (typeof label === 'string') return label.startsWith('$t:') ? t(label.slice(3)) : label; return entry.value; } function matchesQuery(entry: IconCatalogEntry, label: string, q: string): boolean { if (q === '') return true; const needle = q.toLowerCase(); if (entry.value.toLowerCase().includes(needle)) return true; if (label.toLowerCase().includes(needle)) return true; return (entry.keywords ?? []).some((k) => k.toLowerCase().includes(needle)); } /** FA 프리뷰 — preview.html → preview.className → `fas fa-{value}` (FA 직접 렌더) → 텍스트 */ function IconPreview({ entry }: { entry: IconCatalogEntry }): React.ReactElement { const preview = entry.preview; if (preview?.html) { return ( ); } if (preview?.className) { return ; } // 템플릿은 자기 라이브러리(Font Awesome)를 알므로 클래스 직접 합성. return