"use client"; import React, { useState, useEffect } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { Check, Plus, X, Info, RotateCcw } from "lucide-react"; import { icons as allLucideIcons } from "lucide-react"; import DOMPurify from "isomorphic-dompurify"; import { cn } from "@/lib/utils"; import { ComponentData } from "@/types/screen"; import { ColorPickerWithTransparent } from "../../common/ColorPickerWithTransparent"; import { actionIconMap, noIconActions, NO_ICON_MESSAGE, iconSizePresets, getLucideIcon, addToIconMap, getDefaultIconForAction, } from "@/lib/button-icon-map"; import type { ButtonTabProps } from "./types"; export const BasicTab: React.FC = ({ component, onUpdateProperty, }) => { const config = component.componentConfig || {}; // 표시 모드, 버튼 텍스트, 액션 타입 const [displayMode, setDisplayMode] = useState<"text" | "icon" | "icon-text">( config.displayMode || "text" ); const [localText, setLocalText] = useState( config.text !== undefined ? config.text : "버튼" ); const [localActionType, setLocalActionType] = useState( String(config.action?.type || "save") ); // 아이콘 설정 상태 const [selectedIcon, setSelectedIcon] = useState(config.icon?.name || ""); const [selectedIconType, setSelectedIconType] = useState<"lucide" | "svg">( config.icon?.type || "lucide" ); const [iconSize, setIconSize] = useState(config.icon?.size || "보통"); const [iconColor, setIconColor] = useState(config.icon?.color || ""); const [iconGap, setIconGap] = useState(config.iconGap ?? 6); const [iconTextPosition, setIconTextPosition] = useState< "right" | "left" | "bottom" | "top" >(config.iconTextPosition || "right"); // 커스텀 아이콘 UI 상태 const [lucideSearchOpen, setLucideSearchOpen] = useState(false); const [lucideSearchTerm, setLucideSearchTerm] = useState(""); const [svgPasteOpen, setSvgPasteOpen] = useState(false); const [svgInput, setSvgInput] = useState(""); const [svgName, setSvgName] = useState(""); const [svgError, setSvgError] = useState(""); // 컴포넌트 prop 변경 시 로컬 상태 동기화 useEffect(() => { const latestConfig = component.componentConfig || {}; const latestAction = latestConfig.action || {}; setLocalText(latestConfig.text !== undefined ? latestConfig.text : "버튼"); setLocalActionType(String(latestAction.type || "save")); setDisplayMode((latestConfig.displayMode as "text" | "icon" | "icon-text") || "text"); setSelectedIcon(latestConfig.icon?.name || ""); setSelectedIconType((latestConfig.icon?.type as "lucide" | "svg") || "lucide"); setIconSize(latestConfig.icon?.size || "보통"); setIconColor(latestConfig.icon?.color || ""); setIconGap(latestConfig.iconGap ?? 6); setIconTextPosition( (latestConfig.iconTextPosition as "right" | "left" | "bottom" | "top") || "right" ); }, [component.id, component.componentConfig?.action?.type]); // 현재 액션의 추천 아이콘 목록 const currentActionIcons = actionIconMap[localActionType] || []; const isNoIconAction = noIconActions.has(localActionType); const customIcons: string[] = config.customIcons || []; const customSvgIcons: Array<{ name: string; svg: string }> = config.customSvgIcons || []; const showIconSettings = displayMode === "icon" || displayMode === "icon-text"; // 아이콘 선택 핸들러 const handleSelectIcon = (iconName: string, iconType: "lucide" | "svg" = "lucide") => { setSelectedIcon(iconName); setSelectedIconType(iconType); onUpdateProperty("componentConfig.icon", { name: iconName, type: iconType, size: iconSize, ...(iconColor ? { color: iconColor } : {}), }); }; // 선택 중인 아이콘이 삭제되었을 때 디폴트 아이콘으로 복귀 const revertToDefaultIcon = () => { const def = getDefaultIconForAction(localActionType); setSelectedIcon(def.name); setSelectedIconType(def.type); handleSelectIcon(def.name, def.type); }; // 표시 모드 변경 핸들러 const handleDisplayModeChange = (mode: "text" | "icon" | "icon-text") => { setDisplayMode(mode); onUpdateProperty("componentConfig.displayMode", mode); if ((mode === "icon" || mode === "icon-text") && !selectedIcon) { revertToDefaultIcon(); } }; // 아이콘 크기 프리셋 변경 const handleIconSizePreset = (preset: string) => { setIconSize(preset); if (selectedIcon) { onUpdateProperty("componentConfig.icon.size", preset); } }; // 아이콘 색상 변경 const handleIconColorChange = (color: string | undefined) => { const val = color || ""; setIconColor(val); if (selectedIcon) { if (val) { onUpdateProperty("componentConfig.icon.color", val); } else { onUpdateProperty("componentConfig.icon.color", undefined); } } }; return (
{/* 표시 모드 선택 */}
{( [ { value: "text", label: "텍스트" }, { value: "icon", label: "아이콘" }, { value: "icon-text", label: "아이콘+텍스트" }, ] as const ).map((opt) => ( ))}
{/* 아이콘 모드 레이아웃 안내 */} {displayMode === "icon" && (
아이콘만 표시할 때는 버튼 영역의 가로 폭을 줄여 정사각형에 가깝게 만들면 더 깔끔합니다.
)} {/* 버튼 텍스트 (텍스트 / 아이콘+텍스트 모드에서 표시) */} {(displayMode === "text" || displayMode === "icon-text") && (
{ const newValue = e.target.value; setLocalText(newValue); onUpdateProperty("componentConfig.text", newValue); }} placeholder="버튼 텍스트를 입력하세요" />
)} {/* 버튼 액션 */}
{/* 아이콘 설정 영역 */} {showIconSettings && (
{/* 추천 아이콘 / 안내 문구 */} {isNoIconAction ? (
{NO_ICON_MESSAGE}
{/* 커스텀 아이콘이 있으면 표시 */} {(customIcons.length > 0 || customSvgIcons.length > 0) && ( <>
커스텀 아이콘
{customIcons.map((iconName) => { const Icon = getLucideIcon(iconName); if (!Icon) return null; return (
); })} {customSvgIcons.map((svgIcon) => (
))}
)} {/* 커스텀 아이콘 추가 버튼 */}
아이콘을 찾을 수 없습니다. {Object.keys(allLucideIcons) .filter((name) => name .toLowerCase() .includes(lucideSearchTerm.toLowerCase()) ) .slice(0, 30) .map((iconName) => { const Icon = allLucideIcons[ iconName as keyof typeof allLucideIcons ]; return ( { const next = [...customIcons]; if (!next.includes(iconName)) { next.push(iconName); onUpdateProperty( "componentConfig.customIcons", next ); if (Icon) addToIconMap(iconName, Icon); } setLucideSearchOpen(false); setLucideSearchTerm(""); }} className="flex items-center gap-2 text-xs" > {Icon ? ( ) : ( )} {iconName} {customIcons.includes(iconName) && ( )} ); })} setSvgName(e.target.value)} placeholder="예: 회사로고" className="h-7 text-xs" />