라벨 표시기능
This commit is contained in:
parent
9af3cdea01
commit
162ab12806
|
|
@ -244,15 +244,33 @@ export const RealtimePreview: React.FC<RealtimePreviewProps> = ({
|
|||
}
|
||||
: style;
|
||||
|
||||
// 라벨 스타일 계산
|
||||
const shouldShowLabel = component.style?.labelDisplay !== false && (component.label || component.style?.labelText);
|
||||
const labelText = component.style?.labelText || component.label || "";
|
||||
|
||||
// 라벨 하단 여백 값 추출 (px 단위 숫자로 변환)
|
||||
const labelMarginBottomValue = parseInt(component.style?.labelMarginBottom || "4px", 10);
|
||||
|
||||
const labelStyle: React.CSSProperties = {
|
||||
fontSize: component.style?.labelFontSize || "12px",
|
||||
color: component.style?.labelColor || "#374151",
|
||||
fontWeight: component.style?.labelFontWeight || "500",
|
||||
fontFamily: component.style?.labelFontFamily || "inherit",
|
||||
textAlign: component.style?.labelTextAlign || "left",
|
||||
backgroundColor: component.style?.labelBackgroundColor || "transparent",
|
||||
padding: component.style?.labelPadding || "0",
|
||||
borderRadius: component.style?.labelBorderRadius || "0",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute cursor-move transition-all ${defaultRingClass}`}
|
||||
style={{
|
||||
left: `${component.position.x}px`,
|
||||
top: `${component.position.y}px`,
|
||||
width: `${size.width}px`, // 격자 기반 계산 제거
|
||||
height: `${size.height}px`,
|
||||
zIndex: component.position.z || 1, // z-index 적용
|
||||
width: `${size.width}px`,
|
||||
height: shouldShowLabel ? `${size.height + 20 + labelMarginBottomValue}px` : `${size.height}px`, // 라벨 공간 + 여백 추가
|
||||
zIndex: component.position.z || 1,
|
||||
...selectionStyle,
|
||||
}}
|
||||
onClick={(e) => {
|
||||
|
|
@ -267,31 +285,53 @@ export const RealtimePreview: React.FC<RealtimePreviewProps> = ({
|
|||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{type === "container" && (
|
||||
<div className="pointer-events-none flex h-full flex-col items-center justify-center p-2">
|
||||
<div className="flex flex-col items-center space-y-1">
|
||||
<Database className="h-6 w-6 text-blue-600" />
|
||||
<div className="text-center">
|
||||
<div className="text-xs font-medium">{label}</div>
|
||||
<div className="text-xs text-gray-500">{tableName}</div>
|
||||
{/* 라벨 표시 */}
|
||||
{shouldShowLabel && (
|
||||
<div
|
||||
className="pointer-events-none absolute left-0 w-full truncate"
|
||||
style={{
|
||||
...labelStyle,
|
||||
top: `${-20 - labelMarginBottomValue}px`, // 라벨 높이(약 20px) + 여백값만큼 위로 이동
|
||||
}}
|
||||
>
|
||||
{labelText}
|
||||
{component.required && <span style={{ color: "#f97316", marginLeft: "2px" }}>*</span>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 컴포넌트 내용 */}
|
||||
<div
|
||||
style={{
|
||||
width: `${size.width}px`,
|
||||
height: `${size.height}px`,
|
||||
}}
|
||||
>
|
||||
{type === "container" && (
|
||||
<div className="pointer-events-none flex h-full flex-col items-center justify-center p-2">
|
||||
<div className="flex flex-col items-center space-y-1">
|
||||
<Database className="h-6 w-6 text-blue-600" />
|
||||
<div className="text-center">
|
||||
<div className="text-xs font-medium">{label}</div>
|
||||
<div className="text-xs text-gray-500">{tableName}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{type === "group" && (
|
||||
<div className="relative h-full w-full">
|
||||
{/* 그룹 박스/헤더 제거: 투명 컨테이너 */}
|
||||
<div className="absolute inset-0">{children}</div>
|
||||
</div>
|
||||
)}
|
||||
{type === "group" && (
|
||||
<div className="relative h-full w-full">
|
||||
{/* 그룹 박스/헤더 제거: 투명 컨테이너 */}
|
||||
<div className="absolute inset-0">{children}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === "widget" && (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 위젯 본체 - 실제 웹 위젯처럼 보이도록 */}
|
||||
<div className="pointer-events-none flex-1">{renderWidget(component)}</div>
|
||||
</div>
|
||||
)}
|
||||
{type === "widget" && (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 위젯 본체 - 실제 웹 위젯처럼 보이도록 */}
|
||||
<div className="pointer-events-none flex-1">{renderWidget(component)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -431,6 +431,13 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
tableName: table.tableName,
|
||||
position: { x, y, z: 1 } as Position,
|
||||
size: { width: 300, height: 200 },
|
||||
style: {
|
||||
labelDisplay: true,
|
||||
labelFontSize: "14px",
|
||||
labelColor: "#374151",
|
||||
labelFontWeight: "600",
|
||||
labelMarginBottom: "8px",
|
||||
},
|
||||
};
|
||||
} else if (type === "column") {
|
||||
// 격자 기반 컬럼 너비 계산
|
||||
|
|
@ -449,6 +456,13 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
|||
readonly: false, // 누락된 속성 추가
|
||||
position: { x, y, z: 1 } as Position,
|
||||
size: { width: columnWidth, height: 40 },
|
||||
style: {
|
||||
labelDisplay: true,
|
||||
labelFontSize: "12px",
|
||||
labelColor: "#374151",
|
||||
labelFontWeight: "500",
|
||||
labelMarginBottom: "6px",
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -42,23 +42,6 @@ const webTypeOptions: { value: WebType; label: string }[] = [
|
|||
{ value: "file", label: "파일" },
|
||||
];
|
||||
|
||||
// Debounce hook for better performance
|
||||
const useDebounce = (value: any, delay: number) => {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(value);
|
||||
}, delay);
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [value, delay]);
|
||||
|
||||
return debouncedValue;
|
||||
};
|
||||
|
||||
export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
||||
selectedComponent,
|
||||
onUpdateProperty,
|
||||
|
|
@ -73,122 +56,49 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
const selectedComponentRef = useRef(selectedComponent);
|
||||
const onUpdatePropertyRef = useRef(onUpdateProperty);
|
||||
|
||||
// 입력 필드들의 로컬 상태 (실시간 타이핑 반영용)
|
||||
const [localInputs, setLocalInputs] = useState({
|
||||
placeholder: selectedComponent?.placeholder || "",
|
||||
title: selectedComponent?.title || "",
|
||||
positionX: selectedComponent?.position.x?.toString() || "0",
|
||||
positionY: selectedComponent?.position.y?.toString() || "0",
|
||||
positionZ: selectedComponent?.position.z?.toString() || "1",
|
||||
width: selectedComponent?.size.width?.toString() || "0",
|
||||
height: selectedComponent?.size.height?.toString() || "0",
|
||||
labelText: selectedComponent?.style?.labelText || selectedComponent?.label || "",
|
||||
labelFontSize: selectedComponent?.style?.labelFontSize || "12px",
|
||||
labelColor: selectedComponent?.style?.labelColor || "#374151",
|
||||
labelMarginBottom: selectedComponent?.style?.labelMarginBottom || "4px",
|
||||
required: selectedComponent?.required || false,
|
||||
readonly: selectedComponent?.readonly || false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
selectedComponentRef.current = selectedComponent;
|
||||
onUpdatePropertyRef.current = onUpdateProperty;
|
||||
});
|
||||
|
||||
// 로컬 상태 관리 (실시간 입력 반영용)
|
||||
const [localValues, setLocalValues] = useState({
|
||||
label: selectedComponent?.label || "",
|
||||
placeholder: selectedComponent?.placeholder || "",
|
||||
title: selectedComponent?.title || "",
|
||||
positionX: selectedComponent?.position.x || 0,
|
||||
positionY: selectedComponent?.position.y || 0,
|
||||
positionZ: selectedComponent?.position.z || 1,
|
||||
width: selectedComponent?.size.width || 0,
|
||||
height: selectedComponent?.size.height || 0,
|
||||
});
|
||||
|
||||
// 선택된 컴포넌트가 변경될 때 로컬 상태 업데이트
|
||||
// 선택된 컴포넌트가 변경될 때 로컬 입력 상태 업데이트
|
||||
useEffect(() => {
|
||||
if (selectedComponent) {
|
||||
setLocalValues({
|
||||
label: selectedComponent.label || "",
|
||||
setLocalInputs({
|
||||
placeholder: selectedComponent.placeholder || "",
|
||||
title: selectedComponent.title || "",
|
||||
positionX: selectedComponent.position.x || 0,
|
||||
positionY: selectedComponent.position.y || 0,
|
||||
positionZ: selectedComponent.position.z || 1,
|
||||
width: selectedComponent.size.width || 0,
|
||||
height: selectedComponent.size.height || 0,
|
||||
positionX: selectedComponent.position.x?.toString() || "0",
|
||||
positionY: selectedComponent.position.y?.toString() || "0",
|
||||
positionZ: selectedComponent.position.z?.toString() || "1",
|
||||
width: selectedComponent.size.width?.toString() || "0",
|
||||
height: selectedComponent.size.height?.toString() || "0",
|
||||
labelText: selectedComponent.style?.labelText || selectedComponent.label || "",
|
||||
labelFontSize: selectedComponent.style?.labelFontSize || "12px",
|
||||
labelColor: selectedComponent.style?.labelColor || "#374151",
|
||||
labelMarginBottom: selectedComponent.style?.labelMarginBottom || "4px",
|
||||
required: selectedComponent.required || false,
|
||||
readonly: selectedComponent.readonly || false,
|
||||
});
|
||||
}
|
||||
}, [selectedComponent]);
|
||||
|
||||
// Debounce된 값들
|
||||
const debouncedLabel = useDebounce(localValues.label, 300);
|
||||
const debouncedPlaceholder = useDebounce(localValues.placeholder, 300);
|
||||
const debouncedTitle = useDebounce(localValues.title, 300);
|
||||
const debouncedPositionX = useDebounce(localValues.positionX, 150);
|
||||
const debouncedPositionY = useDebounce(localValues.positionY, 150);
|
||||
const debouncedPositionZ = useDebounce(localValues.positionZ, 150);
|
||||
const debouncedWidth = useDebounce(localValues.width, 150);
|
||||
const debouncedHeight = useDebounce(localValues.height, 150);
|
||||
|
||||
// Debounce된 값이 변경될 때 실제 업데이트
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedLabel !== currentComponent.label && debouncedLabel) {
|
||||
updateProperty("label", debouncedLabel);
|
||||
}
|
||||
}, [debouncedLabel]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedPlaceholder !== currentComponent.placeholder) {
|
||||
updateProperty("placeholder", debouncedPlaceholder);
|
||||
}
|
||||
}, [debouncedPlaceholder]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedTitle !== currentComponent.title) {
|
||||
updateProperty("title", debouncedTitle);
|
||||
}
|
||||
}, [debouncedTitle]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedPositionX !== currentComponent.position.x) {
|
||||
updateProperty("position.x", debouncedPositionX);
|
||||
}
|
||||
}, [debouncedPositionX]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedPositionY !== currentComponent.position.y) {
|
||||
updateProperty("position.y", debouncedPositionY);
|
||||
}
|
||||
}, [debouncedPositionY]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedPositionZ !== currentComponent.position.z) {
|
||||
updateProperty("position.z", debouncedPositionZ);
|
||||
}
|
||||
}, [debouncedPositionZ]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedWidth !== currentComponent.size.width) {
|
||||
updateProperty("size.width", debouncedWidth);
|
||||
}
|
||||
}, [debouncedWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentComponent = selectedComponentRef.current;
|
||||
const updateProperty = onUpdatePropertyRef.current;
|
||||
|
||||
if (currentComponent && debouncedHeight !== currentComponent.size.height) {
|
||||
updateProperty("size.height", debouncedHeight);
|
||||
}
|
||||
}, [debouncedHeight]);
|
||||
|
||||
if (!selectedComponent) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center p-6 text-center">
|
||||
|
|
@ -251,19 +161,6 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Label htmlFor="label" className="text-sm font-medium">
|
||||
라벨
|
||||
</Label>
|
||||
<Input
|
||||
id="label"
|
||||
value={localValues.label}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, label: e.target.value }))}
|
||||
placeholder="컴포넌트 라벨"
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedComponent.type === "widget" && (
|
||||
<>
|
||||
<div>
|
||||
|
|
@ -307,8 +204,12 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
</Label>
|
||||
<Input
|
||||
id="placeholder"
|
||||
value={localValues.placeholder}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, placeholder: e.target.value }))}
|
||||
value={localInputs.placeholder}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, placeholder: newValue }));
|
||||
onUpdateProperty("placeholder", newValue);
|
||||
}}
|
||||
placeholder="입력 힌트 텍스트"
|
||||
className="mt-1"
|
||||
/>
|
||||
|
|
@ -318,8 +219,11 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="required"
|
||||
checked={selectedComponent.required || false}
|
||||
onCheckedChange={(checked) => onUpdateProperty("required", checked)}
|
||||
checked={localInputs.required}
|
||||
onCheckedChange={(checked) => {
|
||||
setLocalInputs((prev) => ({ ...prev, required: !!checked }));
|
||||
onUpdateProperty("required", checked);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="required" className="text-sm">
|
||||
필수 입력
|
||||
|
|
@ -329,8 +233,11 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="readonly"
|
||||
checked={selectedComponent.readonly || false}
|
||||
onCheckedChange={(checked) => onUpdateProperty("readonly", checked)}
|
||||
checked={localInputs.readonly}
|
||||
onCheckedChange={(checked) => {
|
||||
setLocalInputs((prev) => ({ ...prev, readonly: !!checked }));
|
||||
onUpdateProperty("readonly", checked);
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="readonly" className="text-sm">
|
||||
읽기 전용
|
||||
|
|
@ -359,8 +266,12 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
<Input
|
||||
id="positionX"
|
||||
type="number"
|
||||
value={localValues.positionX}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, positionX: Number(e.target.value) }))}
|
||||
value={localInputs.positionX}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, positionX: newValue }));
|
||||
onUpdateProperty("position", { ...selectedComponent.position, x: Number(newValue) });
|
||||
}}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -372,8 +283,12 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
<Input
|
||||
id="positionY"
|
||||
type="number"
|
||||
value={localValues.positionY}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, positionY: Number(e.target.value) }))}
|
||||
value={localInputs.positionY}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, positionY: newValue }));
|
||||
onUpdateProperty("position", { ...selectedComponent.position, y: Number(newValue) });
|
||||
}}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -385,8 +300,12 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
<Input
|
||||
id="width"
|
||||
type="number"
|
||||
value={localValues.width}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, width: Number(e.target.value) }))}
|
||||
value={localInputs.width}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, width: newValue }));
|
||||
onUpdateProperty("size", { ...selectedComponent.size, width: Number(newValue) });
|
||||
}}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -398,8 +317,12 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
<Input
|
||||
id="height"
|
||||
type="number"
|
||||
value={localValues.height}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, height: Number(e.target.value) }))}
|
||||
value={localInputs.height}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, height: newValue }));
|
||||
onUpdateProperty("size", { ...selectedComponent.size, height: Number(newValue) });
|
||||
}}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -413,8 +336,12 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
type="number"
|
||||
min="0"
|
||||
max="9999"
|
||||
value={localValues.positionZ}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, positionZ: Number(e.target.value) }))}
|
||||
value={localInputs.positionZ}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, positionZ: newValue }));
|
||||
onUpdateProperty("position", { ...selectedComponent.position, z: Number(newValue) });
|
||||
}}
|
||||
className="mt-1"
|
||||
placeholder="1"
|
||||
/>
|
||||
|
|
@ -422,6 +349,149 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* 라벨 스타일 */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Type className="h-4 w-4 text-gray-600" />
|
||||
<h4 className="font-medium text-gray-900">라벨 설정</h4>
|
||||
</div>
|
||||
|
||||
{/* 라벨 표시 토글 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="labelDisplay" className="text-sm font-medium">
|
||||
라벨 표시
|
||||
</Label>
|
||||
<Checkbox
|
||||
id="labelDisplay"
|
||||
checked={selectedComponent.style?.labelDisplay !== false}
|
||||
onCheckedChange={(checked) => onUpdateProperty("style.labelDisplay", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 라벨 텍스트 */}
|
||||
<div>
|
||||
<Label htmlFor="labelText" className="text-sm font-medium">
|
||||
라벨 텍스트
|
||||
</Label>
|
||||
<Input
|
||||
id="labelText"
|
||||
value={localInputs.labelText}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, labelText: newValue }));
|
||||
// 기본 라벨과 스타일 라벨을 모두 업데이트
|
||||
onUpdateProperty("label", newValue);
|
||||
onUpdateProperty("style.labelText", newValue);
|
||||
}}
|
||||
placeholder="라벨 텍스트"
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 라벨 스타일 */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<Label htmlFor="labelFontSize" className="text-sm font-medium">
|
||||
폰트 크기
|
||||
</Label>
|
||||
<Input
|
||||
id="labelFontSize"
|
||||
value={localInputs.labelFontSize}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, labelFontSize: newValue }));
|
||||
onUpdateProperty("style.labelFontSize", newValue);
|
||||
}}
|
||||
placeholder="12px"
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="labelColor" className="text-sm font-medium">
|
||||
색상
|
||||
</Label>
|
||||
<Input
|
||||
id="labelColor"
|
||||
type="color"
|
||||
value={localInputs.labelColor}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, labelColor: newValue }));
|
||||
onUpdateProperty("style.labelColor", newValue);
|
||||
}}
|
||||
className="mt-1 h-8"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="labelFontWeight" className="text-sm font-medium">
|
||||
폰트 굵기
|
||||
</Label>
|
||||
<Select
|
||||
value={selectedComponent.style?.labelFontWeight || "500"}
|
||||
onValueChange={(value) => onUpdateProperty("style.labelFontWeight", value)}
|
||||
>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="normal">Normal</SelectItem>
|
||||
<SelectItem value="bold">Bold</SelectItem>
|
||||
<SelectItem value="100">100</SelectItem>
|
||||
<SelectItem value="200">200</SelectItem>
|
||||
<SelectItem value="300">300</SelectItem>
|
||||
<SelectItem value="400">400</SelectItem>
|
||||
<SelectItem value="500">500</SelectItem>
|
||||
<SelectItem value="600">600</SelectItem>
|
||||
<SelectItem value="700">700</SelectItem>
|
||||
<SelectItem value="800">800</SelectItem>
|
||||
<SelectItem value="900">900</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="labelTextAlign" className="text-sm font-medium">
|
||||
텍스트 정렬
|
||||
</Label>
|
||||
<Select
|
||||
value={selectedComponent.style?.labelTextAlign || "left"}
|
||||
onValueChange={(value) => onUpdateProperty("style.labelTextAlign", value)}
|
||||
>
|
||||
<SelectTrigger className="mt-1">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="left">왼쪽</SelectItem>
|
||||
<SelectItem value="center">가운데</SelectItem>
|
||||
<SelectItem value="right">오른쪽</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 라벨 여백 */}
|
||||
<div>
|
||||
<Label htmlFor="labelMarginBottom" className="text-sm font-medium">
|
||||
라벨 하단 여백
|
||||
</Label>
|
||||
<Input
|
||||
id="labelMarginBottom"
|
||||
value={localInputs.labelMarginBottom}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, labelMarginBottom: newValue }));
|
||||
onUpdateProperty("style.labelMarginBottom", newValue);
|
||||
}}
|
||||
placeholder="4px"
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedComponent.type === "group" && (
|
||||
<>
|
||||
<Separator />
|
||||
|
|
@ -439,8 +509,12 @@ export const PropertiesPanel: React.FC<PropertiesPanelProps> = ({
|
|||
</Label>
|
||||
<Input
|
||||
id="groupTitle"
|
||||
value={localValues.title}
|
||||
onChange={(e) => setLocalValues((prev) => ({ ...prev, title: e.target.value }))}
|
||||
value={localInputs.title}
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
setLocalInputs((prev) => ({ ...prev, title: newValue }));
|
||||
onUpdateProperty("title", newValue);
|
||||
}}
|
||||
placeholder="그룹 제목"
|
||||
className="mt-1"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -114,6 +114,19 @@ export interface ComponentStyle {
|
|||
cursor?: string;
|
||||
transition?: string;
|
||||
transform?: string;
|
||||
|
||||
// 라벨 스타일
|
||||
labelDisplay?: boolean; // 라벨 표시 여부
|
||||
labelText?: string; // 라벨 텍스트 (기본값은 label 속성 사용)
|
||||
labelFontSize?: string | number; // 라벨 폰트 크기
|
||||
labelColor?: string; // 라벨 색상
|
||||
labelFontWeight?: "normal" | "bold" | "100" | "200" | "300" | "400" | "500" | "600" | "700" | "800" | "900"; // 라벨 폰트 굵기
|
||||
labelFontFamily?: string; // 라벨 폰트 패밀리
|
||||
labelTextAlign?: "left" | "center" | "right"; // 라벨 텍스트 정렬
|
||||
labelMarginBottom?: string | number; // 라벨과 컴포넌트 사이의 간격
|
||||
labelBackgroundColor?: string; // 라벨 배경색
|
||||
labelPadding?: string; // 라벨 패딩
|
||||
labelBorderRadius?: string | number; // 라벨 모서리 둥글기
|
||||
}
|
||||
|
||||
// BaseComponent에 스타일 속성 추가
|
||||
|
|
|
|||
Loading…
Reference in New Issue