tailwind-grid-layout 1.0.1 → 1.0.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +171 -505
- package/README.md +171 -502
- package/dist/components/DroppableGridContainer.d.ts.map +1 -1
- package/dist/components/GridContainer.d.ts.map +1 -1
- package/dist/components/GridItem.d.ts +1 -0
- package/dist/components/GridItem.d.ts.map +1 -1
- package/dist/components/ResizeHandle.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/useDrag.d.ts +27 -0
- package/dist/hooks/useDrag.d.ts.map +1 -0
- package/dist/hooks/useResize.d.ts +22 -0
- package/dist/hooks/useResize.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +640 -341
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +8 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/throttle.d.ts +2 -0
- package/dist/utils/throttle.d.ts.map +1 -0
- package/package.json +6 -14
package/README.ko.md
CHANGED
|
@@ -1,606 +1,272 @@
|
|
|
1
1
|
# Tailwind Grid Layout
|
|
2
2
|
|
|
3
|
-
React용 현대적이고 가벼운 그리드 레이아웃
|
|
3
|
+
React용 현대적이고 가벼운 그리드 레이아웃 시스템. Tailwind CSS로 구축되었으며, react-grid-layout의 강력한 대안입니다.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/tailwind-grid-layout)
|
|
6
6
|
[](https://github.com/Seungwoo321/tailwind-grid-layout/blob/main/LICENSE)
|
|
7
7
|
[](https://bundlephobia.com/package/tailwind-grid-layout)
|
|
8
8
|
|
|
9
|
-
>
|
|
9
|
+
> [Live Demo](https://seungwoo321.github.io/tailwind-grid-layout/) | [Storybook](https://seungwoo321.github.io/tailwind-grid-layout/storybook)
|
|
10
10
|
|
|
11
11
|
> [English](./README.md) | 한국어
|
|
12
12
|
|
|
13
|
+

|
|
14
|
+
|
|
13
15
|
## 특징
|
|
14
16
|
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- 스크롤과 드래그 충돌 방지
|
|
23
|
-
- 멀티터치 방지로 안정성 확보
|
|
24
|
-
- 🔧 **TypeScript** - 완전한 TypeScript 지원
|
|
25
|
-
- ⚡ **고성능** - 최적화된 렌더링과 애니메이션
|
|
26
|
-
- 🧪 **철저한 테스트** - 100% 테스트 커버리지
|
|
17
|
+
- **react-grid-layout과 완벽한 기능 호환**
|
|
18
|
+
- **경량화** - 더 작은 번들 크기 (~15KB gzip)
|
|
19
|
+
- **Tailwind 네이티브** - Tailwind CSS v4 기반
|
|
20
|
+
- **반응형** - 모든 화면 크기 지원
|
|
21
|
+
- **모바일 터치** - 터치 디바이스 완전 지원
|
|
22
|
+
- **TypeScript** - 완벽한 타입 지원
|
|
23
|
+
- **철저한 테스트** - 100% 테스트 커버리지
|
|
27
24
|
|
|
28
25
|
## 설치
|
|
29
26
|
|
|
30
27
|
```bash
|
|
31
28
|
npm install tailwind-grid-layout
|
|
32
|
-
# 또는
|
|
33
|
-
yarn add tailwind-grid-layout
|
|
34
|
-
# 또는
|
|
35
|
-
pnpm add tailwind-grid-layout
|
|
36
29
|
```
|
|
37
30
|
|
|
38
|
-
###
|
|
39
|
-
|
|
40
|
-
- React 19.1.0
|
|
41
|
-
- Tailwind CSS 4.1.8+ (v4 전용 - CSS 우선 구성)
|
|
42
|
-
- Node.js 20.0.0+
|
|
43
|
-
- pnpm 10.11.0+
|
|
44
|
-
|
|
45
|
-
## Tailwind CSS v4 설정
|
|
31
|
+
### 요구사항
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```css
|
|
50
|
-
/* 메인 CSS 파일에서 */
|
|
51
|
-
@import "tailwindcss";
|
|
52
|
-
|
|
53
|
-
/* 선택사항: 커스텀 테마 구성 추가 */
|
|
54
|
-
@theme {
|
|
55
|
-
--color-grid-placeholder: oklch(0.7 0.15 210);
|
|
56
|
-
--color-grid-handle: oklch(0.3 0.05 210);
|
|
57
|
-
}
|
|
58
|
-
```
|
|
33
|
+
- React 18+
|
|
34
|
+
- Tailwind CSS 4.x
|
|
59
35
|
|
|
60
36
|
## 빠른 시작
|
|
61
37
|
|
|
62
38
|
```tsx
|
|
39
|
+
import { useState } from 'react'
|
|
63
40
|
import { GridContainer } from 'tailwind-grid-layout'
|
|
41
|
+
import type { GridItem } from 'tailwind-grid-layout'
|
|
64
42
|
|
|
65
|
-
const
|
|
66
|
-
{ id: '1', x: 0, y: 0, w:
|
|
67
|
-
{ id: '2', x:
|
|
68
|
-
{ id: '3', x:
|
|
43
|
+
const initialItems: GridItem[] = [
|
|
44
|
+
{ id: '1', x: 0, y: 0, w: 3, h: 2 },
|
|
45
|
+
{ id: '2', x: 3, y: 0, w: 5, h: 3 },
|
|
46
|
+
{ id: '3', x: 8, y: 0, w: 4, h: 2 },
|
|
69
47
|
]
|
|
70
48
|
|
|
71
49
|
function App() {
|
|
50
|
+
const [items, setItems] = useState(initialItems)
|
|
51
|
+
|
|
72
52
|
return (
|
|
73
53
|
<GridContainer
|
|
74
54
|
items={items}
|
|
75
55
|
cols={12}
|
|
76
|
-
rowHeight={
|
|
77
|
-
|
|
56
|
+
rowHeight={80}
|
|
57
|
+
gap={16}
|
|
58
|
+
onLayoutChange={setItems}
|
|
78
59
|
>
|
|
79
60
|
{(item) => (
|
|
80
|
-
<div className="bg-
|
|
81
|
-
Item {item.id}
|
|
82
|
-
</div>
|
|
83
|
-
)}
|
|
84
|
-
</GridContainer>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## 테스트
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
# 테스트 실행
|
|
93
|
-
pnpm test
|
|
94
|
-
|
|
95
|
-
# 테스트 감시 모드
|
|
96
|
-
pnpm test:watch
|
|
97
|
-
|
|
98
|
-
# 테스트 커버리지 리포트
|
|
99
|
-
pnpm test:coverage
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### 테스트 커버리지
|
|
103
|
-
|
|
104
|
-
이 라이브러리는 100% 테스트 커버리지를 유지하고 있습니다:
|
|
105
|
-
|
|
106
|
-
- ✅ Lines: 100%
|
|
107
|
-
- ✅ Statements: 100%
|
|
108
|
-
- ✅ Functions: 100%
|
|
109
|
-
- ✅ Branches: 100%
|
|
110
|
-
|
|
111
|
-
## Props 참조
|
|
112
|
-
|
|
113
|
-
### GridContainer Props
|
|
114
|
-
|
|
115
|
-
| Prop | 타입 | 기본값 | 설명 |
|
|
116
|
-
|------|------|---------|-------------|
|
|
117
|
-
| **items** | `GridItem[]` | 필수 | 위치와 크기 정보를 포함한 그리드 아이템 배열 |
|
|
118
|
-
| **children** | `(item: GridItem) => ReactNode` | 필수 | 그리드 아이템을 위한 렌더 함수 |
|
|
119
|
-
| **cols** | `number` | `12` | 그리드의 열 개수 |
|
|
120
|
-
| **rowHeight** | `number` | `60` | 각 행의 높이 (픽셀) |
|
|
121
|
-
| **gap** | `number` | `16` | 그리드 아이템 간 간격 (픽셀) |
|
|
122
|
-
| **margin** | `[number, number]` | `[gap, gap]` | 아이템 간 여백 [수평, 수직] |
|
|
123
|
-
| **containerPadding** | `[number, number]` | `[16, 16]` | 그리드 컨테이너 내부 패딩 [수평, 수직] |
|
|
124
|
-
| **maxRows** | `number` | - | 최대 행 개수 |
|
|
125
|
-
| **isDraggable** | `boolean` | `true` | 드래그 활성화/비활성화 |
|
|
126
|
-
| **isResizable** | `boolean` | `true` | 크기 조정 활성화/비활성화 |
|
|
127
|
-
| **preventCollision** | `boolean` | `false` | 아이템 충돌 방지 |
|
|
128
|
-
| **allowOverlap** | `boolean` | `false` | 아이템 겹침 허용 |
|
|
129
|
-
| **isBounded** | `boolean` | `true` | 컨테이너 경계 내 아이템 유지 |
|
|
130
|
-
| **compactType** | `'vertical' \| 'horizontal' \| null` | `'vertical'` | 압축 타입 |
|
|
131
|
-
| **resizeHandles** | `Array<'s' \| 'w' \| 'e' \| 'n' \| 'sw' \| 'nw' \| 'se' \| 'ne'>` | `['se']` | 크기 조정 핸들 위치 |
|
|
132
|
-
| **draggableCancel** | `string` | - | 드래그를 트리거하지 않을 요소의 CSS 선택자 |
|
|
133
|
-
| **draggableHandle** | `string` | - | 드래그 핸들용 CSS 선택자 |
|
|
134
|
-
| **autoSize** | `boolean` | `true` | 모든 아이템에 맞게 컨테이너 높이 자동 조정 |
|
|
135
|
-
| **verticalCompact** | `boolean` | `true` | 더 이상 사용되지 않음: compactType 사용 |
|
|
136
|
-
| **transformScale** | `number` | `1` | 확대/축소 시 드래그/크기 조정을 위한 스케일 팩터 |
|
|
137
|
-
| **droppingItem** | `Partial<GridItem>` | - | 외부에서 드래그 중 미리보기 아이템 |
|
|
138
|
-
| **className** | `string` | - | 컨테이너에 추가할 CSS 클래스 |
|
|
139
|
-
| **style** | `React.CSSProperties` | - | 컨테이너용 인라인 스타일 |
|
|
140
|
-
| **onLayoutChange** | `(layout: GridItem[]) => void` | - | 레이아웃 변경 시 콜백 |
|
|
141
|
-
| **onDragStart** | `(layout, oldItem, newItem, placeholder, e, element) => void` | - | 드래그 시작 콜백 |
|
|
142
|
-
| **onDrag** | `(layout, oldItem, newItem, placeholder, e, element) => void` | - | 드래그 중 콜백 |
|
|
143
|
-
| **onDragStop** | `(layout, oldItem, newItem, placeholder, e, element) => void` | - | 드래그 종료 콜백 |
|
|
144
|
-
| **onResizeStart** | `(layout, oldItem, newItem, placeholder, e, element) => void` | - | 크기 조정 시작 콜백 |
|
|
145
|
-
| **onResize** | `(layout, oldItem, newItem, placeholder, e, element) => void` | - | 크기 조정 중 콜백 |
|
|
146
|
-
| **onResizeStop** | `(layout, oldItem, newItem, placeholder, e, element) => void` | - | 크기 조정 종료 콜백 |
|
|
147
|
-
|
|
148
|
-
### GridItem 속성
|
|
149
|
-
|
|
150
|
-
| 속성 | 타입 | 필수 | 설명 |
|
|
151
|
-
|----------|------|----------|-------------|
|
|
152
|
-
| **id** | `string` | ✓ | 아이템의 고유 식별자 |
|
|
153
|
-
| **x** | `number` | ✓ | 그리드 단위의 X 위치 |
|
|
154
|
-
| **y** | `number` | ✓ | 그리드 단위의 Y 위치 |
|
|
155
|
-
| **w** | `number` | ✓ | 그리드 단위의 너비 |
|
|
156
|
-
| **h** | `number` | ✓ | 그리드 단위의 높이 |
|
|
157
|
-
| **minW** | `number` | - | 최소 너비 |
|
|
158
|
-
| **minH** | `number` | - | 최소 높이 |
|
|
159
|
-
| **maxW** | `number` | - | 최대 너비 |
|
|
160
|
-
| **maxH** | `number` | - | 최대 높이 |
|
|
161
|
-
| **isDraggable** | `boolean` | - | 컨테이너의 isDraggable 재정의 |
|
|
162
|
-
| **isResizable** | `boolean` | - | 컨테이너의 isResizable 재정의 |
|
|
163
|
-
| **static** | `boolean` | - | 아이템을 정적으로 만들기 (이동/크기조정 불가) |
|
|
164
|
-
| **className** | `string` | - | 아이템에 추가할 CSS 클래스 |
|
|
165
|
-
|
|
166
|
-
### ResponsiveGridContainer Props
|
|
167
|
-
|
|
168
|
-
| Prop | 타입 | 기본값 | 설명 |
|
|
169
|
-
|------|------|---------|-------------|
|
|
170
|
-
| **layouts** | `BreakpointLayouts` | 필수 | 각 브레이크포인트별 레이아웃 객체 |
|
|
171
|
-
| **breakpoints** | `{ [breakpoint: string]: number }` | `{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }` | 각 브레이크포인트의 최소 너비 |
|
|
172
|
-
| **cols** | `{ [breakpoint: string]: number }` | `{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }` | 각 브레이크포인트의 열 개수 |
|
|
173
|
-
| **onLayoutChange** | `(layout: GridItem[], layouts: BreakpointLayouts) => void` | - | 레이아웃 변경 시 현재 레이아웃과 모든 레이아웃을 전달하는 콜백 |
|
|
174
|
-
| **onBreakpointChange** | `(newBreakpoint: string, cols: number) => void` | - | 브레이크포인트 변경 시 호출되는 콜백 |
|
|
175
|
-
| **width** | `number` | - | 컨테이너 너비 (WidthProvider가 제공) |
|
|
176
|
-
| ...GridContainerProps | - | - | items, cols, onLayoutChange를 제외한 모든 GridContainer props |
|
|
177
|
-
|
|
178
|
-
## react-grid-layout과의 비교
|
|
179
|
-
|
|
180
|
-
| 기능 | react-grid-layout | tailwind-grid-layout | 비고 |
|
|
181
|
-
|---------|-------------------|---------------------|--------|
|
|
182
|
-
| **핵심 기능** |
|
|
183
|
-
| 드래그 & 드롭 | ✅ | ✅ | 완전 지원 |
|
|
184
|
-
| 크기 조정 | ✅ | ✅ | 8방향 크기 조정 |
|
|
185
|
-
| 충돌 감지 | ✅ | ✅ | 50% 겹침 규칙 |
|
|
186
|
-
| 자동 압축 | ✅ | ✅ | 수직, 수평 또는 없음 |
|
|
187
|
-
| 정적 아이템 | ✅ | ✅ | 완전 지원 |
|
|
188
|
-
| 경계 내 이동 | ✅ | ✅ | 아이템을 경계 내에 유지 |
|
|
189
|
-
| **레이아웃 옵션** |
|
|
190
|
-
| 반응형 브레이크포인트 | ✅ | ✅ | ResizeObserver를 통한 실시간 반응형 레이아웃 |
|
|
191
|
-
| 레이아웃 유지 | ✅ | ✅ | onLayoutChange를 통해 |
|
|
192
|
-
| 최소/최대 크기 | ✅ | ✅ | 완전 지원 |
|
|
193
|
-
| 충돌 방지 | ✅ | ✅ | 완전 지원 |
|
|
194
|
-
| 겹침 허용 | ✅ | ✅ | 완전 지원 |
|
|
195
|
-
| **이벤트** |
|
|
196
|
-
| 레이아웃 변경 | ✅ | ✅ | 완전 지원 |
|
|
197
|
-
| 드래그 이벤트 | ✅ | ✅ | 시작, 이동, 종료 |
|
|
198
|
-
| 크기 조정 이벤트 | ✅ | ✅ | 시작, 조정, 종료 |
|
|
199
|
-
| 외부에서 드롭 | ✅ | ✅ | DroppableGridContainer로 완전 지원 |
|
|
200
|
-
| **스타일링** |
|
|
201
|
-
| CSS-in-JS | ✅ | ❌ | Tailwind 사용 |
|
|
202
|
-
| 커스텀 클래스 | ✅ | ✅ | 완전 지원 |
|
|
203
|
-
| 애니메이션 | ✅ | ✅ | Tailwind 트랜지션 |
|
|
204
|
-
| **성능** |
|
|
205
|
-
| 번들 크기 | ~30KB | ~22KB (gzip) | 더 작은 번들 |
|
|
206
|
-
| 의존성 | React만 | React + Tailwind | |
|
|
207
|
-
| Tree-shaking | ✅ | ✅ | 완전 지원 |
|
|
208
|
-
|
|
209
|
-
## 고급 예제
|
|
210
|
-
|
|
211
|
-
### 커스텀 드래그 핸들
|
|
212
|
-
|
|
213
|
-
```tsx
|
|
214
|
-
<GridContainer items={items}>
|
|
215
|
-
{(item) => (
|
|
216
|
-
<div className="bg-white rounded-lg shadow p-4">
|
|
217
|
-
<div className="cursor-move p-2 bg-gray-100 rounded" data-drag-handle>
|
|
218
|
-
<GripIcon className="w-4 h-4" />
|
|
219
|
-
</div>
|
|
220
|
-
<div className="p-4">
|
|
221
|
-
{item.id}의 콘텐츠
|
|
222
|
-
</div>
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
225
|
-
</GridContainer>
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### 정적 아이템
|
|
229
|
-
|
|
230
|
-
```tsx
|
|
231
|
-
const items = [
|
|
232
|
-
{ id: '1', x: 0, y: 0, w: 4, h: 2, static: true }, // 이 아이템은 이동할 수 없음
|
|
233
|
-
{ id: '2', x: 4, y: 0, w: 4, h: 2 },
|
|
234
|
-
]
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### 반응형 브레이크포인트
|
|
238
|
-
|
|
239
|
-
```tsx
|
|
240
|
-
import { ResponsiveGridContainer, WidthProvider } from 'tailwind-grid-layout'
|
|
241
|
-
|
|
242
|
-
// 각 브레이크포인트별 레이아웃 정의
|
|
243
|
-
const layouts = {
|
|
244
|
-
lg: [
|
|
245
|
-
{ id: '1', x: 0, y: 0, w: 6, h: 2 },
|
|
246
|
-
{ id: '2', x: 6, y: 0, w: 6, h: 2 },
|
|
247
|
-
{ id: '3', x: 0, y: 2, w: 4, h: 2 },
|
|
248
|
-
{ id: '4', x: 4, y: 2, w: 8, h: 2 }
|
|
249
|
-
],
|
|
250
|
-
md: [
|
|
251
|
-
{ id: '1', x: 0, y: 0, w: 10, h: 2 },
|
|
252
|
-
{ id: '2', x: 0, y: 2, w: 10, h: 2 },
|
|
253
|
-
{ id: '3', x: 0, y: 4, w: 5, h: 2 },
|
|
254
|
-
{ id: '4', x: 5, y: 4, w: 5, h: 2 }
|
|
255
|
-
],
|
|
256
|
-
sm: [
|
|
257
|
-
{ id: '1', x: 0, y: 0, w: 6, h: 2 },
|
|
258
|
-
{ id: '2', x: 0, y: 2, w: 6, h: 2 },
|
|
259
|
-
{ id: '3', x: 0, y: 4, w: 6, h: 2 },
|
|
260
|
-
{ id: '4', x: 0, y: 6, w: 6, h: 2 }
|
|
261
|
-
],
|
|
262
|
-
xs: [
|
|
263
|
-
{ id: '1', x: 0, y: 0, w: 4, h: 2 },
|
|
264
|
-
{ id: '2', x: 0, y: 2, w: 4, h: 2 },
|
|
265
|
-
{ id: '3', x: 0, y: 4, w: 4, h: 2 },
|
|
266
|
-
{ id: '4', x: 0, y: 6, w: 4, h: 2 }
|
|
267
|
-
],
|
|
268
|
-
xxs: [
|
|
269
|
-
{ id: '1', x: 0, y: 0, w: 2, h: 2 },
|
|
270
|
-
{ id: '2', x: 0, y: 2, w: 2, h: 2 },
|
|
271
|
-
{ id: '3', x: 0, y: 4, w: 2, h: 2 },
|
|
272
|
-
{ id: '4', x: 0, y: 6, w: 2, h: 2 }
|
|
273
|
-
]
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 옵션 1: 수동 너비 추적
|
|
277
|
-
function ResponsiveExample() {
|
|
278
|
-
const [currentBreakpoint, setCurrentBreakpoint] = useState('lg')
|
|
279
|
-
|
|
280
|
-
return (
|
|
281
|
-
<ResponsiveGridContainer
|
|
282
|
-
layouts={layouts}
|
|
283
|
-
onBreakpointChange={(breakpoint) => {
|
|
284
|
-
setCurrentBreakpoint(breakpoint)
|
|
285
|
-
console.log(`${breakpoint} 브레이크포인트로 전환됨`)
|
|
286
|
-
}}
|
|
287
|
-
onLayoutChange={(layout, allLayouts) => {
|
|
288
|
-
// 레이아웃을 상태나 백엔드에 저장
|
|
289
|
-
console.log('레이아웃 변경됨:', allLayouts)
|
|
290
|
-
}}
|
|
291
|
-
>
|
|
292
|
-
{(item) => (
|
|
293
|
-
<div className="bg-blue-500 text-white p-4 rounded">
|
|
61
|
+
<div className="bg-white rounded-xl p-4 shadow">
|
|
294
62
|
아이템 {item.id}
|
|
295
63
|
</div>
|
|
296
64
|
)}
|
|
297
|
-
</
|
|
298
|
-
)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// 옵션 2: WidthProvider를 사용한 자동 너비 감지
|
|
302
|
-
const ResponsiveGridWithWidth = WidthProvider(ResponsiveGridContainer)
|
|
303
|
-
|
|
304
|
-
function App() {
|
|
305
|
-
return (
|
|
306
|
-
<ResponsiveGridWithWidth
|
|
307
|
-
layouts={layouts}
|
|
308
|
-
// 커스텀 브레이크포인트 (선택사항)
|
|
309
|
-
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
|
310
|
-
// 커스텀 열 구성 (선택사항)
|
|
311
|
-
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
|
|
312
|
-
>
|
|
313
|
-
{(item) => <div>아이템 {item.id}</div>}
|
|
314
|
-
</ResponsiveGridWithWidth>
|
|
65
|
+
</GridContainer>
|
|
315
66
|
)
|
|
316
67
|
}
|
|
317
68
|
```
|
|
318
69
|
|
|
319
|
-
|
|
70
|
+
## 컴포넌트
|
|
320
71
|
|
|
321
|
-
|
|
322
|
-
import { DroppableGridContainer } from 'tailwind-grid-layout'
|
|
72
|
+
### GridContainer
|
|
323
73
|
|
|
324
|
-
|
|
325
|
-
items={items}
|
|
326
|
-
onDrop={(newItem) => setItems([...items, newItem])}
|
|
327
|
-
droppingItem={{ w: 2, h: 2 }} // 드롭된 아이템의 기본 크기
|
|
328
|
-
>
|
|
329
|
-
{(item) => <div>드롭된 아이템 {item.id}</div>}
|
|
330
|
-
</DroppableGridContainer>
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### 커스텀 크기 조정 핸들
|
|
74
|
+
드래그 및 크기 조절을 지원하는 기본 그리드 컨테이너.
|
|
334
75
|
|
|
335
76
|
```tsx
|
|
336
77
|
<GridContainer
|
|
337
78
|
items={items}
|
|
338
|
-
|
|
79
|
+
cols={12}
|
|
80
|
+
rowHeight={80}
|
|
81
|
+
gap={16}
|
|
82
|
+
isDraggable={true}
|
|
83
|
+
isResizable={true}
|
|
84
|
+
resizeHandles={['se', 'sw', 'ne', 'nw', 'n', 's', 'e', 'w']}
|
|
85
|
+
onLayoutChange={setItems}
|
|
339
86
|
>
|
|
340
87
|
{(item) => <div>아이템 {item.id}</div>}
|
|
341
88
|
</GridContainer>
|
|
342
89
|
```
|
|
343
90
|
|
|
344
|
-
|
|
91
|
+
#### GridContainer Props
|
|
345
92
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
93
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
94
|
+
|------|------|--------|------|
|
|
95
|
+
| `items` | `GridItem[]` | **필수** | 그리드 아이템 배열 |
|
|
96
|
+
| `children` | `(item: GridItem) => ReactNode` | **필수** | 각 아이템의 렌더 함수 |
|
|
97
|
+
| `cols` | `number` | `12` | 컬럼 수 |
|
|
98
|
+
| `rowHeight` | `number` | `60` | 행 높이 (픽셀) |
|
|
99
|
+
| `gap` | `number` | `16` | 아이템 간격 (픽셀) |
|
|
100
|
+
| `margin` | `[number, number]` | `[gap, gap]` | 마진 [가로, 세로] |
|
|
101
|
+
| `containerPadding` | `[number, number]` | `[16, 16]` | 컨테이너 패딩 [가로, 세로] |
|
|
102
|
+
| `maxRows` | `number` | `undefined` | 최대 행 수 |
|
|
103
|
+
| `isDraggable` | `boolean` | `true` | 드래그 활성화/비활성화 |
|
|
104
|
+
| `isResizable` | `boolean` | `true` | 크기 조절 활성화/비활성화 |
|
|
105
|
+
| `preventCollision` | `boolean` | `false` | 아이템 충돌 방지 |
|
|
106
|
+
| `allowOverlap` | `boolean` | `false` | 아이템 겹침 허용 |
|
|
107
|
+
| `isBounded` | `boolean` | `true` | 컨테이너 경계 내 유지 |
|
|
108
|
+
| `compactType` | `'vertical' \| 'horizontal' \| null` | `'vertical'` | 압축 방향 |
|
|
109
|
+
| `resizeHandles` | `ResizeHandle[]` | `['se']` | 크기 조절 핸들 위치 |
|
|
110
|
+
| `draggableCancel` | `string` | `undefined` | 드래그 불가 요소 CSS 선택자 |
|
|
111
|
+
| `draggableHandle` | `string` | `undefined` | 드래그 핸들 CSS 선택자 |
|
|
112
|
+
| `autoSize` | `boolean` | `true` | 컨테이너 높이 자동 조절 |
|
|
113
|
+
| `preserveInitialHeight` | `boolean` | `false` | 초기 레이아웃 높이 유지 |
|
|
114
|
+
| `transformScale` | `number` | `1` | 중첩 트랜스폼용 스케일 |
|
|
115
|
+
| `className` | `string` | `undefined` | 추가 CSS 클래스 |
|
|
116
|
+
| `style` | `CSSProperties` | `undefined` | 추가 인라인 스타일 |
|
|
117
|
+
|
|
118
|
+
#### GridContainer 콜백
|
|
119
|
+
|
|
120
|
+
| 콜백 | 타입 | 설명 |
|
|
121
|
+
|------|------|------|
|
|
122
|
+
| `onLayoutChange` | `(layout: GridItem[]) => void` | 레이아웃 변경 시 호출 |
|
|
123
|
+
| `onDragStart` | `(layout, oldItem, newItem, placeholder, e, element) => void` | 드래그 시작 시 호출 |
|
|
124
|
+
| `onDrag` | `(layout, oldItem, newItem, placeholder, e, element) => void` | 드래그 중 호출 |
|
|
125
|
+
| `onDragStop` | `(layout, oldItem, newItem, placeholder, e, element) => void` | 드래그 종료 시 호출 |
|
|
126
|
+
| `onResizeStart` | `(layout, oldItem, newItem, placeholder, e, element) => void` | 크기 조절 시작 시 호출 |
|
|
127
|
+
| `onResize` | `(layout, oldItem, newItem, placeholder, e, element) => void` | 크기 조절 중 호출 |
|
|
128
|
+
| `onResizeStop` | `(layout, oldItem, newItem, placeholder, e, element) => void` | 크기 조절 종료 시 호출 |
|
|
129
|
+
|
|
130
|
+
### ResponsiveGridContainer
|
|
131
|
+
|
|
132
|
+
브레이크포인트 기반 반응형 그리드.
|
|
357
133
|
|
|
358
134
|
```tsx
|
|
359
|
-
|
|
360
|
-
items={items}
|
|
361
|
-
isBounded={true}
|
|
362
|
-
maxRows={10} // 그리드를 10행으로 제한
|
|
363
|
-
>
|
|
364
|
-
{(item) => <div>아이템 {item.id}</div>}
|
|
365
|
-
</GridContainer>
|
|
366
|
-
```
|
|
135
|
+
import { ResponsiveGridContainer, WidthProvider } from 'tailwind-grid-layout'
|
|
367
136
|
|
|
368
|
-
|
|
137
|
+
const ResponsiveGrid = WidthProvider(ResponsiveGridContainer)
|
|
369
138
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
139
|
+
const layouts = {
|
|
140
|
+
lg: [{ id: '1', x: 0, y: 0, w: 6, h: 2 }],
|
|
141
|
+
md: [{ id: '1', x: 0, y: 0, w: 10, h: 2 }],
|
|
142
|
+
sm: [{ id: '1', x: 0, y: 0, w: 6, h: 2 }],
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
<ResponsiveGrid
|
|
146
|
+
layouts={layouts}
|
|
147
|
+
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
|
|
148
|
+
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
|
|
149
|
+
onLayoutChange={(layout, layouts) => console.log(layouts)}
|
|
150
|
+
onBreakpointChange={(breakpoint, cols) => console.log(breakpoint)}
|
|
374
151
|
>
|
|
375
152
|
{(item) => <div>아이템 {item.id}</div>}
|
|
376
|
-
</
|
|
377
|
-
|
|
378
|
-
// 고정 높이
|
|
379
|
-
<div style={{ height: 400, overflow: 'auto' }}>
|
|
380
|
-
<GridContainer
|
|
381
|
-
items={items}
|
|
382
|
-
autoSize={false}
|
|
383
|
-
style={{ height: '100%' }}
|
|
384
|
-
>
|
|
385
|
-
{(item) => <div>아이템 {item.id}</div>}
|
|
386
|
-
</GridContainer>
|
|
387
|
-
</div>
|
|
153
|
+
</ResponsiveGrid>
|
|
388
154
|
```
|
|
389
155
|
|
|
390
|
-
|
|
156
|
+
#### ResponsiveGridContainer Props
|
|
391
157
|
|
|
392
|
-
|
|
158
|
+
GridContainer의 모든 props를 상속하며, `items`, `cols`, `onLayoutChange`는 제외됩니다.
|
|
393
159
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
xs: dashboardLayoutXs,
|
|
403
|
-
xxs: dashboardLayoutXxs
|
|
404
|
-
})
|
|
405
|
-
const [currentBreakpoint, setCurrentBreakpoint] = useState('')
|
|
406
|
-
const [currentCols, setCurrentCols] = useState(12)
|
|
160
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
161
|
+
|------|------|--------|------|
|
|
162
|
+
| `layouts` | `{ [breakpoint: string]: GridItem[] }` | **필수** | 각 브레이크포인트별 레이아웃 |
|
|
163
|
+
| `breakpoints` | `{ [breakpoint: string]: number }` | `{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }` | 브레이크포인트 너비 |
|
|
164
|
+
| `cols` | `{ [breakpoint: string]: number }` | `{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }` | 브레이크포인트별 컬럼 수 |
|
|
165
|
+
| `onLayoutChange` | `(layout: GridItem[], layouts: BreakpointLayouts) => void` | `undefined` | 레이아웃 변경 시 호출 |
|
|
166
|
+
| `onBreakpointChange` | `(breakpoint: string, cols: number) => void` | `undefined` | 브레이크포인트 변경 시 호출 |
|
|
167
|
+
| `width` | `number` | `undefined` | 컨테이너 너비 (WidthProvider용) |
|
|
407
168
|
|
|
408
|
-
|
|
409
|
-
<>
|
|
410
|
-
{/* 시각적 브레이크포인트 표시기 */}
|
|
411
|
-
<div className="mb-4 p-2 bg-green-100 rounded">
|
|
412
|
-
현재: {currentBreakpoint} ({currentCols}열)
|
|
413
|
-
</div>
|
|
414
|
-
|
|
415
|
-
<ResponsiveGridContainer
|
|
416
|
-
layouts={layouts}
|
|
417
|
-
onLayoutChange={(layout, allLayouts) => {
|
|
418
|
-
setLayouts(allLayouts)
|
|
419
|
-
}}
|
|
420
|
-
onBreakpointChange={(breakpoint, cols) => {
|
|
421
|
-
setCurrentBreakpoint(breakpoint)
|
|
422
|
-
setCurrentCols(cols)
|
|
423
|
-
}}
|
|
424
|
-
rowHeight={100}
|
|
425
|
-
gap={16}
|
|
426
|
-
containerPadding={[16, 16]}
|
|
427
|
-
>
|
|
428
|
-
{(item) => (
|
|
429
|
-
<Card key={item.id}>
|
|
430
|
-
<CardHeader>
|
|
431
|
-
<CardTitle>{item.title}</CardTitle>
|
|
432
|
-
</CardHeader>
|
|
433
|
-
<CardContent>
|
|
434
|
-
{item.content}
|
|
435
|
-
</CardContent>
|
|
436
|
-
</Card>
|
|
437
|
-
)}
|
|
438
|
-
</ResponsiveGridContainer>
|
|
439
|
-
</>
|
|
440
|
-
)
|
|
441
|
-
}
|
|
442
|
-
```
|
|
169
|
+
### DroppableGridContainer
|
|
443
170
|
|
|
444
|
-
|
|
171
|
+
외부 드래그 앤 드롭을 지원하는 그리드.
|
|
445
172
|
|
|
446
173
|
```tsx
|
|
174
|
+
import { DroppableGridContainer } from 'tailwind-grid-layout'
|
|
175
|
+
|
|
447
176
|
<DroppableGridContainer
|
|
448
177
|
items={items}
|
|
449
|
-
droppingItem={{ w:
|
|
178
|
+
droppingItem={{ w: 2, h: 2 }}
|
|
450
179
|
onDrop={(newItem) => setItems([...items, newItem])}
|
|
451
180
|
>
|
|
452
181
|
{(item) => <div>아이템 {item.id}</div>}
|
|
453
182
|
</DroppableGridContainer>
|
|
454
183
|
```
|
|
455
184
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
### generateLayouts
|
|
459
|
-
|
|
460
|
-
단일 레이아웃 정의에서 모든 브레이크포인트에 대한 동일한 레이아웃을 생성합니다.
|
|
461
|
-
|
|
462
|
-
```tsx
|
|
463
|
-
import { generateLayouts } from 'tailwind-grid-layout'
|
|
464
|
-
|
|
465
|
-
const items = [
|
|
466
|
-
{ id: '1', x: 0, y: 0, w: 4, h: 2 },
|
|
467
|
-
{ id: '2', x: 4, y: 0, w: 4, h: 2 }
|
|
468
|
-
]
|
|
469
|
-
|
|
470
|
-
// lg, md, sm, xs, xxs에 대해 동일한 위치로 레이아웃 생성
|
|
471
|
-
const layouts = generateLayouts(items)
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### generateResponsiveLayouts
|
|
185
|
+
#### DroppableGridContainer Props
|
|
475
186
|
|
|
476
|
-
|
|
187
|
+
GridContainer의 모든 props를 상속하며, `onDrop`은 제외됩니다.
|
|
477
188
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
{ id: '1', x: 0, y: 0, w: 12, h: 2 },
|
|
483
|
-
{ id: '2', x: 0, y: 2, w: 6, h: 2 }
|
|
484
|
-
]
|
|
485
|
-
|
|
486
|
-
// 컬럼 제약에 맞게 아이템 너비와 위치를 조정
|
|
487
|
-
const layouts = generateResponsiveLayouts(items, {
|
|
488
|
-
lg: 12,
|
|
489
|
-
md: 10,
|
|
490
|
-
sm: 6,
|
|
491
|
-
xs: 4,
|
|
492
|
-
xxs: 2
|
|
493
|
-
})
|
|
494
|
-
```
|
|
189
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
190
|
+
|------|------|--------|------|
|
|
191
|
+
| `droppingItem` | `Partial<GridItem>` | `{ w: 2, h: 2 }` | 드롭되는 아이템 크기 |
|
|
192
|
+
| `onDrop` | `(item: GridItem) => void` | `undefined` | 아이템 드롭 시 호출 |
|
|
495
193
|
|
|
496
|
-
### WidthProvider
|
|
194
|
+
### WidthProvider
|
|
497
195
|
|
|
498
|
-
|
|
196
|
+
ResponsiveGridContainer에 너비를 제공하는 HOC.
|
|
499
197
|
|
|
500
198
|
```tsx
|
|
501
199
|
import { ResponsiveGridContainer, WidthProvider } from 'tailwind-grid-layout'
|
|
502
200
|
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
// 기본 사용법
|
|
506
|
-
<ResponsiveGridWithWidth
|
|
507
|
-
layouts={layouts}
|
|
508
|
-
rowHeight={100}
|
|
509
|
-
>
|
|
510
|
-
{(item) => <div>아이템 {item.id}</div>}
|
|
511
|
-
</ResponsiveGridWithWidth>
|
|
512
|
-
|
|
513
|
-
// 초기 렌더링 시 레이아웃 변경을 방지하는 measureBeforeMount 사용
|
|
514
|
-
<ResponsiveGridWithWidth
|
|
515
|
-
layouts={layouts}
|
|
516
|
-
measureBeforeMount={true}
|
|
517
|
-
rowHeight={100}
|
|
518
|
-
>
|
|
519
|
-
{(item) => <div>아이템 {item.id}</div>}
|
|
520
|
-
</ResponsiveGridWithWidth>
|
|
201
|
+
const ResponsiveGrid = WidthProvider(ResponsiveGridContainer)
|
|
521
202
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
// - measureBeforeMount 옵션으로 SSR을 올바르게 처리
|
|
526
|
-
// - 더 나은 성능을 위한 디바운스된 리사이즈 처리 (150ms)
|
|
203
|
+
<ResponsiveGrid measureBeforeMount={true}>
|
|
204
|
+
{/* ... */}
|
|
205
|
+
</ResponsiveGrid>
|
|
527
206
|
```
|
|
528
207
|
|
|
208
|
+
#### WidthProvider Props
|
|
529
209
|
|
|
530
|
-
|
|
210
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
211
|
+
|------|------|--------|------|
|
|
212
|
+
| `measureBeforeMount` | `boolean` | `false` | 마운트 전 너비 측정 |
|
|
531
213
|
|
|
532
|
-
|
|
214
|
+
## GridItem 속성
|
|
533
215
|
|
|
534
|
-
|
|
216
|
+
| 속성 | 타입 | 필수 | 설명 |
|
|
217
|
+
|------|------|------|------|
|
|
218
|
+
| `id` | `string` | Yes | 고유 식별자 |
|
|
219
|
+
| `x` | `number` | Yes | 그리드 단위 X 위치 |
|
|
220
|
+
| `y` | `number` | Yes | 그리드 단위 Y 위치 |
|
|
221
|
+
| `w` | `number` | Yes | 그리드 단위 너비 |
|
|
222
|
+
| `h` | `number` | Yes | 그리드 단위 높이 |
|
|
223
|
+
| `minW` | `number` | No | 최소 너비 |
|
|
224
|
+
| `maxW` | `number` | No | 최대 너비 |
|
|
225
|
+
| `minH` | `number` | No | 최소 높이 |
|
|
226
|
+
| `maxH` | `number` | No | 최대 높이 |
|
|
227
|
+
| `static` | `boolean` | No | 이동/크기조절 불가 설정 |
|
|
228
|
+
| `isDraggable` | `boolean` | No | 컨테이너의 isDraggable 재정의 |
|
|
229
|
+
| `isResizable` | `boolean` | No | 컨테이너의 isResizable 재정의 |
|
|
230
|
+
| `className` | `string` | No | 아이템 추가 CSS 클래스 |
|
|
231
|
+
|
|
232
|
+
## 크기 조절 핸들
|
|
233
|
+
|
|
234
|
+
사용 가능한 크기 조절 핸들 위치:
|
|
235
|
+
|
|
236
|
+
| 핸들 | 위치 |
|
|
237
|
+
|------|------|
|
|
238
|
+
| `n` | 북쪽 (상단 중앙) |
|
|
239
|
+
| `s` | 남쪽 (하단 중앙) |
|
|
240
|
+
| `e` | 동쪽 (우측 중앙) |
|
|
241
|
+
| `w` | 서쪽 (좌측 중앙) |
|
|
242
|
+
| `ne` | 북동쪽 (우상단 모서리) |
|
|
243
|
+
| `nw` | 북서쪽 (좌상단 모서리) |
|
|
244
|
+
| `se` | 남동쪽 (우하단 모서리) |
|
|
245
|
+
| `sw` | 남서쪽 (좌하단 모서리) |
|
|
246
|
+
|
|
247
|
+
## 유틸리티 함수
|
|
535
248
|
|
|
536
249
|
```tsx
|
|
537
|
-
|
|
538
|
-
{(item) => (
|
|
539
|
-
<div className="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
|
|
540
|
-
<div className="p-4">
|
|
541
|
-
<h3 className="text-lg font-semibold">아이템 {item.id}</h3>
|
|
542
|
-
</div>
|
|
543
|
-
</div>
|
|
544
|
-
)}
|
|
545
|
-
</GridContainer>
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### 커스텀 플레이스홀더
|
|
250
|
+
import { generateLayouts, generateResponsiveLayouts } from 'tailwind-grid-layout'
|
|
549
251
|
|
|
550
|
-
|
|
252
|
+
// 아이템에서 레이아웃 생성
|
|
253
|
+
const layout = generateLayouts(items)
|
|
551
254
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
.tailwind-grid-layout .drag-placeholder {
|
|
555
|
-
background: rgba(59, 130, 246, 0.15);
|
|
556
|
-
border: 2px dashed rgb(59, 130, 246);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/* 크기 조정 플레이스홀더 */
|
|
560
|
-
.tailwind-grid-layout .resize-placeholder {
|
|
561
|
-
background: rgba(59, 130, 246, 0.1);
|
|
562
|
-
border: 2px dashed rgb(59, 130, 246);
|
|
563
|
-
}
|
|
255
|
+
// 반응형 레이아웃 생성
|
|
256
|
+
const responsiveLayouts = generateResponsiveLayouts(items, breakpoints, cols)
|
|
564
257
|
```
|
|
565
258
|
|
|
566
|
-
##
|
|
567
|
-
|
|
568
|
-
- **하드웨어 가속**: will-change와 함께 CSS transform 사용
|
|
569
|
-
- **제스처 디바운싱**: 최적화된 터치 이벤트 처리
|
|
570
|
-
- 터치 이벤트는 16ms (60fps) 단위로 디바운싱
|
|
571
|
-
- 불필요한 렌더링 최소화
|
|
572
|
-
- **메모리 관리**: 이벤트 리스너의 적절한 정리
|
|
573
|
-
- **번들 분할**: Tree-shakable exports
|
|
574
|
-
- **ResizeObserver**: 효율적인 컨테이너 너비 감지
|
|
575
|
-
- **애니메이션 제어**: 상호작용 중 트랜지션 비활성화
|
|
259
|
+
## react-grid-layout과 비교
|
|
576
260
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
## 브라우저 지원
|
|
587
|
-
|
|
588
|
-
- Chrome (최신)
|
|
589
|
-
- Firefox (최신)
|
|
590
|
-
- Safari (최신)
|
|
591
|
-
- Edge (최신)
|
|
592
|
-
- **Mobile Safari** (iOS 12+)
|
|
593
|
-
- **Chrome Mobile** (Android 7+)
|
|
594
|
-
- **ResizeObserver 지원**이 최적 성능을 위해 필요
|
|
595
|
-
|
|
596
|
-
## 기여하기
|
|
597
|
-
|
|
598
|
-
기여를 환영합니다! 자세한 내용은 [기여 가이드](CONTRIBUTING.md)를 참조하세요.
|
|
261
|
+
| 기능 | react-grid-layout | tailwind-grid-layout |
|
|
262
|
+
|------|-------------------|---------------------|
|
|
263
|
+
| 번들 크기 | ~30KB | ~15KB |
|
|
264
|
+
| Tailwind 네이티브 | No | Yes |
|
|
265
|
+
| TypeScript | Yes | Yes |
|
|
266
|
+
| 터치 지원 | Yes | Yes |
|
|
267
|
+
| 반응형 | Yes | Yes |
|
|
268
|
+
| 외부 CSS | 필요 | 불필요 |
|
|
599
269
|
|
|
600
270
|
## 라이선스
|
|
601
271
|
|
|
602
272
|
MIT © [Seungwoo, Lee](./LICENSE)
|
|
603
|
-
|
|
604
|
-
## 감사의 글
|
|
605
|
-
|
|
606
|
-
이 라이브러리는 [react-grid-layout](https://github.com/react-grid-layout/react-grid-layout)에서 영감을 받았으며, 현대적이고 Tailwind 우선의 대안을 제공하는 것을 목표로 합니다.
|