반응형 레이아웃 기능 구현
This commit is contained in:
156
frontend/components/screen/panels/ResponsiveConfigPanel.tsx
Normal file
156
frontend/components/screen/panels/ResponsiveConfigPanel.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 반응형 설정 패널
|
||||
*
|
||||
* 컴포넌트별로 브레이크포인트마다 다른 레이아웃 설정 가능
|
||||
*/
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { ComponentData } from "@/types/screen-management";
|
||||
import { Breakpoint, BREAKPOINTS, ResponsiveComponentConfig } from "@/types/responsive";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
interface ResponsiveConfigPanelProps {
|
||||
component: ComponentData;
|
||||
onUpdate: (config: ResponsiveComponentConfig) => void;
|
||||
}
|
||||
|
||||
export const ResponsiveConfigPanel: React.FC<ResponsiveConfigPanelProps> = ({ component, onUpdate }) => {
|
||||
const [activeTab, setActiveTab] = useState<Breakpoint>("desktop");
|
||||
|
||||
const config = component.responsiveConfig || {
|
||||
designerPosition: {
|
||||
x: component.position.x,
|
||||
y: component.position.y,
|
||||
width: component.size.width,
|
||||
height: component.size.height,
|
||||
},
|
||||
useSmartDefaults: true,
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>반응형 설정</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* 스마트 기본값 토글 */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="smartDefaults"
|
||||
checked={config.useSmartDefaults}
|
||||
onCheckedChange={(checked) => {
|
||||
onUpdate({
|
||||
...config,
|
||||
useSmartDefaults: checked as boolean,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="smartDefaults">스마트 기본값 사용 (권장)</Label>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-gray-500">
|
||||
스마트 기본값은 컴포넌트 크기에 따라 자동으로 반응형 레이아웃을 생성합니다.
|
||||
</div>
|
||||
|
||||
{/* 수동 설정 */}
|
||||
{!config.useSmartDefaults && (
|
||||
<Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as Breakpoint)}>
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="desktop">데스크톱</TabsTrigger>
|
||||
<TabsTrigger value="tablet">태블릿</TabsTrigger>
|
||||
<TabsTrigger value="mobile">모바일</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value={activeTab} className="space-y-4">
|
||||
{/* 그리드 컬럼 수 */}
|
||||
<div className="space-y-2">
|
||||
<Label>너비 (그리드 컬럼)</Label>
|
||||
<Select
|
||||
value={config.responsive?.[activeTab]?.gridColumns?.toString()}
|
||||
onValueChange={(v) => {
|
||||
onUpdate({
|
||||
...config,
|
||||
responsive: {
|
||||
...config.responsive,
|
||||
[activeTab]: {
|
||||
...config.responsive?.[activeTab],
|
||||
gridColumns: parseInt(v),
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="컬럼 수 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{[...Array(BREAKPOINTS[activeTab].columns)].map((_, i) => {
|
||||
const cols = i + 1;
|
||||
const percent = ((cols / BREAKPOINTS[activeTab].columns) * 100).toFixed(0);
|
||||
return (
|
||||
<SelectItem key={cols} value={cols.toString()}>
|
||||
{cols} / {BREAKPOINTS[activeTab].columns} ({percent}%)
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 표시 순서 */}
|
||||
<div className="space-y-2">
|
||||
<Label>표시 순서</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
value={config.responsive?.[activeTab]?.order || 1}
|
||||
onChange={(e) => {
|
||||
onUpdate({
|
||||
...config,
|
||||
responsive: {
|
||||
...config.responsive,
|
||||
[activeTab]: {
|
||||
...config.responsive?.[activeTab],
|
||||
order: parseInt(e.target.value),
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="text-xs text-gray-500">작은 숫자가 먼저 표시됩니다.</div>
|
||||
</div>
|
||||
|
||||
{/* 숨김 */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`hide-${activeTab}`}
|
||||
checked={config.responsive?.[activeTab]?.hide || false}
|
||||
onCheckedChange={(checked) => {
|
||||
onUpdate({
|
||||
...config,
|
||||
responsive: {
|
||||
...config.responsive,
|
||||
[activeTab]: {
|
||||
...config.responsive?.[activeTab],
|
||||
hide: checked as boolean,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor={`hide-${activeTab}`}>
|
||||
{activeTab === "desktop" ? "데스크톱" : activeTab === "tablet" ? "태블릿" : "모바일"}에서 숨김
|
||||
</Label>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -48,6 +48,7 @@ import { DashboardConfigPanel } from "../config-panels/DashboardConfigPanel";
|
||||
import { StatsCardConfigPanel } from "../config-panels/StatsCardConfigPanel";
|
||||
import { ProgressBarConfigPanel } from "../config-panels/ProgressBarConfigPanel";
|
||||
import { ChartConfigPanel } from "../config-panels/ChartConfigPanel";
|
||||
import { ResponsiveConfigPanel } from "./ResponsiveConfigPanel";
|
||||
import { AlertConfigPanel } from "../config-panels/AlertConfigPanel";
|
||||
import { BadgeConfigPanel } from "../config-panels/BadgeConfigPanel";
|
||||
import { DynamicComponentConfigPanel } from "@/lib/utils/getComponentConfigPanel";
|
||||
@@ -707,6 +708,7 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||
<TabsTrigger value="basic">기본</TabsTrigger>
|
||||
<TabsTrigger value="detail">상세</TabsTrigger>
|
||||
<TabsTrigger value="data">데이터</TabsTrigger>
|
||||
<TabsTrigger value="responsive">반응형</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
@@ -719,6 +721,14 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||
<TabsContent value="data" className="m-0 p-4">
|
||||
{renderDataTab()}
|
||||
</TabsContent>
|
||||
<TabsContent value="responsive" className="m-0 p-4">
|
||||
<ResponsiveConfigPanel
|
||||
component={selectedComponent}
|
||||
onUpdate={(config) => {
|
||||
onUpdateProperty(selectedComponent.id, "responsiveConfig", config);
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user