# ๐Ÿ—‘๏ธ Width ์†์„ฑ ์™„์ „ ์ œ๊ฑฐ ๊ณ„ํš์„œ ## ๐ŸŽฏ ๋ชฉํ‘œ ํ˜„์žฌ ํ™”๋ฉด ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์—์„œ **ํ”ฝ์…€ ๊ธฐ๋ฐ˜ width ์„ค์ •์„ ์™„์ „ํžˆ ์ œ๊ฑฐ**ํ•˜๊ณ , **์ปฌ๋Ÿผ ์ˆ˜(gridColumnSpan)๋กœ๋งŒ ์ œ์–ด**ํ•˜๋„๋ก ๋ณ€๊ฒฝ ## ๐Ÿ“Š ํ˜„์žฌ Width ์‚ฌ์šฉ ํ˜„ํ™ฉ ### 1. ํƒ€์ž… ์ •์˜์—์„œ์˜ width ```typescript // frontend/types/screen-management.ts export interface BaseComponent { size: Size; // โŒ ์ œ๊ฑฐ ๋Œ€์ƒ } export interface Size { width: number; // โŒ ์ œ๊ฑฐ height: number; // โœ… ์œ ์ง€ (ํ–‰ ๋†’์ด ์ œ์–ด์šฉ) } ``` ### 2. PropertiesPanel์—์„œ width ์ž…๋ ฅ UI **์œ„์น˜**: `frontend/components/screen/panels/PropertiesPanel.tsx` - ๋ผ์ธ 665-680: ๋„ˆ๋น„ ์ž…๋ ฅ ํ•„๋“œ - ๋ผ์ธ 1081-1092: ์‚ฌ์ด๋“œ๋ฐ” ๋„ˆ๋น„ ์„ค์ • ### 3. StyleEditor์—์„œ width ์Šคํƒ€์ผ **์œ„์น˜**: `frontend/components/screen/ScreenDesigner.tsx` - ๋ผ์ธ 3874-3891: ์Šคํƒ€์ผ์—์„œ width ์ถ”์ถœ ๋ฐ ์ ์šฉ ### 4. ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง์—์„œ width ์‚ฌ์šฉ **์œ„์น˜**: `frontend/components/screen/layout/ContainerComponent.tsx` - ๋ผ์ธ 27: `gridColumn: span ${component.size.width}` ### 5. ํ…œํ”Œ๋ฆฟ์—์„œ width ์ •์˜ **์œ„์น˜**: `frontend/components/screen/panels/TemplatesPanel.tsx` - ๋ผ์ธ 48: `defaultSize: { width, height }` - ๋ผ์ธ 54: `size: { width, height }` --- ## ๐Ÿ”„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „๋žต ### Phase 1: ํƒ€์ž… ์‹œ์Šคํ…œ ์ˆ˜์ • #### 1.1 ์ƒˆ๋กœ์šด ํƒ€์ž… ์ •์˜ ```typescript // frontend/types/screen-management.ts /** * ๐Ÿ†• ์ƒˆ๋กœ์šด Size ์ธํ„ฐํŽ˜์ด์Šค (width ์ œ๊ฑฐ) */ export interface Size { height: number; // ํ–‰ ๋†’์ด๋งŒ ์ œ์–ด } /** * ๐Ÿ†• BaseComponent ํ™•์žฅ */ export interface BaseComponent { id: string; type: ComponentType; position: Position; // y ์ขŒํ‘œ๋งŒ ์‚ฌ์šฉ (ํ–‰ ์œ„์น˜) size: Size; // height๋งŒ ํฌํ•จ // ๐Ÿ†• ๊ทธ๋ฆฌ๋“œ ์‹œ์Šคํ…œ ์†์„ฑ gridColumnSpan: ColumnSpanPreset; // ํ•„์ˆ˜: ์ปฌ๋Ÿผ ๋„ˆ๋น„ gridColumnStart?: number; // ์„ ํƒ: ์‹œ์ž‘ ์ปฌ๋Ÿผ gridRowIndex: number; // ํ•„์ˆ˜: ํ–‰ ์ธ๋ฑ์Šค parentId?: string; label?: string; required?: boolean; readonly?: boolean; style?: ComponentStyle; className?: string; } /** * ๐Ÿ†• ์ปฌ๋Ÿผ ์ŠคํŒฌ ํ”„๋ฆฌ์…‹ */ export type ColumnSpanPreset = | "full" // 12 ์ปฌ๋Ÿผ | "half" // 6 ์ปฌ๋Ÿผ | "third" // 4 ์ปฌ๋Ÿผ | "twoThirds" // 8 ์ปฌ๋Ÿผ | "quarter" // 3 ์ปฌ๋Ÿผ | "threeQuarters" // 9 ์ปฌ๋Ÿผ | "label" // 3 ์ปฌ๋Ÿผ (๋ผ๋ฒจ์šฉ) | "input" // 9 ์ปฌ๋Ÿผ (์ž…๋ ฅ์šฉ) | "small" // 2 ์ปฌ๋Ÿผ | "medium" // 4 ์ปฌ๋Ÿผ | "large" // 8 ์ปฌ๋Ÿผ | "auto"; // ์ž๋™ ๊ณ„์‚ฐ export const COLUMN_SPAN_VALUES: Record = { full: 12, half: 6, third: 4, twoThirds: 8, quarter: 3, threeQuarters: 9, label: 3, input: 9, small: 2, medium: 4, large: 8, auto: 0, // ์ž๋™ ๊ณ„์‚ฐ }; ``` #### 1.2 ComponentStyle ์ˆ˜์ • (width ์ œ๊ฑฐ) ```typescript export interface ComponentStyle extends CommonStyle { // โŒ ์ œ๊ฑฐ: width // โœ… ์œ ์ง€: height (์ปดํฌ๋„ŒํŠธ ์ž์ฒด ๋†’์ด) height?: string; // ๋‚˜๋จธ์ง€ ์Šคํƒ€์ผ ์†์„ฑ๋“ค margin?: string; padding?: string; backgroundColor?: string; // ... ๊ธฐํƒ€ } ``` --- ### Phase 2: UI ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ • #### 2.1 PropertiesPanel ์ˆ˜์ • **ํŒŒ์ผ**: `frontend/components/screen/panels/PropertiesPanel.tsx` **๋ณ€๊ฒฝ ์ „**: ```typescript // ๋ผ์ธ 665-680
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, width: newValue })); onUpdateProperty("size.width", Number(newValue)); }} className="mt-1" />
``` **๋ณ€๊ฒฝ ํ›„**: ```typescript { /* ๐Ÿ†• ์ปฌ๋Ÿผ ์ŠคํŒฌ ์„ ํƒ */ }
{/* ์‹œ๊ฐ์  ํ”„๋ฆฌ๋ทฐ */}
{Array.from({ length: 12 }).map((_, i) => { const spanValue = COLUMN_SPAN_VALUES[selectedComponent.gridColumnSpan || "half"]; const startCol = selectedComponent.gridColumnStart || 1; const isActive = i + 1 >= startCol && i + 1 < startCol + spanValue; return (
); })}

