diff --git a/frontend/components/screen/StyleEditor.tsx b/frontend/components/screen/StyleEditor.tsx index 2fe737c3..f265115b 100644 --- a/frontend/components/screen/StyleEditor.tsx +++ b/frontend/components/screen/StyleEditor.tsx @@ -4,13 +4,24 @@ import { useState, useEffect } from "react"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { Separator } from "@/components/ui/separator"; -import { Palette, Type, Square } from "lucide-react"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { Palette, Type, Square, ChevronDown } from "lucide-react"; import { ComponentStyle } from "@/types/screen"; import { ColorPickerWithTransparent } from "./common/ColorPickerWithTransparent"; +interface StyleEditorProps { + style: ComponentStyle; + onStyleChange: (style: ComponentStyle) => void; + className?: string; +} + export default function StyleEditor({ style, onStyleChange, className }: StyleEditorProps) { const [localStyle, setLocalStyle] = useState(style || {}); + const [openSections, setOpenSections] = useState>({ + border: false, + background: false, + text: false, + }); useEffect(() => { setLocalStyle(style || {}); @@ -22,232 +33,255 @@ export default function StyleEditor({ style, onStyleChange, className }: StyleEd onStyleChange(newStyle); }; - return ( -
- {/* 테두리 섹션 */} -
-
- -

테두리

-
- -
-
-
- - handleStyleChange("borderWidth", e.target.value)} - className="h-6 w-full px-2 py-0 text-xs" - /> -
-
- - -
-
+ const toggleSection = (section: string) => { + setOpenSections((prev) => ({ + ...prev, + [section]: !prev[section], + })); + }; -
-
- - handleStyleChange("borderColor", value)} - defaultColor="#e5e7eb" - placeholder="#e5e7eb" - /> + return ( +
+ {/* 테두리 섹션 */} + toggleSection("border")}> + +
+ + 테두리 +
+ +
+ +
+
+
+ + handleStyleChange("borderWidth", e.target.value)} + className="h-6 w-full px-2 py-0 text-xs" + /> +
+
+ + +
-
- - handleStyleChange("borderRadius", e.target.value)} - className="h-6 w-full px-2 py-0 text-xs" - /> + +
+
+ + handleStyleChange("borderColor", value)} + defaultColor="#e5e7eb" + placeholder="#e5e7eb" + /> +
+
+ + handleStyleChange("borderRadius", e.target.value)} + className="h-6 w-full px-2 py-0 text-xs" + /> +
-
-
+ + {/* 배경 섹션 */} -
-
- -

배경

-
- -
-
- - handleStyleChange("backgroundColor", value)} - defaultColor="#ffffff" - placeholder="#ffffff" - /> + toggleSection("background")}> + +
+ + 배경
- -
- - handleStyleChange("backgroundImage", e.target.value)} - className="h-6 w-full px-2 py-0 text-xs" - /> -

- 위젯 배경 꾸미기용 (고급 사용자 전용) -

-
-
-
- - {/* 텍스트 섹션 */} -
-
- -

텍스트

-
- -
-
+ + + +
-
+
-
+
+ -
-
- - + {/* 텍스트 섹션 */} + toggleSection("text")}> + +
+ + 텍스트 +
+ +
+ +
+
+
+ + handleStyleChange("color", value)} + defaultColor="#000000" + placeholder="#000000" + /> +
+
+ + handleStyleChange("fontSize", e.target.value)} + className="h-6 w-full px-2 py-0 text-xs" + /> +
-
- - + +
+
+ + +
+
+ + +
-
-
+ +
); } diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index eb3f5853..3bffcadc 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -228,18 +228,6 @@ export const UnifiedPropertiesPanel: React.FC = ({ onUpdateProperty(selectedComponent.id, "componentConfig", { ...currentConfig, ...newConfig }); }; - const unifiedNames: Record = { - "unified-input": "통합 입력", - "unified-select": "통합 선택", - "unified-date": "통합 날짜", - "unified-list": "통합 목록", - "unified-layout": "통합 레이아웃", - "unified-group": "통합 그룹", - "unified-media": "통합 미디어", - "unified-biz": "통합 비즈니스", - "unified-hierarchy": "통합 계층", - }; - // 컬럼의 inputType 가져오기 (entity 타입인지 확인용) const inputType = currentConfig.inputType || currentConfig.webType || (selectedComponent as any).inputType; @@ -257,10 +245,6 @@ export const UnifiedPropertiesPanel: React.FC = ({ return (
-
- -

{unifiedNames[componentId] || componentId} 설정

-
); @@ -301,10 +285,6 @@ export const UnifiedPropertiesPanel: React.FC = ({ return (
-
- -

{definition.name} 설정

-
@@ -669,16 +649,89 @@ export const UnifiedPropertiesPanel: React.FC = ({ const group = selectedComponent as GroupComponent; const area = selectedComponent as AreaComponent; + // 라벨 설정이 표시될 입력 필드 타입들 + const inputFieldTypes = [ + "text", + "number", + "decimal", + "date", + "datetime", + "time", + "email", + "tel", + "url", + "password", + "textarea", + "select", + "dropdown", + "entity", + "code", + "checkbox", + "radio", + "boolean", + "file", + "autocomplete", + "text-input", + "number-input", + "date-input", + "textarea-basic", + "select-basic", + "checkbox-basic", + "radio-basic", + "entity-search-input", + "autocomplete-search-input", + // 새로운 통합 입력 컴포넌트 + "unified-input", + "unified-select", + "unified-entity-select", + "unified-checkbox", + "unified-radio", + "unified-textarea", + "unified-date", + "unified-datetime", + "unified-time", + "unified-file", + ]; + + // 현재 컴포넌트가 입력 필드인지 확인 + const componentType = widget.widgetType || (widget as any).componentId || (widget as any).componentType; + const isInputField = inputFieldTypes.includes(componentType); + return (
- {/* 라벨 + 최소 높이 (같은 행) */} + {/* 너비 + 높이 (같은 행) */}
- + handleUpdate("label", e.target.value)} - placeholder="라벨" + type="number" + min={10} + max={3840} + step="1" + value={localWidth} + onChange={(e) => { + setLocalWidth(e.target.value); + }} + onBlur={(e) => { + const value = parseInt(e.target.value) || 0; + if (value >= 10) { + const snappedValue = Math.round(value / 10) * 10; + handleUpdate("size.width", snappedValue); + setLocalWidth(String(snappedValue)); + } + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + const value = parseInt(e.currentTarget.value) || 0; + if (value >= 10) { + const snappedValue = Math.round(value / 10) * 10; + handleUpdate("size.width", snappedValue); + setLocalWidth(String(snappedValue)); + } + e.currentTarget.blur(); + } + }} + placeholder="100" className="h-6 w-full px-2 py-0 text-xs" />
@@ -688,11 +741,9 @@ export const UnifiedPropertiesPanel: React.FC = ({ type="number" value={localHeight} onChange={(e) => { - // 입력 중에는 로컬 상태만 업데이트 (자유 입력) setLocalHeight(e.target.value); }} onBlur={(e) => { - // 포커스를 잃을 때 10px 단위로 스냅 const value = parseInt(e.target.value) || 0; if (value >= 10) { const snappedValue = Math.round(value / 10) * 10; @@ -701,7 +752,6 @@ export const UnifiedPropertiesPanel: React.FC = ({ } }} onKeyDown={(e) => { - // Enter 키를 누르면 즉시 적용 (10px 단위로 스냅) if (e.key === "Enter") { const value = parseInt(e.currentTarget.value) || 0; if (value >= 10) { @@ -709,7 +759,7 @@ export const UnifiedPropertiesPanel: React.FC = ({ handleUpdate("size.height", snappedValue); setLocalHeight(String(snappedValue)); } - e.currentTarget.blur(); // 포커스 제거 + e.currentTarget.blur(); } }} step={1} @@ -719,19 +769,6 @@ export const UnifiedPropertiesPanel: React.FC = ({
- {/* Placeholder (widget만) */} - {selectedComponent.type === "widget" && ( -
- - handleUpdate("placeholder", e.target.value)} - placeholder="입력 안내 텍스트" - className="h-6 w-full px-2 py-0 text-xs" - /> -
- )} - {/* Title (group/area) */} {(selectedComponent.type === "group" || selectedComponent.type === "area") && (
@@ -758,116 +795,74 @@ export const UnifiedPropertiesPanel: React.FC = ({
)} - {/* Width + Z-Index (같은 행) */} -
-
- -
- { - // 입력 중에는 로컬 상태만 업데이트 (자유 입력) - setLocalWidth(e.target.value); - }} - onBlur={(e) => { - // 포커스를 잃을 때 10px 단위로 스냅 - const value = parseInt(e.target.value, 10); - if (!isNaN(value) && value >= 10) { - const snappedValue = Math.round(value / 10) * 10; - handleUpdate("size.width", snappedValue); - setLocalWidth(String(snappedValue)); - } - }} - onKeyDown={(e) => { - // Enter 키를 누르면 즉시 적용 (10px 단위로 스냅) - if (e.key === "Enter") { - const value = parseInt(e.currentTarget.value, 10); - if (!isNaN(value) && value >= 10) { - const snappedValue = Math.round(value / 10) * 10; - handleUpdate("size.width", snappedValue); - setLocalWidth(String(snappedValue)); - } - e.currentTarget.blur(); // 포커스 제거 - } - }} - className="h-6 w-full px-2 py-0 text-xs" - /> -
-
-
- - handleUpdate("position.z", parseInt(e.target.value) || 1)} - className="h-6 w-full px-2 py-0 text-xs" - className="text-xs" - /> -
+ {/* Z-Index */} +
+ + handleUpdate("position.z", parseInt(e.target.value) || 1)} + className="h-6 w-full px-2 py-0 text-xs" + />
- {/* 라벨 스타일 */} - - - 라벨 스타일 - - - -
- - handleUpdate("style.labelText", e.target.value)} - className="h-6 w-full px-2 py-0 text-xs" - className="text-xs" - /> -
-
+ {/* 라벨 스타일 - 입력 필드에서만 표시 */} + {isInputField && ( + + + 라벨 스타일 + + +
- + handleUpdate("style.labelFontSize", e.target.value)} + value={selectedComponent.style?.labelText || selectedComponent.label || ""} + onChange={(e) => handleUpdate("style.labelText", e.target.value)} className="h-6 w-full px-2 py-0 text-xs" - className="text-xs" />
-
- - handleUpdate("style.labelColor", value)} - defaultColor="#212121" - placeholder="#212121" - /> +
+
+ + handleUpdate("style.labelFontSize", e.target.value)} + className="h-6 w-full px-2 py-0 text-xs" + /> +
+
+ + handleUpdate("style.labelColor", value)} + defaultColor="#212121" + placeholder="#212121" + /> +
-
-
-
- - handleUpdate("style.labelMarginBottom", e.target.value)} - className="h-6 w-full px-2 py-0 text-xs" - className="text-xs" - /> +
+
+ + handleUpdate("style.labelMarginBottom", e.target.value)} + className="h-6 w-full px-2 py-0 text-xs" + /> +
+
+ handleUpdate("style.labelDisplay", checked)} + className="h-4 w-4" + /> + +
-
- handleUpdate("style.labelDisplay", checked)} - className="h-4 w-4" - /> - -
-
- - + + + )} {/* 옵션 */}
diff --git a/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx b/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx index 7346ce36..f2fd24fd 100644 --- a/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx +++ b/frontend/components/unified/config-panels/UnifiedListConfigPanel.tsx @@ -170,12 +170,6 @@ export const UnifiedListConfigPanel: React.FC = ({ updateConfig("columns", newColumns); }; - // 컬럼 너비 수정 - const updateColumnWidth = (columnKey: string, width: string) => { - const newColumns = configColumns.map((col) => (col.key === columnKey ? { ...col, width } : col)); - updateConfig("columns", newColumns); - }; - // 그룹별 컬럼 분리 const baseColumns = useMemo(() => columns.filter((col) => !col.isJoinColumn), [columns]); @@ -209,21 +203,6 @@ export const UnifiedListConfigPanel: React.FC = ({ return (
- {/* 데이터 소스 정보 (읽기 전용) */} -
- - {tableName ? ( -
- - {tableName} -
- ) : ( -

화면에 테이블이 설정되지 않았습니다

- )} -
- - - {/* 뷰 모드 */}
@@ -422,12 +401,6 @@ export const UnifiedListConfigPanel: React.FC = ({ placeholder="제목" className="h-6 flex-1 text-xs" /> - updateColumnWidth(column.key, e.target.value)} - placeholder="너비" - className="h-6 w-14 text-xs" - />