feature/v2-unified-renewal #379
|
|
@ -4252,6 +4252,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
onBack={onBackToList}
|
onBack={onBackToList}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
|
onResolutionChange={setScreenResolution}
|
||||||
|
gridSettings={layout.gridSettings}
|
||||||
|
onGridSettingsChange={updateGridSettings}
|
||||||
/>
|
/>
|
||||||
{/* 메인 컨테이너 (좌측 툴바 + 패널들 + 캔버스) */}
|
{/* 메인 컨테이너 (좌측 툴바 + 패널들 + 캔버스) */}
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
|
|
@ -4303,9 +4306,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
<UnifiedPropertiesPanel
|
<UnifiedPropertiesPanel
|
||||||
selectedComponent={selectedComponent || undefined}
|
selectedComponent={selectedComponent || undefined}
|
||||||
tables={tables}
|
tables={tables}
|
||||||
gridSettings={layout.gridSettings}
|
|
||||||
onUpdateProperty={updateComponentProperty}
|
onUpdateProperty={updateComponentProperty}
|
||||||
onGridSettingsChange={updateGridSettings}
|
|
||||||
onDeleteComponent={deleteComponent}
|
onDeleteComponent={deleteComponent}
|
||||||
onCopyComponent={copyComponent}
|
onCopyComponent={copyComponent}
|
||||||
currentTable={tables.length > 0 ? tables[0] : undefined}
|
currentTable={tables.length > 0 ? tables[0] : undefined}
|
||||||
|
|
@ -4317,8 +4318,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
updateComponentProperty(selectedComponent.id, "style", style);
|
updateComponentProperty(selectedComponent.id, "style", style);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
currentResolution={screenResolution}
|
|
||||||
onResolutionChange={handleResolutionChange}
|
|
||||||
allComponents={layout.components} // 🆕 플로우 위젯 감지용
|
allComponents={layout.components} // 🆕 플로우 위젯 감지용
|
||||||
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
import { ChevronDown, Settings, Info, Database, Trash2, Copy, Palette, Monitor } from "lucide-react";
|
import { ChevronDown, Settings, Info, Database, Trash2, Copy, Palette } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
ComponentData,
|
ComponentData,
|
||||||
WebType,
|
WebType,
|
||||||
|
|
@ -59,26 +59,15 @@ import { AlertConfigPanel } from "../config-panels/AlertConfigPanel";
|
||||||
import { BadgeConfigPanel } from "../config-panels/BadgeConfigPanel";
|
import { BadgeConfigPanel } from "../config-panels/BadgeConfigPanel";
|
||||||
import { DynamicComponentConfigPanel } from "@/lib/utils/getComponentConfigPanel";
|
import { DynamicComponentConfigPanel } from "@/lib/utils/getComponentConfigPanel";
|
||||||
import StyleEditor from "../StyleEditor";
|
import StyleEditor from "../StyleEditor";
|
||||||
import ResolutionPanel from "./ResolutionPanel";
|
|
||||||
import { Slider } from "@/components/ui/slider";
|
import { Slider } from "@/components/ui/slider";
|
||||||
import { Grid3X3, Eye, EyeOff, Zap } from "lucide-react";
|
import { Zap } from "lucide-react";
|
||||||
import { ConditionalConfigPanel } from "@/components/unified/ConditionalConfigPanel";
|
import { ConditionalConfigPanel } from "@/components/unified/ConditionalConfigPanel";
|
||||||
import { ConditionalConfig } from "@/types/unified-components";
|
import { ConditionalConfig } from "@/types/unified-components";
|
||||||
|
|
||||||
interface UnifiedPropertiesPanelProps {
|
interface UnifiedPropertiesPanelProps {
|
||||||
selectedComponent?: ComponentData;
|
selectedComponent?: ComponentData;
|
||||||
tables: TableInfo[];
|
tables: TableInfo[];
|
||||||
gridSettings?: {
|
|
||||||
columns: number;
|
|
||||||
gap: number;
|
|
||||||
padding: number;
|
|
||||||
snapToGrid: boolean;
|
|
||||||
showGrid: boolean;
|
|
||||||
gridColor?: string;
|
|
||||||
gridOpacity?: number;
|
|
||||||
};
|
|
||||||
onUpdateProperty: (componentId: string, path: string, value: any) => void;
|
onUpdateProperty: (componentId: string, path: string, value: any) => void;
|
||||||
onGridSettingsChange?: (settings: any) => void;
|
|
||||||
onDeleteComponent?: (componentId: string) => void;
|
onDeleteComponent?: (componentId: string) => void;
|
||||||
onCopyComponent?: (componentId: string) => void;
|
onCopyComponent?: (componentId: string) => void;
|
||||||
currentTable?: TableInfo;
|
currentTable?: TableInfo;
|
||||||
|
|
@ -86,9 +75,6 @@ interface UnifiedPropertiesPanelProps {
|
||||||
dragState?: any;
|
dragState?: any;
|
||||||
// 스타일 관련
|
// 스타일 관련
|
||||||
onStyleChange?: (style: any) => void;
|
onStyleChange?: (style: any) => void;
|
||||||
// 해상도 관련
|
|
||||||
currentResolution?: { name: string; width: number; height: number };
|
|
||||||
onResolutionChange?: (resolution: { name: string; width: number; height: number }) => void;
|
|
||||||
// 🆕 플로우 위젯 감지용
|
// 🆕 플로우 위젯 감지용
|
||||||
allComponents?: ComponentData[];
|
allComponents?: ComponentData[];
|
||||||
// 🆕 메뉴 OBJID (코드/카테고리 스코프용)
|
// 🆕 메뉴 OBJID (코드/카테고리 스코프용)
|
||||||
|
|
@ -100,9 +86,7 @@ interface UnifiedPropertiesPanelProps {
|
||||||
export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
selectedComponent,
|
selectedComponent,
|
||||||
tables,
|
tables,
|
||||||
gridSettings,
|
|
||||||
onUpdateProperty,
|
onUpdateProperty,
|
||||||
onGridSettingsChange,
|
|
||||||
onDeleteComponent,
|
onDeleteComponent,
|
||||||
onCopyComponent,
|
onCopyComponent,
|
||||||
currentTable,
|
currentTable,
|
||||||
|
|
@ -111,8 +95,6 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
dragState,
|
dragState,
|
||||||
onStyleChange,
|
onStyleChange,
|
||||||
menuObjid,
|
menuObjid,
|
||||||
currentResolution,
|
|
||||||
onResolutionChange,
|
|
||||||
allComponents = [], // 🆕 기본값 빈 배열
|
allComponents = [], // 🆕 기본값 빈 배열
|
||||||
}) => {
|
}) => {
|
||||||
const { webTypes } = useWebTypes({ active: "Y" });
|
const { webTypes } = useWebTypes({ active: "Y" });
|
||||||
|
|
@ -165,106 +147,13 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
}
|
}
|
||||||
}, [selectedComponent?.size?.width, selectedComponent?.id]);
|
}, [selectedComponent?.size?.width, selectedComponent?.id]);
|
||||||
|
|
||||||
// 격자 설정 업데이트 함수 (early return 이전에 정의)
|
// 컴포넌트가 선택되지 않았을 때는 안내 메시지만 표시
|
||||||
const updateGridSetting = (key: string, value: any) => {
|
|
||||||
if (onGridSettingsChange && gridSettings) {
|
|
||||||
onGridSettingsChange({
|
|
||||||
...gridSettings,
|
|
||||||
[key]: value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 격자 설정 렌더링 (early return 이전에 정의)
|
|
||||||
const renderGridSettings = () => {
|
|
||||||
if (!gridSettings || !onGridSettingsChange) return null;
|
|
||||||
|
|
||||||
// 최대 컬럼 수 계산
|
|
||||||
const MIN_COLUMN_WIDTH = 30;
|
|
||||||
const maxColumns = currentResolution
|
|
||||||
? Math.floor(
|
|
||||||
(currentResolution.width - gridSettings.padding * 2 + gridSettings.gap) /
|
|
||||||
(MIN_COLUMN_WIDTH + gridSettings.gap),
|
|
||||||
)
|
|
||||||
: 24;
|
|
||||||
const safeMaxColumns = Math.max(1, Math.min(maxColumns, 100)); // 최대 100개로 제한
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<Grid3X3 className="text-primary h-3 w-3" />
|
|
||||||
<h4 className="text-xs font-semibold">격자 설정</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-3">
|
|
||||||
{/* 토글들 */}
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{gridSettings.showGrid ? (
|
|
||||||
<Eye className="text-primary h-3 w-3" />
|
|
||||||
) : (
|
|
||||||
<EyeOff className="text-muted-foreground h-3 w-3" />
|
|
||||||
)}
|
|
||||||
<Label htmlFor="showGrid" className="text-xs font-medium">
|
|
||||||
격자 표시
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="showGrid"
|
|
||||||
checked={gridSettings.showGrid}
|
|
||||||
onCheckedChange={(checked) => updateGridSetting("showGrid", checked)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Zap className="text-primary h-3 w-3" />
|
|
||||||
<Label htmlFor="snapToGrid" className="text-xs font-medium">
|
|
||||||
격자 스냅
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
<Checkbox
|
|
||||||
id="snapToGrid"
|
|
||||||
checked={gridSettings.snapToGrid}
|
|
||||||
onCheckedChange={(checked) => updateGridSetting("snapToGrid", checked)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 10px 단위 스냅 안내 */}
|
|
||||||
<div className="bg-muted/50 rounded-md p-2">
|
|
||||||
<p className="text-muted-foreground text-[10px]">모든 컴포넌트는 10px 단위로 자동 배치됩니다.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 컴포넌트가 선택되지 않았을 때도 해상도 설정과 격자 설정은 표시
|
|
||||||
if (!selectedComponent) {
|
if (!selectedComponent) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full flex-col overflow-x-auto bg-white">
|
<div className="flex h-full flex-col overflow-x-auto bg-white">
|
||||||
{/* 해상도 설정과 격자 설정 표시 */}
|
|
||||||
<div className="flex-1 overflow-x-auto overflow-y-auto p-2">
|
<div className="flex-1 overflow-x-auto overflow-y-auto p-2">
|
||||||
<div className="space-y-4 text-xs">
|
<div className="space-y-4 text-xs">
|
||||||
{/* 해상도 설정 */}
|
|
||||||
{currentResolution && onResolutionChange && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<Monitor className="text-primary h-3 w-3" />
|
|
||||||
<h4 className="text-xs font-semibold">해상도 설정</h4>
|
|
||||||
</div>
|
|
||||||
<ResolutionPanel currentResolution={currentResolution} onResolutionChange={onResolutionChange} />
|
|
||||||
</div>
|
|
||||||
<Separator className="my-2" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 격자 설정 */}
|
|
||||||
{renderGridSettings()}
|
|
||||||
|
|
||||||
{/* 안내 메시지 */}
|
{/* 안내 메시지 */}
|
||||||
<Separator className="my-4" />
|
|
||||||
<div className="flex flex-col items-center justify-center py-8 text-center">
|
<div className="flex flex-col items-center justify-center py-8 text-center">
|
||||||
<Settings className="text-muted-foreground/30 mb-2 h-8 w-8" />
|
<Settings className="text-muted-foreground/30 mb-2 h-8 w-8" />
|
||||||
<p className="text-muted-foreground text-[10px]">컴포넌트를 선택하여</p>
|
<p className="text-muted-foreground text-[10px]">컴포넌트를 선택하여</p>
|
||||||
|
|
@ -1518,24 +1407,6 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
{/* 통합 컨텐츠 (탭 제거) */}
|
{/* 통합 컨텐츠 (탭 제거) */}
|
||||||
<div className="flex-1 overflow-x-auto overflow-y-auto p-2">
|
<div className="flex-1 overflow-x-auto overflow-y-auto p-2">
|
||||||
<div className="space-y-4 text-xs">
|
<div className="space-y-4 text-xs">
|
||||||
{/* 해상도 설정 - 항상 맨 위에 표시 */}
|
|
||||||
{currentResolution && onResolutionChange && (
|
|
||||||
<>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<Monitor className="text-primary h-3 w-3" />
|
|
||||||
<h4 className="text-xs font-semibold">해상도 설정</h4>
|
|
||||||
</div>
|
|
||||||
<ResolutionPanel currentResolution={currentResolution} onResolutionChange={onResolutionChange} />
|
|
||||||
</div>
|
|
||||||
<Separator className="my-2" />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 격자 설정 - 해상도 설정 아래 표시 */}
|
|
||||||
{renderGridSettings()}
|
|
||||||
{gridSettings && onGridSettingsChange && <Separator className="my-2" />}
|
|
||||||
|
|
||||||
{/* 기본 설정 */}
|
{/* 기본 설정 */}
|
||||||
{renderBasicTab()}
|
{renderBasicTab()}
|
||||||
|
|
||||||
|
|
@ -1578,10 +1449,10 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
const widgetType = (c as any).widgetType || (c as any).componentType || "text";
|
const widgetType = (c as any).widgetType || (c as any).componentType || "text";
|
||||||
const config = (c as any).componentConfig || (c as any).webTypeConfig || {};
|
const config = (c as any).componentConfig || (c as any).webTypeConfig || {};
|
||||||
const detailSettings = (c as any).detailSettings || {};
|
const detailSettings = (c as any).detailSettings || {};
|
||||||
|
|
||||||
// 정적 옵션 추출 (select, dropdown, radio, entity 등)
|
// 정적 옵션 추출 (select, dropdown, radio, entity 등)
|
||||||
let options: Array<{ value: string; label: string }> | undefined;
|
let options: Array<{ value: string; label: string }> | undefined;
|
||||||
|
|
||||||
// Unified 컴포넌트의 경우
|
// Unified 컴포넌트의 경우
|
||||||
if (config.options && Array.isArray(config.options)) {
|
if (config.options && Array.isArray(config.options)) {
|
||||||
options = config.options;
|
options = config.options;
|
||||||
|
|
@ -1590,27 +1461,27 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||||
else if ((c as any).options && Array.isArray((c as any).options)) {
|
else if ((c as any).options && Array.isArray((c as any).options)) {
|
||||||
options = (c as any).options;
|
options = (c as any).options;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 엔티티 정보 추출 (config > detailSettings > 직접 속성 순으로 우선순위)
|
// 엔티티 정보 추출 (config > detailSettings > 직접 속성 순으로 우선순위)
|
||||||
const entityTable =
|
const entityTable =
|
||||||
config.entityTable ||
|
config.entityTable ||
|
||||||
detailSettings.referenceTable ||
|
detailSettings.referenceTable ||
|
||||||
(c as any).entityTable ||
|
(c as any).entityTable ||
|
||||||
(c as any).referenceTable;
|
(c as any).referenceTable;
|
||||||
const entityValueColumn =
|
const entityValueColumn =
|
||||||
config.entityValueColumn ||
|
config.entityValueColumn ||
|
||||||
detailSettings.referenceColumn ||
|
detailSettings.referenceColumn ||
|
||||||
(c as any).entityValueColumn ||
|
(c as any).entityValueColumn ||
|
||||||
(c as any).referenceColumn;
|
(c as any).referenceColumn;
|
||||||
const entityLabelColumn =
|
const entityLabelColumn =
|
||||||
config.entityLabelColumn ||
|
config.entityLabelColumn ||
|
||||||
detailSettings.displayColumn ||
|
detailSettings.displayColumn ||
|
||||||
(c as any).entityLabelColumn ||
|
(c as any).entityLabelColumn ||
|
||||||
(c as any).displayColumn;
|
(c as any).displayColumn;
|
||||||
|
|
||||||
// 공통코드 정보 추출
|
// 공통코드 정보 추출
|
||||||
const codeGroup = config.codeGroup || detailSettings.codeGroup || (c as any).codeGroup;
|
const codeGroup = config.codeGroup || detailSettings.codeGroup || (c as any).codeGroup;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: (c as any).columnName || c.id,
|
id: (c as any).columnName || c.id,
|
||||||
label: (c as any).label || config.label || c.id,
|
label: (c as any).label || config.label || c.id,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,48 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Database, ArrowLeft, Save, Monitor, Smartphone } from "lucide-react";
|
import { Input } from "@/components/ui/input";
|
||||||
import { ScreenResolution } from "@/types/screen";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import {
|
||||||
|
Database,
|
||||||
|
ArrowLeft,
|
||||||
|
Save,
|
||||||
|
Monitor,
|
||||||
|
Smartphone,
|
||||||
|
Tablet,
|
||||||
|
ChevronDown,
|
||||||
|
Settings,
|
||||||
|
Grid3X3,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
Zap,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { ScreenResolution, SCREEN_RESOLUTIONS } from "@/types/screen";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
|
interface GridSettings {
|
||||||
|
columns: number;
|
||||||
|
gap: number;
|
||||||
|
padding: number;
|
||||||
|
snapToGrid: boolean;
|
||||||
|
showGrid: boolean;
|
||||||
|
gridColor?: string;
|
||||||
|
gridOpacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface SlimToolbarProps {
|
interface SlimToolbarProps {
|
||||||
screenName?: string;
|
screenName?: string;
|
||||||
|
|
@ -13,6 +52,9 @@ interface SlimToolbarProps {
|
||||||
onSave: () => void;
|
onSave: () => void;
|
||||||
isSaving?: boolean;
|
isSaving?: boolean;
|
||||||
onPreview?: () => void;
|
onPreview?: () => void;
|
||||||
|
onResolutionChange?: (resolution: ScreenResolution) => void;
|
||||||
|
gridSettings?: GridSettings;
|
||||||
|
onGridSettingsChange?: (settings: GridSettings) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SlimToolbar: React.FC<SlimToolbarProps> = ({
|
export const SlimToolbar: React.FC<SlimToolbarProps> = ({
|
||||||
|
|
@ -23,7 +65,52 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
|
||||||
onSave,
|
onSave,
|
||||||
isSaving = false,
|
isSaving = false,
|
||||||
onPreview,
|
onPreview,
|
||||||
|
onResolutionChange,
|
||||||
|
gridSettings,
|
||||||
|
onGridSettingsChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
// 사용자 정의 해상도 상태
|
||||||
|
const [customWidth, setCustomWidth] = useState("");
|
||||||
|
const [customHeight, setCustomHeight] = useState("");
|
||||||
|
const [showCustomInput, setShowCustomInput] = useState(false);
|
||||||
|
|
||||||
|
const getCategoryIcon = (category: string) => {
|
||||||
|
switch (category) {
|
||||||
|
case "desktop":
|
||||||
|
return <Monitor className="h-4 w-4 text-blue-600" />;
|
||||||
|
case "tablet":
|
||||||
|
return <Tablet className="h-4 w-4 text-green-600" />;
|
||||||
|
case "mobile":
|
||||||
|
return <Smartphone className="h-4 w-4 text-purple-600" />;
|
||||||
|
default:
|
||||||
|
return <Monitor className="h-4 w-4 text-blue-600" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomResolution = () => {
|
||||||
|
const width = parseInt(customWidth);
|
||||||
|
const height = parseInt(customHeight);
|
||||||
|
if (width > 0 && height > 0 && onResolutionChange) {
|
||||||
|
const customResolution: ScreenResolution = {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
name: `사용자 정의 (${width}×${height})`,
|
||||||
|
category: "custom",
|
||||||
|
};
|
||||||
|
onResolutionChange(customResolution);
|
||||||
|
setShowCustomInput(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGridSetting = (key: keyof GridSettings, value: boolean) => {
|
||||||
|
if (onGridSettingsChange && gridSettings) {
|
||||||
|
onGridSettingsChange({
|
||||||
|
...gridSettings,
|
||||||
|
[key]: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-14 items-center justify-between border-b border-gray-200 bg-gradient-to-r from-gray-50 to-white px-4 shadow-sm">
|
<div className="flex h-14 items-center justify-between border-b border-gray-200 bg-gradient-to-r from-gray-50 to-white px-4 shadow-sm">
|
||||||
{/* 좌측: 네비게이션 및 화면 정보 */}
|
{/* 좌측: 네비게이션 및 화면 정보 */}
|
||||||
|
|
@ -47,16 +134,149 @@ export const SlimToolbar: React.FC<SlimToolbarProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 해상도 정보 표시 */}
|
{/* 해상도 선택 드롭다운 */}
|
||||||
{screenResolution && (
|
{screenResolution && (
|
||||||
<>
|
<>
|
||||||
<div className="h-6 w-px bg-gray-300" />
|
<div className="h-6 w-px bg-gray-300" />
|
||||||
<div className="flex items-center space-x-2 rounded-md bg-blue-50 px-3 py-1.5">
|
<Popover open={showCustomInput} onOpenChange={setShowCustomInput}>
|
||||||
<Monitor className="h-4 w-4 text-blue-600" />
|
<DropdownMenu>
|
||||||
<span className="text-sm font-medium text-blue-900">{screenResolution.name}</span>
|
<DropdownMenuTrigger asChild>
|
||||||
<span className="text-xs text-blue-600">
|
<button className="flex items-center space-x-2 rounded-md bg-blue-50 px-3 py-1.5 transition-colors hover:bg-blue-100">
|
||||||
({screenResolution.width} × {screenResolution.height})
|
{getCategoryIcon(screenResolution.category || "desktop")}
|
||||||
</span>
|
<span className="text-sm font-medium text-blue-900">{screenResolution.name}</span>
|
||||||
|
<span className="text-xs text-blue-600">
|
||||||
|
({screenResolution.width} × {screenResolution.height})
|
||||||
|
</span>
|
||||||
|
{onResolutionChange && <ChevronDown className="h-3 w-3 text-blue-600" />}
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
{onResolutionChange && (
|
||||||
|
<DropdownMenuContent align="start" className="w-64">
|
||||||
|
<DropdownMenuLabel className="text-xs text-gray-500">데스크톱</DropdownMenuLabel>
|
||||||
|
{SCREEN_RESOLUTIONS.filter((r) => r.category === "desktop").map((resolution) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={resolution.name}
|
||||||
|
onClick={() => onResolutionChange(resolution)}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<Monitor className="h-4 w-4 text-blue-600" />
|
||||||
|
<span className="flex-1">{resolution.name}</span>
|
||||||
|
<span className="text-xs text-gray-400">
|
||||||
|
{resolution.width}×{resolution.height}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuLabel className="text-xs text-gray-500">태블릿</DropdownMenuLabel>
|
||||||
|
{SCREEN_RESOLUTIONS.filter((r) => r.category === "tablet").map((resolution) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={resolution.name}
|
||||||
|
onClick={() => onResolutionChange(resolution)}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<Tablet className="h-4 w-4 text-green-600" />
|
||||||
|
<span className="flex-1">{resolution.name}</span>
|
||||||
|
<span className="text-xs text-gray-400">
|
||||||
|
{resolution.width}×{resolution.height}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuLabel className="text-xs text-gray-500">모바일</DropdownMenuLabel>
|
||||||
|
{SCREEN_RESOLUTIONS.filter((r) => r.category === "mobile").map((resolution) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={resolution.name}
|
||||||
|
onClick={() => onResolutionChange(resolution)}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<Smartphone className="h-4 w-4 text-purple-600" />
|
||||||
|
<span className="flex-1">{resolution.name}</span>
|
||||||
|
<span className="text-xs text-gray-400">
|
||||||
|
{resolution.width}×{resolution.height}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuLabel className="text-xs text-gray-500">사용자 정의</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setCustomWidth(screenResolution.width.toString());
|
||||||
|
setCustomHeight(screenResolution.height.toString());
|
||||||
|
setShowCustomInput(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center space-x-2"
|
||||||
|
>
|
||||||
|
<Settings className="h-4 w-4 text-gray-600" />
|
||||||
|
<span className="flex-1">사용자 정의...</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
)}
|
||||||
|
</DropdownMenu>
|
||||||
|
<PopoverContent align="start" className="w-64 p-3">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="text-sm font-medium">사용자 정의 해상도</div>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="text-xs text-gray-500">너비 (px)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={customWidth}
|
||||||
|
onChange={(e) => setCustomWidth(e.target.value)}
|
||||||
|
placeholder="1920"
|
||||||
|
className="h-8 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="text-xs text-gray-500">높이 (px)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={customHeight}
|
||||||
|
onChange={(e) => setCustomHeight(e.target.value)}
|
||||||
|
placeholder="1080"
|
||||||
|
className="h-8 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleCustomResolution} size="sm" className="w-full">
|
||||||
|
적용
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 격자 설정 */}
|
||||||
|
{gridSettings && onGridSettingsChange && (
|
||||||
|
<>
|
||||||
|
<div className="h-6 w-px bg-gray-300" />
|
||||||
|
<div className="flex items-center space-x-2 rounded-md bg-gray-50 px-3 py-1.5">
|
||||||
|
<Grid3X3 className="h-4 w-4 text-gray-600" />
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
|
<label className="flex cursor-pointer items-center space-x-1.5">
|
||||||
|
{gridSettings.showGrid ? (
|
||||||
|
<Eye className="h-3.5 w-3.5 text-primary" />
|
||||||
|
) : (
|
||||||
|
<EyeOff className="h-3.5 w-3.5 text-gray-400" />
|
||||||
|
)}
|
||||||
|
<Checkbox
|
||||||
|
checked={gridSettings.showGrid}
|
||||||
|
onCheckedChange={(checked) => updateGridSetting("showGrid", checked as boolean)}
|
||||||
|
className="h-3.5 w-3.5"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-gray-600">격자 표시</span>
|
||||||
|
</label>
|
||||||
|
<label className="flex cursor-pointer items-center space-x-1.5">
|
||||||
|
<Zap className={`h-3.5 w-3.5 ${gridSettings.snapToGrid ? "text-primary" : "text-gray-400"}`} />
|
||||||
|
<Checkbox
|
||||||
|
checked={gridSettings.snapToGrid}
|
||||||
|
onCheckedChange={(checked) => updateGridSetting("snapToGrid", checked as boolean)}
|
||||||
|
className="h-3.5 w-3.5"
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-gray-600">격자 스냅</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue