645 lines
18 KiB
Markdown
645 lines
18 KiB
Markdown
# 반응형 그리드 시스템 아키텍처
|
|
|
|
> 최종 업데이트: 2026-01-30
|
|
|
|
## 1. 개요
|
|
|
|
### 1.1 문제 정의
|
|
|
|
**현재 상황**: 컴포넌트 위치/크기가 픽셀 단위로 고정되어 반응형 미지원
|
|
|
|
```json
|
|
// 현재 저장 방식 (screen_layouts_v2.layout_data)
|
|
{
|
|
"position": { "x": 1753, "y": 88 },
|
|
"size": { "width": 158, "height": 40 }
|
|
}
|
|
```
|
|
|
|
**발생 문제**:
|
|
- 1920px 기준 설계 → 1280px 화면에서 버튼이 화면 밖으로 나감
|
|
- 모바일/태블릿에서 레이아웃 완전히 깨짐
|
|
- 화면 축소해도 컴포넌트 위치/크기 그대로
|
|
|
|
### 1.2 목표
|
|
|
|
| 목표 | 설명 |
|
|
|------|------|
|
|
| **PC 대응** | 1280px ~ 1920px 화면에서 정상 동작 |
|
|
| **태블릿 대응** | 768px ~ 1024px 화면에서 레이아웃 재배치 |
|
|
| **모바일 대응** | 320px ~ 767px 화면에서 세로 스택 |
|
|
| **shadcn/Tailwind 활용** | 반응형 브레이크포인트 시스템 사용 |
|
|
|
|
### 1.3 핵심 원칙
|
|
|
|
```
|
|
현재: 픽셀 좌표 → position: absolute → 고정 레이아웃
|
|
변경: 그리드 셀 번호 → CSS Grid + Tailwind → 반응형 레이아웃
|
|
```
|
|
|
|
---
|
|
|
|
## 2. 현재 시스템 분석
|
|
|
|
### 2.1 기존 그리드 설정 (이미 존재)
|
|
|
|
```typescript
|
|
// frontend/components/screen/ScreenDesigner.tsx
|
|
gridSettings: {
|
|
columns: 12, // ✅ 이미 12컬럼 그리드 있음
|
|
gap: 16, // ✅ 간격 설정 있음
|
|
padding: 0,
|
|
snapToGrid: true, // ✅ 스냅 기능 있음
|
|
showGrid: false,
|
|
gridColor: "#d1d5db",
|
|
gridOpacity: 0.5,
|
|
}
|
|
```
|
|
|
|
### 2.2 현재 저장 방식
|
|
|
|
```typescript
|
|
// 드래그 후 저장되는 데이터
|
|
{
|
|
"id": "comp_1896",
|
|
"url": "@/lib/registry/components/v2-button-primary",
|
|
"position": { "x": 1753.33, "y": 88, "z": 1 }, // 픽셀 좌표
|
|
"size": { "width": 158.67, "height": 40 }, // 픽셀 크기
|
|
"overrides": { ... }
|
|
}
|
|
```
|
|
|
|
### 2.3 현재 렌더링 방식
|
|
|
|
```tsx
|
|
// frontend/lib/registry/layouts/flexbox/FlexboxLayout.tsx (라인 234-248)
|
|
<div style={{
|
|
position: "absolute",
|
|
left: child.position?.x || 0, // 픽셀 절대 위치
|
|
top: child.position?.y || 0,
|
|
width: child.size?.width || "auto",
|
|
height: child.size?.height || "auto",
|
|
zIndex: child.position?.z || 1,
|
|
}}>
|
|
```
|
|
|
|
### 2.4 문제점 요약
|
|
|
|
| 현재 | 문제 |
|
|
|------|------|
|
|
| 12컬럼 그리드 있음 | 스냅용으로만 사용, 저장은 픽셀 |
|
|
| position: 픽셀 좌표 | 화면 크기 변해도 위치 고정 |
|
|
| size: 픽셀 크기 | 화면 작아지면 넘침 |
|
|
| absolute 포지션 | 반응형 불가 |
|
|
|
|
---
|
|
|
|
## 3. 신규 데이터 구조
|
|
|
|
### 3.1 layout_data 구조 변경
|
|
|
|
**현재 구조**:
|
|
```json
|
|
{
|
|
"version": "2.0",
|
|
"components": [{
|
|
"id": "comp_xxx",
|
|
"url": "@/lib/registry/components/v2-button-primary",
|
|
"position": { "x": 1753, "y": 88, "z": 1 },
|
|
"size": { "width": 158, "height": 40 },
|
|
"overrides": { ... }
|
|
}]
|
|
}
|
|
```
|
|
|
|
**변경 후 구조**:
|
|
```json
|
|
{
|
|
"version": "3.0",
|
|
"layoutMode": "grid",
|
|
"components": [{
|
|
"id": "comp_xxx",
|
|
"url": "@/lib/registry/components/v2-button-primary",
|
|
"grid": {
|
|
"col": 11,
|
|
"row": 2,
|
|
"colSpan": 2,
|
|
"rowSpan": 1
|
|
},
|
|
"responsive": {
|
|
"sm": { "col": 1, "colSpan": 12 },
|
|
"md": { "col": 7, "colSpan": 6 },
|
|
"lg": { "col": 11, "colSpan": 2 }
|
|
},
|
|
"overrides": { ... }
|
|
}],
|
|
"gridSettings": {
|
|
"columns": 12,
|
|
"rowHeight": 80,
|
|
"gap": 16
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.2 필드 설명
|
|
|
|
| 필드 | 타입 | 설명 |
|
|
|------|------|------|
|
|
| `version` | string | "3.0" (반응형 그리드 버전) |
|
|
| `layoutMode` | string | "grid" (그리드 레이아웃 사용) |
|
|
| `grid.col` | number | 시작 컬럼 (1-12) |
|
|
| `grid.row` | number | 시작 행 (1부터) |
|
|
| `grid.colSpan` | number | 차지하는 컬럼 수 |
|
|
| `grid.rowSpan` | number | 차지하는 행 수 |
|
|
| `responsive.sm` | object | 모바일 (< 768px) 설정 |
|
|
| `responsive.md` | object | 태블릿 (768px ~ 1024px) 설정 |
|
|
| `responsive.lg` | object | 데스크톱 (> 1024px) 설정 |
|
|
|
|
### 3.3 반응형 브레이크포인트
|
|
|
|
| 브레이크포인트 | 화면 크기 | 기본 동작 |
|
|
|----------------|-----------|-----------|
|
|
| `sm` | < 768px | 모든 컴포넌트 12컬럼 (세로 스택) |
|
|
| `md` | 768px ~ 1024px | 컬럼 수 2배로 확장 |
|
|
| `lg` | > 1024px | 원본 그리드 위치 유지 |
|
|
|
|
---
|
|
|
|
## 4. 변환 로직
|
|
|
|
### 4.1 픽셀 → 그리드 변환 함수
|
|
|
|
```typescript
|
|
// frontend/lib/utils/gridConverter.ts
|
|
|
|
const DESIGN_WIDTH = 1920;
|
|
const COLUMNS = 12;
|
|
const COLUMN_WIDTH = DESIGN_WIDTH / COLUMNS; // 160px
|
|
const ROW_HEIGHT = 80;
|
|
|
|
interface PixelPosition {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
interface PixelSize {
|
|
width: number;
|
|
height: number;
|
|
}
|
|
|
|
interface GridPosition {
|
|
col: number;
|
|
row: number;
|
|
colSpan: number;
|
|
rowSpan: number;
|
|
}
|
|
|
|
interface ResponsiveConfig {
|
|
sm: { col: number; colSpan: number };
|
|
md: { col: number; colSpan: number };
|
|
lg: { col: number; colSpan: number };
|
|
}
|
|
|
|
/**
|
|
* 픽셀 좌표를 그리드 셀 번호로 변환
|
|
*/
|
|
export function pixelToGrid(
|
|
position: PixelPosition,
|
|
size: PixelSize
|
|
): GridPosition {
|
|
// 컬럼 계산 (1-based)
|
|
const col = Math.max(1, Math.min(12, Math.round(position.x / COLUMN_WIDTH) + 1));
|
|
|
|
// 행 계산 (1-based)
|
|
const row = Math.max(1, Math.round(position.y / ROW_HEIGHT) + 1);
|
|
|
|
// 컬럼 스팬 계산
|
|
const colSpan = Math.max(1, Math.min(12 - col + 1, Math.round(size.width / COLUMN_WIDTH)));
|
|
|
|
// 행 스팬 계산
|
|
const rowSpan = Math.max(1, Math.round(size.height / ROW_HEIGHT));
|
|
|
|
return { col, row, colSpan, rowSpan };
|
|
}
|
|
|
|
/**
|
|
* 그리드 셀 번호를 픽셀 좌표로 변환 (디자인 모드용)
|
|
*/
|
|
export function gridToPixel(
|
|
grid: GridPosition
|
|
): { position: PixelPosition; size: PixelSize } {
|
|
return {
|
|
position: {
|
|
x: (grid.col - 1) * COLUMN_WIDTH,
|
|
y: (grid.row - 1) * ROW_HEIGHT,
|
|
},
|
|
size: {
|
|
width: grid.colSpan * COLUMN_WIDTH,
|
|
height: grid.rowSpan * ROW_HEIGHT,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 기본 반응형 설정 생성
|
|
*/
|
|
export function getDefaultResponsive(
|
|
grid: GridPosition
|
|
): ResponsiveConfig {
|
|
return {
|
|
// 모바일: 전체 너비, 원래 순서대로 스택
|
|
sm: {
|
|
col: 1,
|
|
colSpan: 12
|
|
},
|
|
// 태블릿: 컬럼 스팬 2배 (최대 12)
|
|
md: {
|
|
col: Math.max(1, Math.round((grid.col - 1) / 2) + 1),
|
|
colSpan: Math.min(grid.colSpan * 2, 12)
|
|
},
|
|
// 데스크톱: 원본 유지
|
|
lg: {
|
|
col: grid.col,
|
|
colSpan: grid.colSpan
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
### 4.2 Tailwind 클래스 생성 함수
|
|
|
|
```typescript
|
|
// frontend/lib/utils/gridClassGenerator.ts
|
|
|
|
/**
|
|
* 그리드 위치/크기를 Tailwind 클래스로 변환
|
|
*/
|
|
export function generateGridClasses(
|
|
grid: GridPosition,
|
|
responsive: ResponsiveConfig
|
|
): string {
|
|
const classes: string[] = [];
|
|
|
|
// 모바일 (기본)
|
|
classes.push(`col-start-${responsive.sm.col}`);
|
|
classes.push(`col-span-${responsive.sm.colSpan}`);
|
|
|
|
// 태블릿
|
|
classes.push(`md:col-start-${responsive.md.col}`);
|
|
classes.push(`md:col-span-${responsive.md.colSpan}`);
|
|
|
|
// 데스크톱
|
|
classes.push(`lg:col-start-${responsive.lg.col}`);
|
|
classes.push(`lg:col-span-${responsive.lg.colSpan}`);
|
|
|
|
return classes.join(' ');
|
|
}
|
|
```
|
|
|
|
**주의**: Tailwind는 빌드 타임에 클래스를 결정하므로, 동적 클래스 생성 시 safelist 설정 필요
|
|
|
|
```javascript
|
|
// tailwind.config.js
|
|
module.exports = {
|
|
safelist: [
|
|
// 그리드 컬럼 시작
|
|
{ pattern: /col-start-(1|2|3|4|5|6|7|8|9|10|11|12)/ },
|
|
{ pattern: /md:col-start-(1|2|3|4|5|6|7|8|9|10|11|12)/ },
|
|
{ pattern: /lg:col-start-(1|2|3|4|5|6|7|8|9|10|11|12)/ },
|
|
// 그리드 컬럼 스팬
|
|
{ pattern: /col-span-(1|2|3|4|5|6|7|8|9|10|11|12)/ },
|
|
{ pattern: /md:col-span-(1|2|3|4|5|6|7|8|9|10|11|12)/ },
|
|
{ pattern: /lg:col-span-(1|2|3|4|5|6|7|8|9|10|11|12)/ },
|
|
],
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 렌더링 컴포넌트 수정
|
|
|
|
### 5.1 ResponsiveGridLayout 컴포넌트
|
|
|
|
```tsx
|
|
// frontend/lib/registry/layouts/responsive-grid/ResponsiveGridLayout.tsx
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { generateGridClasses } from "@/lib/utils/gridClassGenerator";
|
|
|
|
interface ResponsiveGridLayoutProps {
|
|
layout: LayoutData;
|
|
isDesignMode: boolean;
|
|
renderer: ComponentRenderer;
|
|
}
|
|
|
|
export function ResponsiveGridLayout({
|
|
layout,
|
|
isDesignMode,
|
|
renderer,
|
|
}: ResponsiveGridLayoutProps) {
|
|
const { gridSettings, components } = layout;
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"grid grid-cols-12",
|
|
`gap-${gridSettings?.gap || 4}`,
|
|
isDesignMode && "min-h-[600px] border border-dashed"
|
|
)}
|
|
style={{
|
|
gridAutoRows: `${gridSettings?.rowHeight || 80}px`,
|
|
}}
|
|
>
|
|
{components
|
|
.sort((a, b) => (a.grid?.row || 0) - (b.grid?.row || 0))
|
|
.map((component) => {
|
|
const gridClasses = generateGridClasses(
|
|
component.grid,
|
|
component.responsive
|
|
);
|
|
|
|
return (
|
|
<div
|
|
key={component.id}
|
|
className={cn(
|
|
gridClasses,
|
|
`row-span-${component.grid?.rowSpan || 1}`,
|
|
isDesignMode && "border border-blue-200 hover:border-blue-400"
|
|
)}
|
|
>
|
|
{renderer.renderChild(component)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 5.2 렌더링 결과 예시
|
|
|
|
**데스크톱 (lg: 1024px+)**:
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ [분리] [저장] [수정] [삭제] │ ← 버튼들 오른쪽 정렬
|
|
├─────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ 테이블 컴포넌트 │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**태블릿 (md: 768px ~ 1024px)**:
|
|
```
|
|
┌───────────────────────────────┐
|
|
│ [분리] [저장] [수정] [삭제] │ ← 버튼들 2개씩
|
|
├───────────────────────────────┤
|
|
│ │
|
|
│ 테이블 컴포넌트 │
|
|
│ │
|
|
└───────────────────────────────┘
|
|
```
|
|
|
|
**모바일 (sm: < 768px)**:
|
|
```
|
|
┌─────────────────┐
|
|
│ [분리] │
|
|
│ [저장] │
|
|
│ [수정] │ ← 세로 스택
|
|
│ [삭제] │
|
|
├─────────────────┤
|
|
│ 테이블 컴포넌트 │
|
|
│ (스크롤) │
|
|
└─────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 6. 마이그레이션 계획
|
|
|
|
### 6.1 데이터 마이그레이션 스크립트
|
|
|
|
```sql
|
|
-- 기존 데이터를 V3 구조로 변환하는 함수
|
|
CREATE OR REPLACE FUNCTION migrate_layout_to_v3(layout_data JSONB)
|
|
RETURNS JSONB AS $$
|
|
DECLARE
|
|
result JSONB;
|
|
component JSONB;
|
|
new_components JSONB := '[]'::JSONB;
|
|
grid_col INT;
|
|
grid_row INT;
|
|
col_span INT;
|
|
row_span INT;
|
|
BEGIN
|
|
-- 각 컴포넌트 변환
|
|
FOR component IN SELECT * FROM jsonb_array_elements(layout_data->'components')
|
|
LOOP
|
|
-- 픽셀 → 그리드 변환 (160px = 1컬럼, 80px = 1행)
|
|
grid_col := GREATEST(1, LEAST(12, ROUND((component->'position'->>'x')::NUMERIC / 160) + 1));
|
|
grid_row := GREATEST(1, ROUND((component->'position'->>'y')::NUMERIC / 80) + 1);
|
|
col_span := GREATEST(1, LEAST(13 - grid_col, ROUND((component->'size'->>'width')::NUMERIC / 160)));
|
|
row_span := GREATEST(1, ROUND((component->'size'->>'height')::NUMERIC / 80));
|
|
|
|
-- 새 컴포넌트 구조 생성
|
|
component := component || jsonb_build_object(
|
|
'grid', jsonb_build_object(
|
|
'col', grid_col,
|
|
'row', grid_row,
|
|
'colSpan', col_span,
|
|
'rowSpan', row_span
|
|
),
|
|
'responsive', jsonb_build_object(
|
|
'sm', jsonb_build_object('col', 1, 'colSpan', 12),
|
|
'md', jsonb_build_object('col', GREATEST(1, ROUND(grid_col / 2.0)), 'colSpan', LEAST(col_span * 2, 12)),
|
|
'lg', jsonb_build_object('col', grid_col, 'colSpan', col_span)
|
|
)
|
|
);
|
|
|
|
-- position, size 필드 제거 (선택사항 - 호환성 위해 유지 가능)
|
|
-- component := component - 'position' - 'size';
|
|
|
|
new_components := new_components || component;
|
|
END LOOP;
|
|
|
|
-- 결과 생성
|
|
result := jsonb_build_object(
|
|
'version', '3.0',
|
|
'layoutMode', 'grid',
|
|
'components', new_components,
|
|
'gridSettings', COALESCE(layout_data->'gridSettings', '{"columns": 12, "rowHeight": 80, "gap": 16}'::JSONB)
|
|
);
|
|
|
|
RETURN result;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- 마이그레이션 실행
|
|
UPDATE screen_layouts_v2
|
|
SET layout_data = migrate_layout_to_v3(layout_data)
|
|
WHERE (layout_data->>'version') = '2.0';
|
|
```
|
|
|
|
### 6.2 백워드 호환성
|
|
|
|
V2 ↔ V3 호환을 위한 변환 레이어:
|
|
|
|
```typescript
|
|
// frontend/lib/utils/layoutVersionConverter.ts
|
|
|
|
export function normalizeLayout(layout: any): NormalizedLayout {
|
|
const version = layout.version || "2.0";
|
|
|
|
if (version === "2.0") {
|
|
// V2 → V3 변환 (렌더링 시)
|
|
return {
|
|
...layout,
|
|
version: "3.0",
|
|
layoutMode: "grid",
|
|
components: layout.components.map((comp: any) => ({
|
|
...comp,
|
|
grid: pixelToGrid(comp.position, comp.size),
|
|
responsive: getDefaultResponsive(pixelToGrid(comp.position, comp.size)),
|
|
})),
|
|
};
|
|
}
|
|
|
|
return layout; // V3는 그대로
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 7. 디자인 모드 수정
|
|
|
|
### 7.1 그리드 편집 UI
|
|
|
|
디자인 모드에서 그리드 셀 선택 방식 추가:
|
|
|
|
```tsx
|
|
// 기존: 픽셀 좌표 입력
|
|
<Input
|
|
label="X 좌표"
|
|
value={position.x}
|
|
onChange={(x) => updatePosition({ x })}
|
|
/>
|
|
|
|
// 변경: 그리드 셀 선택
|
|
<div className="grid grid-cols-12 gap-1 p-2 bg-gray-50 rounded">
|
|
{Array.from({ length: 12 }).map((_, col) => (
|
|
<div
|
|
key={col}
|
|
className={cn(
|
|
"h-8 border cursor-pointer hover:bg-blue-100",
|
|
selected.col === col + 1 && "bg-blue-300"
|
|
)}
|
|
onClick={() => setGridCol(col + 1)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex gap-4">
|
|
<Select label="컬럼 시작" value={grid.col} options={[1,2,3,4,5,6,7,8,9,10,11,12]} />
|
|
<Select label="컬럼 스팬" value={grid.colSpan} options={[1,2,3,4,5,6,7,8,9,10,11,12]} />
|
|
</div>
|
|
```
|
|
|
|
### 7.2 반응형 미리보기
|
|
|
|
```tsx
|
|
// 화면 크기 미리보기 버튼
|
|
<div className="flex gap-2">
|
|
<Button onClick={() => setPreviewMode("sm")} icon={<Smartphone />}>
|
|
모바일
|
|
</Button>
|
|
<Button onClick={() => setPreviewMode("md")} icon={<Tablet />}>
|
|
태블릿
|
|
</Button>
|
|
<Button onClick={() => setPreviewMode("lg")} icon={<Monitor />}>
|
|
데스크톱
|
|
</Button>
|
|
</div>
|
|
|
|
// 미리보기 컨테이너
|
|
<div className={cn(
|
|
"mx-auto transition-all",
|
|
previewMode === "sm" && "max-w-[375px]",
|
|
previewMode === "md" && "max-w-[768px]",
|
|
previewMode === "lg" && "max-w-full"
|
|
)}>
|
|
<ResponsiveGridLayout layout={layout} />
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 8. 작업 목록
|
|
|
|
### Phase 1: 핵심 유틸리티 (1일)
|
|
|
|
| 작업 | 파일 | 상태 |
|
|
|------|------|------|
|
|
| 그리드 변환 함수 | `lib/utils/gridConverter.ts` | ⬜ |
|
|
| 클래스 생성 함수 | `lib/utils/gridClassGenerator.ts` | ⬜ |
|
|
| Tailwind safelist 설정 | `tailwind.config.js` | ⬜ |
|
|
|
|
### Phase 2: 렌더링 수정 (1일)
|
|
|
|
| 작업 | 파일 | 상태 |
|
|
|------|------|------|
|
|
| ResponsiveGridLayout 생성 | `lib/registry/layouts/responsive-grid/` | ⬜ |
|
|
| 레이아웃 버전 분기 처리 | `lib/registry/DynamicComponentRenderer.tsx` | ⬜ |
|
|
|
|
### Phase 3: 저장 로직 수정 (1일)
|
|
|
|
| 작업 | 파일 | 상태 |
|
|
|------|------|------|
|
|
| 저장 시 그리드 변환 | `components/screen/ScreenDesigner.tsx` | ⬜ |
|
|
| V3 레이아웃 변환기 | `lib/utils/layoutV3Converter.ts` | ⬜ |
|
|
|
|
### Phase 4: 디자인 모드 UI (1일)
|
|
|
|
| 작업 | 파일 | 상태 |
|
|
|------|------|------|
|
|
| 그리드 셀 편집 UI | `components/screen/panels/V2PropertiesPanel.tsx` | ⬜ |
|
|
| 반응형 미리보기 | `components/screen/ScreenDesigner.tsx` | ⬜ |
|
|
|
|
### Phase 5: 마이그레이션 (0.5일)
|
|
|
|
| 작업 | 파일 | 상태 |
|
|
|------|------|------|
|
|
| 마이그레이션 스크립트 | `db/migrations/xxx_migrate_to_v3.sql` | ⬜ |
|
|
| 백워드 호환성 테스트 | - | ⬜ |
|
|
|
|
---
|
|
|
|
## 9. 예상 일정
|
|
|
|
| 단계 | 기간 | 완료 기준 |
|
|
|------|------|-----------|
|
|
| Phase 1 | 1일 | 유틸리티 함수 테스트 통과 |
|
|
| Phase 2 | 1일 | 그리드 렌더링 정상 동작 |
|
|
| Phase 3 | 1일 | 저장/로드 정상 동작 |
|
|
| Phase 4 | 1일 | 디자인 모드 UI 완성 |
|
|
| Phase 5 | 0.5일 | 기존 데이터 마이그레이션 완료 |
|
|
| 테스트 | 0.5일 | 모든 화면 반응형 테스트 |
|
|
| **합계** | **5일** | |
|
|
|
|
---
|
|
|
|
## 10. 리스크 및 대응
|
|
|
|
| 리스크 | 영향 | 대응 방안 |
|
|
|--------|------|-----------|
|
|
| 기존 레이아웃 깨짐 | 높음 | position/size 필드 유지하여 폴백 |
|
|
| Tailwind 동적 클래스 | 중간 | safelist로 모든 클래스 사전 정의 |
|
|
| 디자인 모드 혼란 | 낮음 | 그리드 가이드라인 시각화 |
|
|
|
|
---
|
|
|
|
## 11. 참고 자료
|
|
|
|
- [COMPONENT_LAYOUT_V2_ARCHITECTURE.md](./COMPONENT_LAYOUT_V2_ARCHITECTURE.md) - V2 아키텍처
|
|
- [Tailwind CSS Grid](https://tailwindcss.com/docs/grid-template-columns) - 그리드 시스템
|
|
- [shadcn/ui](https://ui.shadcn.com/) - 컴포넌트 라이브러리
|