- 컴포넌트 드래그앤드롭 시스템 완성 - 속성 편집 및 실시간 미리보기 기능 - 버튼 시스템 통합 (유니버설 버튼) - 격자 시스템 및 해상도 설정 패널 - 상세설정 패널 구현 - 스타일 편집기 최적화 - 라벨 처리 시스템 개선
268 lines
9.3 KiB
TypeScript
268 lines
9.3 KiB
TypeScript
"use client";
|
||
|
||
import React from "react";
|
||
import { Label } from "@/components/ui/label";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Checkbox } from "@/components/ui/checkbox";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Separator } from "@/components/ui/separator";
|
||
import { Slider } from "@/components/ui/slider";
|
||
import { Grid3X3, RotateCcw, Eye, EyeOff, Zap } from "lucide-react";
|
||
import { GridSettings, ScreenResolution } from "@/types/screen";
|
||
import { calculateGridInfo } from "@/lib/utils/gridUtils";
|
||
|
||
interface GridPanelProps {
|
||
gridSettings: GridSettings;
|
||
onGridSettingsChange: (settings: GridSettings) => void;
|
||
onResetGrid: () => void;
|
||
screenResolution?: ScreenResolution; // 해상도 정보 추가
|
||
}
|
||
|
||
export const GridPanel: React.FC<GridPanelProps> = ({
|
||
gridSettings,
|
||
onGridSettingsChange,
|
||
onResetGrid,
|
||
screenResolution,
|
||
}) => {
|
||
const updateSetting = (key: keyof GridSettings, value: any) => {
|
||
onGridSettingsChange({
|
||
...gridSettings,
|
||
[key]: value,
|
||
});
|
||
};
|
||
|
||
// 실제 격자 정보 계산
|
||
const actualGridInfo = screenResolution
|
||
? calculateGridInfo(screenResolution.width, screenResolution.height, {
|
||
columns: gridSettings.columns,
|
||
gap: gridSettings.gap,
|
||
padding: gridSettings.padding,
|
||
snapToGrid: gridSettings.snapToGrid || false,
|
||
})
|
||
: null;
|
||
|
||
// 실제 표시되는 컬럼 수 계산 (항상 설정된 개수를 표시하되, 너비가 너무 작으면 경고)
|
||
const actualColumns = gridSettings.columns;
|
||
|
||
// 컬럼이 너무 작은지 확인
|
||
const isColumnsTooSmall =
|
||
screenResolution && actualGridInfo
|
||
? actualGridInfo.columnWidth < 30 // 30px 미만이면 너무 작다고 판단
|
||
: false;
|
||
|
||
return (
|
||
<div className="flex h-full flex-col">
|
||
{/* 헤더 */}
|
||
<div className="border-b border-gray-200 p-4">
|
||
<div className="mb-3 flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<Grid3X3 className="h-4 w-4 text-gray-600" />
|
||
<h3 className="font-medium text-gray-900">격자 설정</h3>
|
||
</div>
|
||
|
||
<Button size="sm" variant="outline" onClick={onResetGrid} className="flex items-center space-x-1">
|
||
<RotateCcw className="h-3 w-3" />
|
||
<span>초기화</span>
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 주요 토글들 */}
|
||
<div className="space-y-3">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
{gridSettings.showGrid ? (
|
||
<Eye className="h-4 w-4 text-blue-600" />
|
||
) : (
|
||
<EyeOff className="h-4 w-4 text-gray-400" />
|
||
)}
|
||
<Label htmlFor="showGrid" className="text-sm font-medium">
|
||
격자 표시
|
||
</Label>
|
||
</div>
|
||
<Checkbox
|
||
id="showGrid"
|
||
checked={gridSettings.showGrid}
|
||
onCheckedChange={(checked) => updateSetting("showGrid", checked)}
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-2">
|
||
<Zap className="h-4 w-4 text-green-600" />
|
||
<Label htmlFor="snapToGrid" className="text-sm font-medium">
|
||
격자 스냅
|
||
</Label>
|
||
</div>
|
||
<Checkbox
|
||
id="snapToGrid"
|
||
checked={gridSettings.snapToGrid}
|
||
onCheckedChange={(checked) => updateSetting("snapToGrid", checked)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 해상도 정보 */}
|
||
{screenResolution && (
|
||
<div className="mt-3 rounded bg-blue-50 p-3">
|
||
<h4 className="text-xs font-medium text-blue-900">현재 해상도</h4>
|
||
<p className="mt-1 text-xs text-blue-700">
|
||
{screenResolution.width} × {screenResolution.height}
|
||
</p>
|
||
{actualGridInfo && (
|
||
<p className="mt-1 text-xs text-blue-600">컬럼 너비: {Math.round(actualGridInfo.columnWidth)}px</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 설정 영역 */}
|
||
<div className="flex-1 space-y-6 overflow-y-auto p-4">
|
||
{/* 격자 구조 */}
|
||
<div className="space-y-4">
|
||
<h4 className="font-medium text-gray-900">격자 구조</h4>
|
||
|
||
<div>
|
||
<Label htmlFor="columns" className="mb-2 block text-sm font-medium">
|
||
컬럼 수: {gridSettings.columns}
|
||
{isColumnsTooSmall && (
|
||
<span className="ml-2 text-xs text-orange-600">
|
||
(컬럼이 너무 작음: {Math.round(actualGridInfo!.columnWidth)}px)
|
||
</span>
|
||
)}
|
||
</Label>
|
||
<Slider
|
||
id="columns"
|
||
min={1}
|
||
max={24}
|
||
step={1}
|
||
value={[gridSettings.columns]}
|
||
onValueChange={([value]) => updateSetting("columns", value)}
|
||
className="w-full"
|
||
/>
|
||
<div className="mt-1 flex justify-between text-xs text-gray-500">
|
||
<span>1</span>
|
||
<span>24</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="gap" className="mb-2 block text-sm font-medium">
|
||
간격: {gridSettings.gap}px
|
||
</Label>
|
||
<Slider
|
||
id="gap"
|
||
min={0}
|
||
max={40}
|
||
step={2}
|
||
value={[gridSettings.gap]}
|
||
onValueChange={([value]) => updateSetting("gap", value)}
|
||
className="w-full"
|
||
/>
|
||
<div className="mt-1 flex justify-between text-xs text-gray-500">
|
||
<span>0px</span>
|
||
<span>40px</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="padding" className="mb-2 block text-sm font-medium">
|
||
여백: {gridSettings.padding}px
|
||
</Label>
|
||
<Slider
|
||
id="padding"
|
||
min={0}
|
||
max={60}
|
||
step={4}
|
||
value={[gridSettings.padding]}
|
||
onValueChange={([value]) => updateSetting("padding", value)}
|
||
className="w-full"
|
||
/>
|
||
<div className="mt-1 flex justify-between text-xs text-gray-500">
|
||
<span>0px</span>
|
||
<span>60px</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<Separator />
|
||
|
||
{/* 격자 스타일 */}
|
||
<div className="space-y-4">
|
||
<h4 className="font-medium text-gray-900">격자 스타일</h4>
|
||
|
||
<div>
|
||
<Label htmlFor="gridColor" className="text-sm font-medium">
|
||
격자 색상
|
||
</Label>
|
||
<div className="mt-1 flex items-center space-x-2">
|
||
<Input
|
||
id="gridColor"
|
||
type="color"
|
||
value={gridSettings.gridColor || "#d1d5db"}
|
||
onChange={(e) => updateSetting("gridColor", e.target.value)}
|
||
className="h-8 w-12 rounded border p-1"
|
||
/>
|
||
<Input
|
||
type="text"
|
||
value={gridSettings.gridColor || "#d1d5db"}
|
||
onChange={(e) => updateSetting("gridColor", e.target.value)}
|
||
placeholder="#d1d5db"
|
||
className="flex-1"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="gridOpacity" className="mb-2 block text-sm font-medium">
|
||
격자 투명도: {Math.round((gridSettings.gridOpacity || 0.5) * 100)}%
|
||
</Label>
|
||
<Slider
|
||
id="gridOpacity"
|
||
min={0.1}
|
||
max={1}
|
||
step={0.1}
|
||
value={[gridSettings.gridOpacity || 0.5]}
|
||
onValueChange={([value]) => updateSetting("gridOpacity", value)}
|
||
className="w-full"
|
||
/>
|
||
<div className="mt-1 flex justify-between text-xs text-gray-500">
|
||
<span>10%</span>
|
||
<span>100%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<Separator />
|
||
|
||
{/* 미리보기 */}
|
||
<div className="space-y-3">
|
||
<h4 className="font-medium text-gray-900">미리보기</h4>
|
||
|
||
<div
|
||
className="rounded-md border border-gray-200 bg-white p-4"
|
||
style={{
|
||
backgroundImage: gridSettings.showGrid
|
||
? `linear-gradient(to right, ${gridSettings.gridColor || "#d1d5db"} 1px, transparent 1px),
|
||
linear-gradient(to bottom, ${gridSettings.gridColor || "#d1d5db"} 1px, transparent 1px)`
|
||
: "none",
|
||
backgroundSize: gridSettings.showGrid ? `${100 / gridSettings.columns}% 20px` : "none",
|
||
opacity: gridSettings.gridOpacity || 0.5,
|
||
}}
|
||
>
|
||
<div className="flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300 bg-blue-100">
|
||
<span className="text-xs text-blue-600">컴포넌트 예시</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 푸터 */}
|
||
<div className="border-t border-gray-200 bg-gray-50 p-3">
|
||
<div className="text-xs text-gray-600">💡 격자 설정은 실시간으로 캔버스에 반영됩니다</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default GridPanel;
|