{COLUMN_SPAN_VALUES[selectedComponent.gridColumnSpan || "half"]} / 12 ์ปฌ๋Ÿผ

; { /* ๐Ÿ†• ์‹œ์ž‘ ์ปฌ๋Ÿผ (๊ณ ๊ธ‰ ์„ค์ •) */ }

"์ž๋™"์„ ์„ ํƒํ•˜๋ฉด ์ด์ „ ์ปดํฌ๋„ŒํŠธ ๋‹ค์Œ์— ๋ฐฐ์น˜๋ฉ๋‹ˆ๋‹ค

; { /* โœ… ๋†’์ด๋Š” ์œ ์ง€ */ }
{ const newValue = e.target.value; setLocalInputs((prev) => ({ ...prev, height: newValue })); onUpdateProperty("size.height", Number(newValue)); }} className="mt-1" />
; ``` #### 2.2 ์‚ฌ์ด๋“œ๋ฐ” ๋„ˆ๋น„ ์„ค์ • ์ œ๊ฑฐ **์œ„์น˜**: PropertiesPanel.tsx ๋ผ์ธ 1081-1092 **๋ณ€๊ฒฝ ์ „**: ```typescript
{ const value = Number(e.target.value); onUpdateProperty("layoutConfig.sidebarWidth", value); }} className="mt-1" />
``` **๋ณ€๊ฒฝ ํ›„**: ```typescript
``` --- ### Phase 3: ๋ Œ๋”๋ง ๋กœ์ง ์ˆ˜์ • #### 3.1 ContainerComponent ์ˆ˜์ • **ํŒŒ์ผ**: `frontend/components/screen/layout/ContainerComponent.tsx` **๋ณ€๊ฒฝ ์ „**: ```typescript const style: React.CSSProperties = { gridColumn: `span ${component.size.width}`, // โŒ width ์‚ฌ์šฉ minHeight: `${component.size.height}px`, // ... }; ``` **๋ณ€๊ฒฝ ํ›„**: ```typescript const style: React.CSSProperties = { // ๐Ÿ†• gridColumnSpan ์‚ฌ์šฉ gridColumn: component.gridColumnStart ? `${component.gridColumnStart} / span ${ COLUMN_SPAN_VALUES[component.gridColumnSpan] }` : `span ${COLUMN_SPAN_VALUES[component.gridColumnSpan]}`, minHeight: `${component.size.height}px`, // style.width๋Š” ์ œ๊ฑฐ ...(component.style && { // width: component.style.width, โŒ ์ œ๊ฑฐ height: component.style.height, margin: component.style.margin, padding: component.style.padding, // ... ๋‚˜๋จธ์ง€ }), }; ``` #### 3.2 RealtimePreview ์ˆ˜์ • **ํŒŒ์ผ**: `frontend/components/screen/RealtimePreviewDynamic.tsx` **์ถ”๊ฐ€**: ```typescript // ์ปดํฌ๋„ŒํŠธ wrapper์— ๊ทธ๋ฆฌ๋“œ ํด๋ž˜์Šค ์ ์šฉ const gridClasses = useMemo(() => { if (!component.gridColumnSpan) return ""; const spanValue = COLUMN_SPAN_VALUES[component.gridColumnSpan]; const classes = [`col-span-${spanValue}`]; if (component.gridColumnStart) { classes.push(`col-start-${component.gridColumnStart}`); } return classes.join(" "); }, [component.gridColumnSpan, component.gridColumnStart]); return (
{/* ์ปดํฌ๋„ŒํŠธ ๋ Œ๋”๋ง */}
); ``` --- ### Phase 4: StyleEditor ์ˆ˜์ • #### 4.1 width ์Šคํƒ€์ผ ์ œ๊ฑฐ **ํŒŒ์ผ**: `frontend/components/screen/ScreenDesigner.tsx` (๋ผ์ธ 3874-3891) **๋ณ€๊ฒฝ ์ „**: ```typescript // ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ฒฝ์šฐ component.size๋„ ์—…๋ฐ์ดํŠธ if (newStyle.width || newStyle.height) { const width = newStyle.width ? parseInt(newStyle.width.replace("px", "")) : selectedComponent.size.width; const height = newStyle.height ? parseInt(newStyle.height.replace("px", "")) : selectedComponent.size.height; updateComponentProperty(selectedComponent.id, "size.width", width); updateComponentProperty(selectedComponent.id, "size.height", height); } ``` **๋ณ€๊ฒฝ ํ›„**: ```typescript // ๋†’์ด๋งŒ ์—…๋ฐ์ดํŠธ (๋„ˆ๋น„๋Š” gridColumnSpan์œผ๋กœ ์ œ์–ด) if (newStyle.height) { const height = parseInt(newStyle.height.replace("px", "")); updateComponentProperty(selectedComponent.id, "size.height", height); } ``` #### 4.2 StyleEditor ์ปดํฌ๋„ŒํŠธ ์ž์ฒด ์ˆ˜์ • **ํŒŒ์ผ**: `frontend/components/screen/StyleEditor.tsx` (์ถ”์ •) ```typescript // width ๊ด€๋ จ ํƒญ/์ž…๋ ฅ ์ œ๊ฑฐ // โŒ ์ œ๊ฑฐ ๋Œ€์ƒ: // - ๋„ˆ๋น„ ์ž…๋ ฅ ํ•„๋“œ // - min-width, max-width ์„ค์ • // - width ๊ด€๋ จ ๋ชจ๋“  ์Šคํƒ€์ผ ์˜ต์…˜ // โœ… ์œ ์ง€: // - height ์ž…๋ ฅ ํ•„๋“œ // - min-height, max-height ์„ค์ • ``` --- ### Phase 5: ํ…œํ”Œ๋ฆฟ ์‹œ์Šคํ…œ ์ˆ˜์ • #### 5.1 TemplateComponent ํƒ€์ž… ์ˆ˜์ • **ํŒŒ์ผ**: `frontend/components/screen/panels/TemplatesPanel.tsx` **๋ณ€๊ฒฝ ์ „**: ```typescript export interface TemplateComponent { id: string; name: string; description: string; category: string; icon: React.ReactNode; defaultSize: { width: number; height: number }; // โŒ components: Array<{ type: string; size: { width: number; height: number }; // โŒ // ... }>; } ``` **๋ณ€๊ฒฝ ํ›„**: ```typescript export interface TemplateComponent { id: string; name: string; description: string; category: string; icon: React.ReactNode; defaultSize: { height: number }; // โœ… width ์ œ๊ฑฐ components: Array<{ type: string; gridColumnSpan: ColumnSpanPreset; // ๐Ÿ†• ์ถ”๊ฐ€ gridColumnStart?: number; // ๐Ÿ†• ์ถ”๊ฐ€ size: { height: number }; // โœ… width ์ œ๊ฑฐ // ... }>; } ``` #### 5.2 ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ ์ •์˜ ์ˆ˜์ • **์˜ˆ์‹œ - ํผ ํ…œํ”Œ๋ฆฟ**: ```typescript const formTemplates: TemplateComponent[] = [ { id: "basic-form-row", name: "๊ธฐ๋ณธ ํผ ํ–‰", description: "๋ผ๋ฒจ + ์ž…๋ ฅ ํ•„๋“œ", category: "form", icon: , defaultSize: { height: 40 }, components: [ { type: "widget", widgetType: "text", label: "๋ผ๋ฒจ", gridColumnSpan: "label", // 3/12 size: { height: 40 }, position: { x: 0, y: 0 }, }, { type: "widget", widgetType: "text", placeholder: "์ž…๋ ฅํ•˜์„ธ์š”", gridColumnSpan: "input", // 9/12 gridColumnStart: 4, // 4๋ฒˆ ์ปฌ๋Ÿผ๋ถ€ํ„ฐ ์‹œ์ž‘ size: { height: 40 }, position: { x: 0, y: 0 }, }, ], }, { id: "two-column-form", name: "2์ปฌ๋Ÿผ ํผ", description: "2๊ฐœ์˜ ์ž…๋ ฅ ํ•„๋“œ๋ฅผ ๋‚˜๋ž€ํžˆ", category: "form", icon: , defaultSize: { height: 40 }, components: [ { type: "widget", widgetType: "text", placeholder: "์™ผ์ชฝ ์ž…๋ ฅ", gridColumnSpan: "half", // 6/12 size: { height: 40 }, position: { x: 0, y: 0 }, }, { type: "widget", widgetType: "text", placeholder: "์˜ค๋ฅธ์ชฝ ์ž…๋ ฅ", gridColumnSpan: "half", // 6/12 gridColumnStart: 7, // 7๋ฒˆ ์ปฌ๋Ÿผ๋ถ€ํ„ฐ ์‹œ์ž‘ size: { height: 40 }, position: { x: 0, y: 0 }, }, ], }, ]; ``` --- ### Phase 6: ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ #### 6.1 ๊ธฐ์กด ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ํ•จ์ˆ˜ ```typescript // lib/utils/widthToColumnSpan.ts import { ColumnSpanPreset, COLUMN_SPAN_VALUES, } from "@/types/screen-management"; /** * ๊ธฐ์กด ํ”ฝ์…€ width๋ฅผ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ColumnSpanPreset์œผ๋กœ ๋ณ€ํ™˜ */ export function convertWidthToColumnSpan( width: number, canvasWidth: number = 1920 ): ColumnSpanPreset { const percentage = (width / canvasWidth) * 100; // ๊ฐ ํ”„๋ฆฌ์…‹์˜ ๋ฐฑ๋ถ„์œจ ๊ณ„์‚ฐ const presetPercentages: Array<[ColumnSpanPreset, number]> = [ ["full", 100], ["threeQuarters", 75], ["twoThirds", 67], ["half", 50], ["third", 33], ["quarter", 25], ["label", 25], ["input", 75], ["small", 17], ["medium", 33], ["large", 67], ]; // ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฐ’ ์ฐพ๊ธฐ let closestPreset: ColumnSpanPreset = "half"; let minDiff = Infinity; for (const [preset, presetPercentage] of presetPercentages) { const diff = Math.abs(percentage - presetPercentage); if (diff < minDiff) { minDiff = diff; closestPreset = preset; } } return closestPreset; } /** * ์ปดํฌ๋„ŒํŠธ ๋ฐฐ์—ด์—์„œ width๋ฅผ gridColumnSpan์œผ๋กœ ์ผ๊ด„ ๋ณ€ํ™˜ */ export function migrateComponentsToColumnSpan( components: ComponentData[], canvasWidth: number = 1920 ): ComponentData[] { return components.map((component) => { const gridColumnSpan = convertWidthToColumnSpan( component.size.width, canvasWidth ); return { ...component, gridColumnSpan, gridRowIndex: 0, // ์ดˆ๊ธฐ๊ฐ’ (๋‚˜์ค‘์— Y ์ขŒํ‘œ๋กœ ๊ณ„์‚ฐ) size: { height: component.size.height, // width ์ œ๊ฑฐ }, }; }); } /** * Y ์ขŒํ‘œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ–‰ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ */ export function calculateRowIndices( components: ComponentData[] ): ComponentData[] { // Y ์ขŒํ‘œ๋กœ ์ •๋ ฌ const sorted = [...components].sort((a, b) => a.position.y - b.position.y); let currentRowIndex = 0; let currentY = sorted[0]?.position.y ?? 0; const threshold = 50; // 50px ์ฐจ์ด ์ด๋‚ด๋Š” ๊ฐ™์€ ํ–‰ return sorted.map((component) => { if (Math.abs(component.position.y - currentY) > threshold) { currentRowIndex++; currentY = component.position.y; } return { ...component, gridRowIndex: currentRowIndex, }; }); } /** * ์ „์ฒด ๋ ˆ์ด์•„์›ƒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ */ export function migrateLayoutToGridSystem(layout: LayoutData): LayoutData { console.log("๐Ÿ”„ ๋ ˆ์ด์•„์›ƒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ์ž‘:", layout); // 1๋‹จ๊ณ„: width๋ฅผ gridColumnSpan์œผ๋กœ ๋ณ€ํ™˜ let migratedComponents = migrateComponentsToColumnSpan(layout.components); // 2๋‹จ๊ณ„: Y ์ขŒํ‘œ๋กœ ํ–‰ ์ธ๋ฑ์Šค ๊ณ„์‚ฐ migratedComponents = calculateRowIndices(migratedComponents); // 3๋‹จ๊ณ„: ๊ฐ™์€ ํ–‰ ๋‚ด์—์„œ X ์ขŒํ‘œ๋กœ gridColumnStart ๊ณ„์‚ฐ migratedComponents = calculateColumnStarts(migratedComponents); console.log("โœ… ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์™„๋ฃŒ:", migratedComponents); return { ...layout, components: migratedComponents, }; } /** * ๊ฐ™์€ ํ–‰ ๋‚ด์—์„œ X ์ขŒํ‘œ๋กœ ์‹œ์ž‘ ์ปฌ๋Ÿผ ๊ณ„์‚ฐ */ function calculateColumnStarts(components: ComponentData[]): ComponentData[] { // ํ–‰๋ณ„๋กœ ๊ทธ๋ฃนํ™” const rowGroups = new Map(); for (const component of components) { const rowIndex = component.gridRowIndex; if (!rowGroups.has(rowIndex)) { rowGroups.set(rowIndex, []); } rowGroups.get(rowIndex)!.push(component); } // ๊ฐ ํ–‰ ๋‚ด์—์„œ X ์ขŒํ‘œ๋กœ ์ •๋ ฌํ•˜๊ณ  ์‹œ์ž‘ ์ปฌ๋Ÿผ ๊ณ„์‚ฐ const result: ComponentData[] = []; for (const [rowIndex, rowComponents] of rowGroups) { // X ์ขŒํ‘œ๋กœ ์ •๋ ฌ const sorted = rowComponents.sort((a, b) => a.position.x - b.position.x); let currentColumn = 1; for (const component of sorted) { result.push({ ...component, gridColumnStart: currentColumn, }); // ๋‹ค์Œ ์ปดํฌ๋„ŒํŠธ๋Š” ํ˜„์žฌ ์ปดํฌ๋„ŒํŠธ ๋’ค์— ๋ฐฐ์น˜ currentColumn += COLUMN_SPAN_VALUES[component.gridColumnSpan]; } } return result; } ``` #### 6.2 ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰ ```typescript // lib/api/screen.ts ๋˜๋Š” ์ ์ ˆํ•œ ์œ„์น˜ /** * ํ™”๋ฉด ๋กœ๋“œ ์‹œ ์ž๋™์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฒดํฌ ๋ฐ ์‹คํ–‰ */ export async function loadScreenLayoutWithMigration( screenId: number ): Promise { const layout = await screenApi.getLayout(screenId); // ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•„์š” ์—ฌ๋ถ€ ์ฒดํฌ const needsMigration = layout.components.some( (c) => !c.gridColumnSpan || c.size.width !== undefined ); if (needsMigration) { console.log("๐Ÿ”„ ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰:", screenId); const migratedLayout = migrateLayoutToGridSystem(layout); // ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋œ ๋ ˆ์ด์•„์›ƒ ์ €์žฅ await screenApi.saveLayout(screenId, migratedLayout); return migratedLayout; } return layout; } ``` --- ### Phase 7: Tailwind ์„ค์ • ์—…๋ฐ์ดํŠธ #### 7.1 safelist ์ถ”๊ฐ€ ```javascript // tailwind.config.js module.exports = { // ... ๊ธฐ์กด ์„ค์ • safelist: [ // ๊ทธ๋ฆฌ๋“œ ์ปฌ๋Ÿผ ์ŠคํŒฌ (1-12) ...Array.from({ length: 12 }, (_, i) => `col-span-${i + 1}`), // ๊ทธ๋ฆฌ๋“œ ์‹œ์ž‘ ์œ„์น˜ (1-12) ...Array.from({ length: 12 }, (_, i) => `col-start-${i + 1}`), // ๋ฐ˜์‘ํ˜• (ํ•„์š”์‹œ) ...Array.from({ length: 12 }, (_, i) => `md:col-span-${i + 1}`), ...Array.from({ length: 12 }, (_, i) => `lg:col-span-${i + 1}`), ], // ... ๋‚˜๋จธ์ง€ ์„ค์ • }; ``` --- ## ๐Ÿ“‹ ์ˆ˜์ • ํŒŒ์ผ ๋ชฉ๋ก ### ํ•„์ˆ˜ ์ˆ˜์ • ํŒŒ์ผ 1. โœ๏ธ `frontend/types/screen-management.ts` - ํƒ€์ž… ์ •์˜ ์ˆ˜์ • 2. โœ๏ธ `frontend/components/screen/panels/PropertiesPanel.tsx` - width UI ์ œ๊ฑฐ 3. โœ๏ธ `frontend/components/screen/layout/ContainerComponent.tsx` - ๋ Œ๋”๋ง ์ˆ˜์ • 4. โœ๏ธ `frontend/components/screen/layout/ColumnComponent.tsx` - ๋ Œ๋”๋ง ์ˆ˜์ • 5. โœ๏ธ `frontend/components/screen/layout/RowComponent.tsx` - ๋ Œ๋”๋ง ์ˆ˜์ • 6. โœ๏ธ `frontend/components/screen/ScreenDesigner.tsx` - StyleEditor ๋กœ์ง ์ˆ˜์ • 7. โœ๏ธ `frontend/components/screen/StyleEditor.tsx` - width ์˜ต์…˜ ์ œ๊ฑฐ 8. โœ๏ธ `frontend/components/screen/panels/TemplatesPanel.tsx` - ํ…œํ”Œ๋ฆฟ ์ •์˜ ์ˆ˜์ • 9. โœ๏ธ `frontend/components/screen/RealtimePreviewDynamic.tsx` - ๊ทธ๋ฆฌ๋“œ ํด๋ž˜์Šค ์ถ”๊ฐ€ 10. โœ๏ธ `frontend/components/screen/InteractiveScreenViewerDynamic.tsx` - ๊ทธ๋ฆฌ๋“œ ํด๋ž˜์Šค ์ถ”๊ฐ€ ### ์ƒˆ๋กœ ์ƒ์„ฑํ•  ํŒŒ์ผ 11. ๐Ÿ†• `frontend/lib/utils/widthToColumnSpan.ts` - ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ 12. ๐Ÿ†• `frontend/lib/constants/columnSpans.ts` - ์ปฌ๋Ÿผ ์ŠคํŒฌ ์ƒ์ˆ˜ ์ •์˜ ### ์ถ”๊ฐ€ ๊ฒ€ํ†  ํ•„์š” 13. โš ๏ธ `frontend/components/screen/panels/DataTableConfigPanel.tsx` - ๋ชจ๋‹ฌ width 14. โš ๏ธ `frontend/components/screen/panels/DetailSettingsPanel.tsx` - ํ™•์ธ ํ•„์š” 15. โš ๏ธ `frontend/components/screen/FloatingPanel.tsx` - ํŒจ๋„ ์ž์ฒด width๋Š” ์œ ์ง€ --- ## โœ… ๋‹จ๊ณ„๋ณ„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ ### Phase 1: ํƒ€์ž… ์‹œ์Šคํ…œ (Day 1) - [ ] Size ์ธํ„ฐํŽ˜์ด์Šค์—์„œ width ์ œ๊ฑฐ - [ ] BaseComponent์— gridColumnSpan ์ถ”๊ฐ€ - [ ] ColumnSpanPreset ํƒ€์ž… ์ •์˜ - [ ] COLUMN_SPAN_VALUES ์ƒ์ˆ˜ ์ •์˜ ### Phase 2: UI ์ปดํฌ๋„ŒํŠธ (Day 2-3) - [ ] PropertiesPanel - width ์ž…๋ ฅ โ†’ ์ปฌ๋Ÿผ ์ŠคํŒฌ ์„ ํƒ์œผ๋กœ ๋ณ€๊ฒฝ - [ ] PropertiesPanel - ์‹œ๊ฐ์  ํ”„๋ฆฌ๋ทฐ ์ถ”๊ฐ€ - [ ] PropertiesPanel - ์‚ฌ์ด๋“œ๋ฐ” ๋„ˆ๋น„ โ†’ ์ปฌ๋Ÿผ ์ŠคํŒฌ์œผ๋กœ ๋ณ€๊ฒฝ - [ ] StyleEditor - width ์˜ต์…˜ ์™„์ „ ์ œ๊ฑฐ ### Phase 3: ๋ Œ๋”๋ง ๋กœ์ง (Day 3-4) - [ ] ContainerComponent - gridColumn ๊ณ„์‚ฐ ๋กœ์ง ์ˆ˜์ • - [ ] ColumnComponent - gridColumn ๊ณ„์‚ฐ ๋กœ์ง ์ˆ˜์ • - [ ] RowComponent - gridColumn ๊ณ„์‚ฐ ๋กœ์ง ์ˆ˜์ • - [ ] RealtimePreview - ๊ทธ๋ฆฌ๋“œ ํด๋ž˜์Šค ์ ์šฉ - [ ] InteractiveScreenViewer - ๊ทธ๋ฆฌ๋“œ ํด๋ž˜์Šค ์ ์šฉ ### Phase 4: ํ…œํ”Œ๋ฆฟ ์‹œ์Šคํ…œ (Day 4-5) - [ ] TemplateComponent ํƒ€์ž… ์ˆ˜์ • - [ ] ๋ชจ๋“  ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ ์ •์˜ ์—…๋ฐ์ดํŠธ - [ ] ํ…œํ”Œ๋ฆฟ ์ ์šฉ ๋กœ์ง ์ˆ˜์ • ### Phase 5: ๋ฐ์ดํ„ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (Day 5-6) - [ ] widthToColumnSpan ์œ ํ‹ธ๋ฆฌํ‹ฐ ์ž‘์„ฑ - [ ] ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•จ์ˆ˜ ์ž‘์„ฑ - [ ] ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ ์šฉ - [ ] ๊ธฐ์กด ํ™”๋ฉด ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ ํ…Œ์ŠคํŠธ ### Phase 6: ํ…Œ์ŠคํŠธ ๋ฐ ๊ฒ€์ฆ (Day 6-7) - [ ] ์ƒˆ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ํ…Œ์ŠคํŠธ - [ ] ๊ธฐ์กด ํ™”๋ฉด ๋กœ๋“œ ํ…Œ์ŠคํŠธ - [ ] ์ปฌ๋Ÿผ ์ŠคํŒฌ ๋ณ€๊ฒฝ ํ…Œ์ŠคํŠธ - [ ] ํ…œํ”Œ๋ฆฟ ์ ์šฉ ํ…Œ์ŠคํŠธ - [ ] ๋ฐ˜์‘ํ˜• ๋™์ž‘ ํ™•์ธ ### Phase 7: Tailwind ์„ค์ • (Day 7) - [ ] safelist ์ถ”๊ฐ€ - [ ] ๋ถˆํ•„์š”ํ•œ width ๊ด€๋ จ ์œ ํ‹ธ๋ฆฌํ‹ฐ ์ œ๊ฑฐ - [ ] ๋นŒ๋“œ ํ…Œ์ŠคํŠธ --- ## โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ ### 1. ํ˜ธํ™˜์„ฑ ์œ ์ง€ - ๊ธฐ์กด ํ™”๋ฉด ๋ฐ์ดํ„ฐ๋Š” ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ - ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „ ๋ฐฑ์—… ํ•„์ˆ˜ - ๋‹จ๊ณ„์  ๋ฐฐํฌ ๊ถŒ์žฅ ### 2. ๋ชจ๋‹ฌ/ํŒ์—… ํฌ๊ธฐ - ๋ชจ๋‹ฌ ํฌ๊ธฐ๋Š” ์ปฌ๋Ÿผ ์ŠคํŒฌ์ด ์•„๋‹Œ ๊ธฐ์กด ๋ฐฉ์‹ ์œ ์ง€ - `sm`, `md`, `lg`, `xl` ๋“ฑ์˜ ์‚ฌ์ด์ฆˆ ํ”„๋ฆฌ์…‹ ์‚ฌ์šฉ ### 3. FloatingPanel - ํŽธ์ง‘ ํŒจ๋„ ์ž์ฒด์˜ width๋Š” ์œ ์ง€ - ์บ”๋ฒ„์Šค ๋‚ด ์ปดํฌ๋„ŒํŠธ๋งŒ ์ปฌ๋Ÿผ ์ŠคํŒฌ ์ ์šฉ ### 4. ํŠน์ˆ˜ ์ผ€์ด์Šค - ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”: ์ „์ฒด ๋„ˆ๋น„(full) ๊ณ ์ • - ํŒŒ์ผ ์—…๋กœ๋“œ: ์„ค์ •์— ๋”ฐ๋ผ ๋‹ค๋ฆ„ - ๋ฒ„ํŠผ: small, medium, large ํ”„๋ฆฌ์…‹ ์ œ๊ณต --- ## ๐ŸŽฏ ์™„๋ฃŒ ํ›„ ๊ธฐ๋Œ€ ํšจ๊ณผ ### โœ… ๊ฐœ์„ ์  1. **์ผ๊ด€์„ฑ**: ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ 12์ปฌ๋Ÿผ ๊ทธ๋ฆฌ๋“œ ๊ธฐ๋ฐ˜ 2. **๋‹จ์ˆœ์„ฑ**: ๋ณต์žกํ•œ ํ”ฝ์…€ ๊ณ„์‚ฐ ๋ถˆํ•„์š” 3. **๋ฐ˜์‘ํ˜•**: Tailwind ํ‘œ์ค€์œผ๋กœ ์ž๋™ ๋Œ€์‘ 4. **์œ ์ง€๋ณด์ˆ˜**: width ๊ด€๋ จ ๋ฒ„๊ทธ ์™„์ „ ์ œ๊ฑฐ 5. **์„ฑ๋Šฅ**: ๋ถˆํ•„์š”ํ•œ ๊ณ„์‚ฐ ๋กœ์ง ์ œ๊ฑฐ ### โŒ ์ œ๊ฑฐ๋˜๋Š” ๊ธฐ๋Šฅ - ํ”ฝ์…€ ๋‹จ์œ„ ์ •๋ฐ€ ๋„ˆ๋น„ ์กฐ์ • - ์ž์œ ๋กœ์šด width ์ž…๋ ฅ - ์ปค์Šคํ…€ width ์„ค์ • ### ๐Ÿ”„ ๋Œ€์ฒด ๋ฐฉ์•ˆ - ์ •๋ฐ€ ์กฐ์ • ํ•„์š” ์‹œ โ†’ ์ปฌ๋Ÿผ ์ŠคํŒฌ ์กฐํ•ฉ ์‚ฌ์šฉ - ํŠน์ˆ˜ ์ผ€์ด์Šค โ†’ ์ปค์Šคํ…€ CSS ํด๋ž˜์Šค ์ถ”๊ฐ€ --- ## ๐Ÿ“Š ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํƒ€์ž„๋ผ์ธ ``` Week 1: - Day 1-2: ํƒ€์ž… ์‹œ์Šคํ…œ ๋ฐ UI ์ปดํฌ๋„ŒํŠธ ์ˆ˜์ • - Day 3-4: ๋ Œ๋”๋ง ๋กœ์ง ์ˆ˜์ • - Day 5-6: ํ…œํ”Œ๋ฆฟ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ - Day 7: ํ…Œ์ŠคํŠธ ๋ฐ Tailwind ์„ค์ • Week 2: - ์ „์ฒด ์‹œ์Šคํ…œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ - ๊ธฐ์กด ํ™”๋ฉด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ - ๋ฌธ์„œํ™” ๋ฐ ๋ฐฐํฌ ``` --- ์ด ๊ณ„ํš์„ ๋”ฐ๋ฅด๋ฉด **width ์†์„ฑ์„ ์™„์ „ํžˆ ์ œ๊ฑฐ**ํ•˜๊ณ  **์ปฌ๋Ÿผ ์ˆ˜๋กœ๋งŒ ์ œ์–ด**ํ•˜๋Š” ๊น”๋”ํ•œ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๐ŸŽฏ