revert: e27845a 커밋의 변경사항 되돌림 - 화면 레이아웃 문제 수정
This commit is contained in:
@@ -1,79 +1,335 @@
|
||||
"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 { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Grid3X3 } from "lucide-react";
|
||||
import { GridSettings } from "@/types/screen-management";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Grid3X3, RotateCcw, Eye, EyeOff, Zap, RefreshCw } 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;
|
||||
onForceGridUpdate?: () => void; // 강제 격자 재조정 추가
|
||||
screenResolution?: ScreenResolution; // 해상도 정보 추가
|
||||
}
|
||||
|
||||
/**
|
||||
* 격자 설정 패널 (10px 고정 격자)
|
||||
*
|
||||
* 사용자 설정:
|
||||
* - 격자 표시 ON/OFF
|
||||
* - 격자 스냅 ON/OFF
|
||||
*
|
||||
* 자동 설정 (변경 불가):
|
||||
* - 격자 크기: 10px 고정
|
||||
* - 격자 간격: 10px 고정
|
||||
*/
|
||||
export function GridPanel({ gridSettings, onGridSettingsChange }: GridPanelProps) {
|
||||
const updateSetting = <K extends keyof GridSettings>(key: K, value: GridSettings[K]) => {
|
||||
export const GridPanel: React.FC<GridPanelProps> = ({
|
||||
gridSettings,
|
||||
onGridSettingsChange,
|
||||
onResetGrid,
|
||||
onForceGridUpdate,
|
||||
screenResolution,
|
||||
}) => {
|
||||
const updateSetting = (key: keyof GridSettings, value: any) => {
|
||||
onGridSettingsChange({
|
||||
...gridSettings,
|
||||
[key]: value,
|
||||
});
|
||||
};
|
||||
|
||||
// 최대 컬럼 수 계산 (최소 컬럼 너비 30px 기준)
|
||||
const MIN_COLUMN_WIDTH = 30;
|
||||
const maxColumns = screenResolution
|
||||
? Math.floor((screenResolution.width - gridSettings.padding * 2 + gridSettings.gap) / (MIN_COLUMN_WIDTH + gridSettings.gap))
|
||||
: 24;
|
||||
const safeMaxColumns = Math.max(1, Math.min(maxColumns, 100)); // 최대 100개로 제한
|
||||
|
||||
// 실제 격자 정보 계산
|
||||
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 < MIN_COLUMN_WIDTH
|
||||
: false;
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader className="space-y-1 pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Grid3X3 className="h-4 w-4" />
|
||||
<CardTitle className="text-base">격자 설정</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* 격자 표시 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="showGrid" className="flex items-center gap-2 text-sm font-medium">
|
||||
<Grid3X3 className="h-4 w-4 text-muted-foreground" />
|
||||
격자 표시
|
||||
</Label>
|
||||
<Checkbox
|
||||
id="showGrid"
|
||||
checked={gridSettings.showGrid ?? false}
|
||||
onCheckedChange={(checked) => updateSetting("showGrid", checked as boolean)}
|
||||
/>
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 헤더 */}
|
||||
<div className="border-b p-4">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Grid3X3 className="text-muted-foreground h-4 w-4" />
|
||||
<h3 className="text-sm font-semibold">격자 설정</h3>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
{onForceGridUpdate && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onForceGridUpdate}
|
||||
className="h-7 px-2 text-xs" style={{ fontSize: "12px" }}
|
||||
title="현재 해상도에 맞게 모든 컴포넌트를 격자에 재정렬합니다"
|
||||
>
|
||||
<RefreshCw className="mr-1 h-3 w-3" />
|
||||
재정렬
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button size="sm" variant="outline" onClick={onResetGrid} className="h-7 px-2 text-xs">
|
||||
<RotateCcw className="mr-1 h-3 w-3" />
|
||||
초기화
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 격자 스냅 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="snapToGrid" className="flex items-center gap-2 text-sm font-medium">
|
||||
<Grid3X3 className="h-4 w-4 text-muted-foreground" />
|
||||
격자 스냅
|
||||
</Label>
|
||||
<Checkbox
|
||||
id="snapToGrid"
|
||||
checked={gridSettings.snapToGrid}
|
||||
onCheckedChange={(checked) => updateSetting("snapToGrid", checked as boolean)}
|
||||
/>
|
||||
{/* 주요 토글들 */}
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{gridSettings.showGrid ? (
|
||||
<Eye className="text-primary h-3.5 w-3.5" />
|
||||
) : (
|
||||
<EyeOff className="text-muted-foreground h-3.5 w-3.5" />
|
||||
)}
|
||||
<Label htmlFor="showGrid" className="text-xs 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 gap-2">
|
||||
<Zap className="text-primary h-3.5 w-3.5" />
|
||||
<Label htmlFor="snapToGrid" className="text-xs font-medium">
|
||||
격자 스냅
|
||||
</Label>
|
||||
</div>
|
||||
<Checkbox
|
||||
id="snapToGrid"
|
||||
checked={gridSettings.snapToGrid}
|
||||
onCheckedChange={(checked) => updateSetting("snapToGrid", checked)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 설정 영역 */}
|
||||
<div className="flex-1 space-y-4 overflow-y-auto p-4">
|
||||
{/* 격자 구조 */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-xs font-semibold">격자 구조</h4>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="columns" className="text-xs font-medium">
|
||||
컬럼 수
|
||||
</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
id="columns"
|
||||
type="number"
|
||||
min={1}
|
||||
max={safeMaxColumns}
|
||||
value={gridSettings.columns}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value, 10);
|
||||
if (!isNaN(value) && value >= 1 && value <= safeMaxColumns) {
|
||||
updateSetting("columns", value);
|
||||
}
|
||||
}}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
<span className="text-muted-foreground text-xs">/ {safeMaxColumns}</span>
|
||||
</div>
|
||||
<Slider
|
||||
id="columns-slider"
|
||||
min={1}
|
||||
max={safeMaxColumns}
|
||||
step={1}
|
||||
value={[gridSettings.columns]}
|
||||
onValueChange={([value]) => updateSetting("columns", value)}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="text-muted-foreground flex justify-between text-xs">
|
||||
<span>1열</span>
|
||||
<span>{safeMaxColumns}열</span>
|
||||
</div>
|
||||
{isColumnsTooSmall && (
|
||||
<p className="text-xs text-amber-600">
|
||||
⚠️ 컬럼 너비가 너무 작습니다 (최소 {MIN_COLUMN_WIDTH}px 권장)
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gap" className="text-xs font-medium">
|
||||
간격: <span className="text-primary">{gridSettings.gap}px</span>
|
||||
</Label>
|
||||
<Slider
|
||||
id="gap"
|
||||
min={0}
|
||||
max={40}
|
||||
step={2}
|
||||
value={[gridSettings.gap]}
|
||||
onValueChange={([value]) => updateSetting("gap", value)}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="text-muted-foreground flex justify-between text-xs">
|
||||
<span>0px</span>
|
||||
<span>40px</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="padding" className="text-xs font-medium">
|
||||
여백: <span className="text-primary">{gridSettings.padding}px</span>
|
||||
</Label>
|
||||
<Slider
|
||||
id="padding"
|
||||
min={0}
|
||||
max={60}
|
||||
step={4}
|
||||
value={[gridSettings.padding]}
|
||||
onValueChange={([value]) => updateSetting("padding", value)}
|
||||
className="w-full"
|
||||
/>
|
||||
<div className="text-muted-foreground flex justify-between text-xs">
|
||||
<span>0px</span>
|
||||
<span>60px</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 격자 정보 (읽기 전용) */}
|
||||
<div className="mt-4 rounded-md bg-muted p-3 text-xs text-muted-foreground">
|
||||
<p className="font-medium mb-1">🔧 격자 시스템</p>
|
||||
<ul className="space-y-0.5">
|
||||
<li>• 격자 크기: <span className="font-semibold">10px 고정</span></li>
|
||||
<li>• 컴포넌트는 10px 단위로 배치됩니다</li>
|
||||
<li>• 격자 스냅을 끄면 자유롭게 배치 가능</li>
|
||||
</ul>
|
||||
<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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<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="bg-primary/20 flex h-16 items-center justify-center rounded border-2 border-dashed border-blue-300">
|
||||
<span className="text-primary text-xs">컴포넌트 예시</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 푸터 */}
|
||||
<div className="border-t border-gray-200 bg-gray-50 p-3">
|
||||
<div className="text-muted-foreground text-xs">💡 격자 설정은 실시간으로 캔버스에 반영됩니다 </div>
|
||||
|
||||
{/* 해상도 및 격자 정보 */}
|
||||
{screenResolution && actualGridInfo && (
|
||||
<>
|
||||
<Separator />
|
||||
<div className="space-y-3">
|
||||
<h4 className="font-medium text-gray-900">격자 정보</h4>
|
||||
|
||||
<div className="space-y-2 text-xs" style={{ fontSize: "12px" }}>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">해상도:</span>
|
||||
<span className="font-mono">
|
||||
{screenResolution.width} × {screenResolution.height}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">컬럼 너비:</span>
|
||||
<span className={`font-mono ${isColumnsTooSmall ? "text-destructive" : "text-gray-900"}`}>
|
||||
{actualGridInfo.columnWidth.toFixed(1)}px
|
||||
{isColumnsTooSmall && " (너무 작음)"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">사용 가능 너비:</span>
|
||||
<span className="font-mono">
|
||||
{(screenResolution.width - gridSettings.padding * 2).toLocaleString()}px
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isColumnsTooSmall && (
|
||||
<div className="rounded-md bg-yellow-50 p-2 text-xs text-yellow-800">
|
||||
💡 컬럼이 너무 작습니다. 컬럼 수를 줄이거나 간격을 줄여보세요.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default GridPanel;
|
||||
|
||||
Reference in New Issue
Block a user