@archbase/kanban 4.0.37
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/LICENSE +13 -0
- package/README.md +630 -0
- package/dist/archbase-kanban-4.0.37.tgz +0 -0
- package/dist/components/Card/Card.d.ts +26 -0
- package/dist/components/Card/index.d.ts +1 -0
- package/dist/components/CardSkeleton/CardSkeleton.d.ts +9 -0
- package/dist/components/CardSkeleton/index.d.ts +2 -0
- package/dist/components/Column/Column.d.ts +47 -0
- package/dist/components/Column/index.d.ts +1 -0
- package/dist/components/ColumnAdder/ColumnAdder.d.ts +6 -0
- package/dist/components/ColumnAdder/index.d.ts +1 -0
- package/dist/components/ColumnContent/ColumnContent.d.ts +24 -0
- package/dist/components/ColumnContent/index.d.ts +1 -0
- package/dist/components/ColumnHeader/ColumnHeader.d.ts +10 -0
- package/dist/components/ColumnHeader/index.d.ts +1 -0
- package/dist/components/DefaultCard/DefaultCard.d.ts +3 -0
- package/dist/components/DefaultCard/index.d.ts +1 -0
- package/dist/components/GenericItem/GenericItem.d.ts +26 -0
- package/dist/components/GenericItem/index.d.ts +1 -0
- package/dist/components/Kanban.d.ts +3 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/types.d.ts +124 -0
- package/dist/context/KanbanContext.d.ts +5 -0
- package/dist/global/dnd/dropManager.d.ts +16 -0
- package/dist/global/dnd/useCardDnd.d.ts +22 -0
- package/dist/global/dnd/useColumnDnd.d.ts +29 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1041 -0
- package/dist/utils/columnsUtils.d.ts +4 -0
- package/dist/utils/getPrefix.d.ts +2 -0
- package/dist/utils/getSharedProps.d.ts +8 -0
- package/dist/utils/infinite-scroll.d.ts +4 -0
- package/dist/utils/mergeRefs.d.ts +5 -0
- package/dist/utils/scroll.d.ts +2 -0
- package/package.json +66 -0
- package/src/common/_index.scss +0 -0
- package/src/components/Card/Card.tsx +215 -0
- package/src/components/Card/_Card.scss +11 -0
- package/src/components/Card/_index.scss +1 -0
- package/src/components/Card/index.ts +1 -0
- package/src/components/CardSkeleton/CardSkeleton.tsx +45 -0
- package/src/components/CardSkeleton/_CardSkeleton.scss +245 -0
- package/src/components/CardSkeleton/_index.scss +1 -0
- package/src/components/CardSkeleton/index.ts +2 -0
- package/src/components/Column/Column.tsx +246 -0
- package/src/components/Column/_Column.scss +44 -0
- package/src/components/Column/_index.scss +1 -0
- package/src/components/Column/index.ts +1 -0
- package/src/components/ColumnAdder/ColumnAdder.tsx +18 -0
- package/src/components/ColumnAdder/_ColumnAdder.scss +0 -0
- package/src/components/ColumnAdder/_index.scss +1 -0
- package/src/components/ColumnAdder/index.ts +1 -0
- package/src/components/ColumnContent/ColumnContent.tsx +238 -0
- package/src/components/ColumnContent/_ColumnContent.scss +23 -0
- package/src/components/ColumnContent/_index.scss +1 -0
- package/src/components/ColumnContent/index.ts +1 -0
- package/src/components/ColumnHeader/ColumnHeader.tsx +43 -0
- package/src/components/ColumnHeader/_ColumnHeader.scss +26 -0
- package/src/components/ColumnHeader/_index.scss +1 -0
- package/src/components/ColumnHeader/index.ts +1 -0
- package/src/components/DefaultCard/DefaultCard.tsx +9 -0
- package/src/components/DefaultCard/_DefaultCard.scss +14 -0
- package/src/components/DefaultCard/_index.scss +1 -0
- package/src/components/DefaultCard/index.ts +1 -0
- package/src/components/GenericItem/GenericItem.tsx +132 -0
- package/src/components/GenericItem/_GenericItem.scss +4 -0
- package/src/components/GenericItem/_index.scss +1 -0
- package/src/components/GenericItem/index.ts +1 -0
- package/src/components/Kanban.tsx +102 -0
- package/src/components/_Kanban.scss +8 -0
- package/src/components/_index.scss +9 -0
- package/src/components/index.ts +2 -0
- package/src/components/types.ts +155 -0
- package/src/context/KanbanContext.tsx +19 -0
- package/src/global/_index.scss +3 -0
- package/src/global/assets/styles/abstracts/_breakpoints.scss +71 -0
- package/src/global/assets/styles/abstracts/_colors.scss +85 -0
- package/src/global/assets/styles/abstracts/_functions.scss +3 -0
- package/src/global/assets/styles/abstracts/_index.scss +5 -0
- package/src/global/assets/styles/abstracts/_mixins.scss +49 -0
- package/src/global/assets/styles/abstracts/_padding.scss +1 -0
- package/src/global/assets/styles/abstracts/_variables.scss +8 -0
- package/src/global/assets/styles/base/_index.scss +34 -0
- package/src/global/dnd/dropManager.ts +301 -0
- package/src/global/dnd/useCardDnd.tsx +225 -0
- package/src/global/dnd/useColumnDnd.tsx +231 -0
- package/src/global/theme-default.scss +137 -0
- package/src/index.ts +6 -0
- package/src/utils/columnsUtils.ts +19 -0
- package/src/utils/getPrefix.ts +7 -0
- package/src/utils/getSharedProps.ts +18 -0
- package/src/utils/infinite-scroll.ts +18 -0
- package/src/utils/mergeRefs.ts +23 -0
- package/src/utils/scroll.ts +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2024 Hazem braiek
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0.
|
|
4
|
+
You may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
# React Kanban Kit
|
|
2
|
+
|
|
3
|
+
A flexible and customizable Kanban board component for React applications, built with TypeScript and modern drag-and-drop functionality powered by Atlassian's pragmatic-drag-and-drop.
|
|
4
|
+
|
|
5
|
+
## Demo
|
|
6
|
+
|
|
7
|
+
<img src="https://dl.dropboxusercontent.com/scl/fi/18zb79la4t5xgmt07eihn/1758020089915_GIF-ScreenRecording2025-09-16at11.52.15AM-ezgif.com-video-to-gif-converter.gif?rlkey=ikdmkrl4swpcqht09v4fvm3bo&dl=0"/>
|
|
8
|
+
|
|
9
|
+
Check out the live demo: [https://react-kanban-kit.netlify.app/](https://react-kanban-kit.netlify.app/)
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🎯 **Drag and Drop**: Cards and columns with smooth animations
|
|
14
|
+
- 📱 **Responsive Design**: Works on desktop, tablet, and mobile
|
|
15
|
+
- 🎨 **Highly Customizable**: Custom renderers for cards, headers, footers, and more
|
|
16
|
+
- 🔄 **Virtual Scrolling**: Optimized performance for large datasets
|
|
17
|
+
- 📦 **TypeScript Support**: Full type safety and IntelliSense
|
|
18
|
+
- 🎮 **View-Only Mode**: Disable interactions when needed
|
|
19
|
+
- 🎯 **Skeleton Loading**: Built-in loading states with animations
|
|
20
|
+
- 🎨 **Custom Styling**: Function-based styling with access to data context
|
|
21
|
+
- 🔥 **Modern Architecture**: Built with React hooks and clean separation of concerns
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install react-kanban-kit
|
|
27
|
+
# or
|
|
28
|
+
yarn add react-kanban-kit
|
|
29
|
+
# or
|
|
30
|
+
pnpm add react-kanban-kit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Basic Usage
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Kanban, dropHandler } from "react-kanban-kit";
|
|
37
|
+
import type { BoardData } from "react-kanban-kit";
|
|
38
|
+
|
|
39
|
+
const MyKanbanBoard = () => {
|
|
40
|
+
const [dataSource, setDataSource] = useState<BoardData>({
|
|
41
|
+
root: {
|
|
42
|
+
id: "root",
|
|
43
|
+
title: "Root",
|
|
44
|
+
children: ["col-1", "col-2", "col-3"],
|
|
45
|
+
totalChildrenCount: 3,
|
|
46
|
+
parentId: null,
|
|
47
|
+
},
|
|
48
|
+
"col-1": {
|
|
49
|
+
id: "col-1",
|
|
50
|
+
title: "To Do",
|
|
51
|
+
children: ["task-1", "task-2"],
|
|
52
|
+
totalChildrenCount: 2,
|
|
53
|
+
parentId: "root",
|
|
54
|
+
},
|
|
55
|
+
"col-2": {
|
|
56
|
+
id: "col-2",
|
|
57
|
+
title: "In Progress",
|
|
58
|
+
children: ["task-3"],
|
|
59
|
+
totalChildrenCount: 1,
|
|
60
|
+
parentId: "root",
|
|
61
|
+
},
|
|
62
|
+
"col-3": {
|
|
63
|
+
id: "col-3",
|
|
64
|
+
title: "Done",
|
|
65
|
+
children: ["task-4"],
|
|
66
|
+
totalChildrenCount: 1,
|
|
67
|
+
parentId: "root",
|
|
68
|
+
},
|
|
69
|
+
"task-1": {
|
|
70
|
+
id: "task-1",
|
|
71
|
+
title: "Design Homepage",
|
|
72
|
+
parentId: "col-1",
|
|
73
|
+
children: [],
|
|
74
|
+
totalChildrenCount: 0,
|
|
75
|
+
type: "card",
|
|
76
|
+
content: {
|
|
77
|
+
description: "Create wireframes and mockups for the homepage",
|
|
78
|
+
priority: "high",
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
"task-2": {
|
|
82
|
+
id: "task-2",
|
|
83
|
+
title: "Setup Database",
|
|
84
|
+
parentId: "col-1",
|
|
85
|
+
children: [],
|
|
86
|
+
totalChildrenCount: 0,
|
|
87
|
+
type: "card",
|
|
88
|
+
},
|
|
89
|
+
"task-3": {
|
|
90
|
+
id: "task-3",
|
|
91
|
+
title: "Build Auth Flow",
|
|
92
|
+
parentId: "col-2",
|
|
93
|
+
children: [],
|
|
94
|
+
totalChildrenCount: 0,
|
|
95
|
+
type: "card",
|
|
96
|
+
},
|
|
97
|
+
"task-4": {
|
|
98
|
+
id: "task-4",
|
|
99
|
+
title: "Deploy to Production",
|
|
100
|
+
parentId: "col-3",
|
|
101
|
+
children: [],
|
|
102
|
+
totalChildrenCount: 0,
|
|
103
|
+
type: "card",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const configMap = {
|
|
108
|
+
card: {
|
|
109
|
+
render: ({ data }) => (
|
|
110
|
+
<div className="kanban-card">
|
|
111
|
+
<h3>{data.title}</h3>
|
|
112
|
+
{data.content?.description && <p>{data.content.description}</p>}
|
|
113
|
+
{data.content?.priority && (
|
|
114
|
+
<span className={`priority ${data.content.priority}`}>
|
|
115
|
+
{data.content.priority}
|
|
116
|
+
</span>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
),
|
|
120
|
+
isDraggable: true,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Kanban
|
|
126
|
+
dataSource={dataSource}
|
|
127
|
+
configMap={configMap}
|
|
128
|
+
onCardMove={(move) => {
|
|
129
|
+
setDataSource(dropHandler(move, dataSource, () => {}));
|
|
130
|
+
}}
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Advanced Usage
|
|
137
|
+
|
|
138
|
+
### Custom Card Types and Renderers
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
const configMap = {
|
|
142
|
+
card: {
|
|
143
|
+
render: ({ data, column, index, isDraggable }) => (
|
|
144
|
+
<div className="task-card">
|
|
145
|
+
<h4>{data.title}</h4>
|
|
146
|
+
<p>{data.content?.description}</p>
|
|
147
|
+
<div className="card-footer">
|
|
148
|
+
<span className="assignee">{data.content?.assignee}</span>
|
|
149
|
+
<span className="due-date">{data.content?.dueDate}</span>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
),
|
|
153
|
+
isDraggable: true,
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
divider: {
|
|
157
|
+
render: ({ data }) => (
|
|
158
|
+
<div className="divider">
|
|
159
|
+
<hr />
|
|
160
|
+
<span>{data.title}</span>
|
|
161
|
+
</div>
|
|
162
|
+
),
|
|
163
|
+
isDraggable: false,
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
footer: {
|
|
167
|
+
render: ({ data, column }) => (
|
|
168
|
+
<button className="add-card-btn">+ Add card to {column.title}</button>
|
|
169
|
+
),
|
|
170
|
+
isDraggable: false,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Custom Column Headers and Footers
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<Kanban
|
|
179
|
+
dataSource={dataSource}
|
|
180
|
+
configMap={configMap}
|
|
181
|
+
renderColumnHeader={(column) => (
|
|
182
|
+
<div className="custom-header">
|
|
183
|
+
<h3>{column.title}</h3>
|
|
184
|
+
<span className="count">{column.totalChildrenCount}</span>
|
|
185
|
+
<button className="column-menu">⋯</button>
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
renderColumnFooter={(column) => (
|
|
189
|
+
<div className="column-footer">
|
|
190
|
+
<button>Add New Card</button>
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
// Column adder
|
|
194
|
+
allowColumnAdder={true}
|
|
195
|
+
renderColumnAdder={() => (
|
|
196
|
+
<button className="add-column-btn">+ Add Column</button>
|
|
197
|
+
)}
|
|
198
|
+
// List footer (shown at the bottom of each column's card list)
|
|
199
|
+
allowListFooter={(column) => column.id !== "done"}
|
|
200
|
+
renderListFooter={(column) => (
|
|
201
|
+
<div className="list-footer">
|
|
202
|
+
<button>+ Add another card</button>
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
/>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Column Drag and Drop
|
|
209
|
+
|
|
210
|
+
Enable column reordering by dragging column headers. Columns are dragged by their header element and show a placeholder indicator at the drop position.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
import { Kanban, dropColumnHandler } from "react-kanban-kit";
|
|
214
|
+
|
|
215
|
+
<Kanban
|
|
216
|
+
dataSource={dataSource}
|
|
217
|
+
configMap={configMap}
|
|
218
|
+
allowColumnDrag
|
|
219
|
+
onColumnMove={(move) => {
|
|
220
|
+
setDataSource(dropColumnHandler(move, dataSource));
|
|
221
|
+
}}
|
|
222
|
+
renderColumnHeader={(column) => (
|
|
223
|
+
<div style={{ cursor: "grab" }}>
|
|
224
|
+
<h3>{column.title}</h3>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
/>;
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### How it works
|
|
231
|
+
|
|
232
|
+
- The column **header** is the drag handle users grab the header to drag the entire column
|
|
233
|
+
- While dragging, the source column dims (40% opacity), then hides once the cursor leaves it
|
|
234
|
+
- A **drop indicator** (column-sized placeholder) appears between columns to show the landing position
|
|
235
|
+
- On drop, `onColumnMove` fires with `{ columnId, fromIndex, toIndex }`
|
|
236
|
+
- Use the `dropColumnHandler` utility to produce the updated `dataSource`
|
|
237
|
+
|
|
238
|
+
#### Custom Column Drag Preview
|
|
239
|
+
|
|
240
|
+
By default, the drag preview is a DOM clone of the column. Override it with `renderColumnDragPreview`:
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
<Kanban
|
|
244
|
+
allowColumnDrag
|
|
245
|
+
renderColumnDragPreview={(column, info) => (
|
|
246
|
+
<div
|
|
247
|
+
style={{
|
|
248
|
+
width: info.state.dragging.width,
|
|
249
|
+
height: info.state.dragging.height,
|
|
250
|
+
backgroundColor: "#fff",
|
|
251
|
+
borderRadius: "12px",
|
|
252
|
+
padding: "12px",
|
|
253
|
+
boxShadow: "0 12px 30px rgba(0,0,0,0.2)",
|
|
254
|
+
transform: "rotate(4deg)",
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
<strong>{column.title}</strong>
|
|
258
|
+
<p>{column.totalChildrenCount} cards</p>
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
/>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### Custom Column Drop Indicator
|
|
265
|
+
|
|
266
|
+
By default, the drop indicator is a column-sized placeholder box. Override it with `renderColumnDragIndicator`:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
<Kanban
|
|
270
|
+
allowColumnDrag
|
|
271
|
+
renderColumnDragIndicator={(column, info) => (
|
|
272
|
+
<div
|
|
273
|
+
style={{
|
|
274
|
+
width: 4,
|
|
275
|
+
height: info.height,
|
|
276
|
+
backgroundColor: "#4a90d9",
|
|
277
|
+
borderRadius: 4,
|
|
278
|
+
}}
|
|
279
|
+
/>
|
|
280
|
+
)}
|
|
281
|
+
/>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The `info` object provides `{ width, height, edge }` where `edge` is `"left"` or `"right"` indicating which side of the target column the indicator appears on.
|
|
285
|
+
|
|
286
|
+
#### Disable Drag for Specific Columns
|
|
287
|
+
|
|
288
|
+
Set `isDraggable: false` on individual `BoardItem` entries to lock specific columns in place:
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
const dataSource = {
|
|
292
|
+
// ...
|
|
293
|
+
"col-1": {
|
|
294
|
+
id: "col-1",
|
|
295
|
+
title: "Backlog",
|
|
296
|
+
isDraggable: false, // This column cannot be dragged
|
|
297
|
+
// ...
|
|
298
|
+
},
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Card Drag and Drop Customization
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
<Kanban
|
|
306
|
+
renderCardDragPreview={(card, info) => (
|
|
307
|
+
<div className="drag-preview">
|
|
308
|
+
<h4>{card.title}</h4>
|
|
309
|
+
</div>
|
|
310
|
+
)}
|
|
311
|
+
renderCardDragIndicator={(card, info) => (
|
|
312
|
+
<div className="drop-indicator" style={{ height: info.height }} />
|
|
313
|
+
)}
|
|
314
|
+
onCardDndStateChange={(info) => {
|
|
315
|
+
if (info.state.type === "is-dragging") {
|
|
316
|
+
// Card is being dragged
|
|
317
|
+
}
|
|
318
|
+
}}
|
|
319
|
+
onColumnDndStateChange={(info) => {
|
|
320
|
+
if (info.state.type === "is-card-over") {
|
|
321
|
+
// A card is being dragged over this column
|
|
322
|
+
}
|
|
323
|
+
}}
|
|
324
|
+
/>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Advanced Styling
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
<Kanban
|
|
331
|
+
rootClassName="my-kanban-board"
|
|
332
|
+
rootStyle={{ backgroundColor: "#f5f5f5", padding: "20px" }}
|
|
333
|
+
columnWrapperStyle={(column) => ({
|
|
334
|
+
border: `2px solid ${column.content?.color || "#ddd"}`,
|
|
335
|
+
})}
|
|
336
|
+
columnWrapperClassName={(column) =>
|
|
337
|
+
`column-wrapper ${column.content?.theme || "default"}`
|
|
338
|
+
}
|
|
339
|
+
columnHeaderStyle={(column) => ({
|
|
340
|
+
backgroundColor: column.content?.headerColor || "#f8f9fa",
|
|
341
|
+
})}
|
|
342
|
+
columnStyle={(column) => ({
|
|
343
|
+
minHeight: column.totalChildrenCount > 10 ? "800px" : "400px",
|
|
344
|
+
})}
|
|
345
|
+
columnClassName={(column) =>
|
|
346
|
+
column.totalChildrenCount === 0 ? "empty-column" : "has-items"
|
|
347
|
+
}
|
|
348
|
+
cardWrapperStyle={(card, column) => ({
|
|
349
|
+
opacity: card.content?.archived ? 0.5 : 1,
|
|
350
|
+
})}
|
|
351
|
+
cardWrapperClassName="custom-card-wrapper"
|
|
352
|
+
cardsGap={12}
|
|
353
|
+
columnListContentStyle={(column) => ({
|
|
354
|
+
padding: column.totalChildrenCount === 0 ? "40px 16px" : "8px",
|
|
355
|
+
})}
|
|
356
|
+
columnListContentClassName={(column) =>
|
|
357
|
+
`column-content ${column.totalChildrenCount === 0 ? "empty" : "filled"}`
|
|
358
|
+
}
|
|
359
|
+
/>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### View-Only Mode
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
<Kanban dataSource={dataSource} configMap={configMap} viewOnly={true} />
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Infinite Scroll
|
|
371
|
+
|
|
372
|
+
Infinite scroll lets each column load cards on demand as the user scrolls, instead of loading everything upfront.
|
|
373
|
+
|
|
374
|
+
### How it works
|
|
375
|
+
|
|
376
|
+
The library uses `totalChildrenCount` and the actual `children` array to determine how many skeleton placeholders to render. When `totalChildrenCount > children.length`, the board renders skeleton cards to fill the gap. As the user scrolls and those skeletons become visible in the viewport, the library automatically calls your `loadMore(columnId)` callback.
|
|
377
|
+
|
|
378
|
+
For a full working implementation, see the [Infinite Scroll example in the demo](https://github.com/braiekhazem/react-kanban-kit/tree/main/rkk-demo/src/pages/InfiniteScrollExample) or try it live at [react-kanban-kit.netlify.app](https://react-kanban-kit.netlify.app).
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## `dropHandler` utility
|
|
383
|
+
|
|
384
|
+
When a card is dropped, `onCardMove` gives you the move details. Use `dropHandler` to produce the updated `dataSource`:
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { dropHandler } from "react-kanban-kit";
|
|
388
|
+
|
|
389
|
+
onCardMove={(move) => {
|
|
390
|
+
setDataSource(
|
|
391
|
+
dropHandler(
|
|
392
|
+
move,
|
|
393
|
+
dataSource,
|
|
394
|
+
() => {}, // called with the moved card (optional)
|
|
395
|
+
(targetColumn) => ({ // optional: update the target column
|
|
396
|
+
...targetColumn,
|
|
397
|
+
totalChildrenCount: targetColumn.totalChildrenCount + 1,
|
|
398
|
+
}),
|
|
399
|
+
(sourceColumn) => ({ // optional: update the source column
|
|
400
|
+
...sourceColumn,
|
|
401
|
+
totalChildrenCount: sourceColumn.totalChildrenCount - 1,
|
|
402
|
+
})
|
|
403
|
+
)
|
|
404
|
+
);
|
|
405
|
+
}}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
## `dropColumnHandler` utility
|
|
409
|
+
|
|
410
|
+
When a column is dropped, `onColumnMove` gives you the move details. Use `dropColumnHandler` to produce the updated `dataSource`:
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
import { dropColumnHandler } from "react-kanban-kit";
|
|
414
|
+
|
|
415
|
+
onColumnMove={(move) => {
|
|
416
|
+
setDataSource(dropColumnHandler(move, dataSource));
|
|
417
|
+
}}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
`dropColumnHandler` reorders the `root.children` array to move the column from `fromIndex` to `toIndex`.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Props Reference
|
|
425
|
+
|
|
426
|
+
### Core Props
|
|
427
|
+
|
|
428
|
+
| Prop | Type | Description |
|
|
429
|
+
| ------------ | ----------- | ---------------------------------------------------- |
|
|
430
|
+
| `dataSource` | `BoardData` | **Required.** The data structure for the board |
|
|
431
|
+
| `configMap` | `ConfigMap` | **Required.** Configuration for different card types |
|
|
432
|
+
| `viewOnly` | `boolean` | Disable all drag and drop interactions |
|
|
433
|
+
|
|
434
|
+
### Data Loading
|
|
435
|
+
|
|
436
|
+
| Prop | Type | Description |
|
|
437
|
+
| -------------------- | ---------------------------------- | ------------------------------------------------------------------------- |
|
|
438
|
+
| `loadMore` | `(columnId: string) => void` | Called automatically when skeleton cards scroll into view for that column |
|
|
439
|
+
| `renderSkeletonCard` | `({ index, column }) => ReactNode` | Custom skeleton card rendered for items not yet loaded |
|
|
440
|
+
|
|
441
|
+
### Drag and Drop Events
|
|
442
|
+
|
|
443
|
+
| Prop | Type | Description |
|
|
444
|
+
| ------------------------ | ---------------------------- | ------------------------------ |
|
|
445
|
+
| `onCardMove` | `(move: CardMove) => void` | Fired when a card is dropped |
|
|
446
|
+
| `onColumnMove` | `(move: ColumnMove) => void` | Fired when a column is dropped |
|
|
447
|
+
| `onCardDndStateChange` | `(info: DndState) => void` | Card drag state changes |
|
|
448
|
+
| `onColumnDndStateChange` | `(info: DndState) => void` | Column drag state changes |
|
|
449
|
+
|
|
450
|
+
### Drag and Drop Customization
|
|
451
|
+
|
|
452
|
+
| Prop | Type | Description |
|
|
453
|
+
| --------------------------- | ----------------------------- | ---------------------------- |
|
|
454
|
+
| `allowColumnDrag` | `boolean` | Enable column reordering |
|
|
455
|
+
| `renderCardDragPreview` | `(card, info) => ReactNode` | Custom card drag preview |
|
|
456
|
+
| `renderCardDragIndicator` | `(card, info) => ReactNode` | Custom card drop indicator |
|
|
457
|
+
| `renderColumnDragPreview` | `(column, info) => ReactNode` | Custom column drag preview |
|
|
458
|
+
| `renderColumnDragIndicator` | `(column, info) => ReactNode` | Custom column drop indicator |
|
|
459
|
+
|
|
460
|
+
### Column Customization
|
|
461
|
+
|
|
462
|
+
| Prop | Type | Description |
|
|
463
|
+
| --------------------- | ---------------------------------- | ----------------------------- |
|
|
464
|
+
| `renderColumnHeader` | `(column: BoardItem) => ReactNode` | Custom column header |
|
|
465
|
+
| `renderColumnFooter` | `(column: BoardItem) => ReactNode` | Custom column footer |
|
|
466
|
+
| `renderColumnWrapper` | `(column, props) => ReactNode` | Wrap entire column |
|
|
467
|
+
| `allowColumnAdder` | `boolean` | Show add column button |
|
|
468
|
+
| `renderColumnAdder` | `() => ReactNode` | Custom add column button |
|
|
469
|
+
| `renderListFooter` | `(column: BoardItem) => ReactNode` | Footer at bottom of card list |
|
|
470
|
+
| `allowListFooter` | `(column: BoardItem) => boolean` | Show list footer per column |
|
|
471
|
+
|
|
472
|
+
### Styling Props (Functions)
|
|
473
|
+
|
|
474
|
+
| Prop | Type | Description |
|
|
475
|
+
| ------------------------ | -------------------------------------- | -------------------------- |
|
|
476
|
+
| `columnWrapperStyle` | `(column: BoardItem) => CSSProperties` | Column wrapper styles |
|
|
477
|
+
| `columnHeaderStyle` | `(column: BoardItem) => CSSProperties` | Column header styles |
|
|
478
|
+
| `columnStyle` | `(column: BoardItem) => CSSProperties` | Column inner styles |
|
|
479
|
+
| `columnListContentStyle` | `(column: BoardItem) => CSSProperties` | Column content area styles |
|
|
480
|
+
| `cardWrapperStyle` | `(card, column) => CSSProperties` | Card wrapper styles |
|
|
481
|
+
|
|
482
|
+
### Styling Props (Class Names)
|
|
483
|
+
|
|
484
|
+
| Prop | Type | Description |
|
|
485
|
+
| ---------------------------- | ------------------------------- | -------------------- |
|
|
486
|
+
| `rootClassName` | `string` | Root container class |
|
|
487
|
+
| `columnWrapperClassName` | `(column: BoardItem) => string` | Column wrapper class |
|
|
488
|
+
| `columnHeaderClassName` | `(column: BoardItem) => string` | Column header class |
|
|
489
|
+
| `columnClassName` | `(column: BoardItem) => string` | Column inner class |
|
|
490
|
+
| `columnListContentClassName` | `(column: BoardItem) => string` | Column content class |
|
|
491
|
+
| `cardWrapperClassName` | `string` | Card wrapper class |
|
|
492
|
+
|
|
493
|
+
### Performance & Behavior
|
|
494
|
+
|
|
495
|
+
| Prop | Type | Description |
|
|
496
|
+
| ---------------- | --------- | ---------------------------------------- |
|
|
497
|
+
| `virtualization` | `boolean` | Enable virtual scrolling (default: true) |
|
|
498
|
+
| `cardsGap` | `number` | Gap between cards in pixels |
|
|
499
|
+
|
|
500
|
+
### Event Handlers
|
|
501
|
+
|
|
502
|
+
| Prop | Type | Description |
|
|
503
|
+
| --------------- | --------------------- | --------------------- |
|
|
504
|
+
| `onColumnClick` | `(e, column) => void` | Column click handler |
|
|
505
|
+
| `onCardClick` | `(e, card) => void` | Card click handler |
|
|
506
|
+
| `onScroll` | `(e, column) => void` | Column scroll handler |
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Data Structure
|
|
511
|
+
|
|
512
|
+
### BoardData
|
|
513
|
+
|
|
514
|
+
```typescript
|
|
515
|
+
interface BoardData {
|
|
516
|
+
root: BoardItem;
|
|
517
|
+
[key: string]: BoardItem;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
interface BoardItem {
|
|
521
|
+
id: string;
|
|
522
|
+
title: string;
|
|
523
|
+
parentId: string | null;
|
|
524
|
+
children: string[]; // IDs of loaded children
|
|
525
|
+
totalChildrenCount: number; // Real total (including unloaded items)
|
|
526
|
+
content?: any; // Your custom data
|
|
527
|
+
type?: keyof ConfigMap; // Card type key into configMap
|
|
528
|
+
isDraggable?: boolean; // Override per-item draggability
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### ConfigMap
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
type ConfigMap = {
|
|
536
|
+
[type: string]: {
|
|
537
|
+
render: (props: CardRenderProps) => React.ReactNode;
|
|
538
|
+
isDraggable?: boolean;
|
|
539
|
+
};
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
type CardRenderProps = {
|
|
543
|
+
data: BoardItem;
|
|
544
|
+
column: BoardItem;
|
|
545
|
+
index: number;
|
|
546
|
+
isDraggable: boolean;
|
|
547
|
+
};
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Event Types
|
|
553
|
+
|
|
554
|
+
### CardMove
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
interface CardMove {
|
|
558
|
+
cardId: string;
|
|
559
|
+
fromColumnId: string;
|
|
560
|
+
toColumnId: string;
|
|
561
|
+
taskAbove: string | null;
|
|
562
|
+
taskBelow: string | null;
|
|
563
|
+
position: number;
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### ColumnMove
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
interface ColumnMove {
|
|
571
|
+
columnId: string;
|
|
572
|
+
fromIndex: number;
|
|
573
|
+
toIndex: number;
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## CSS Classes
|
|
580
|
+
|
|
581
|
+
```css
|
|
582
|
+
.rkk-board {
|
|
583
|
+
} /* Root board container */
|
|
584
|
+
.rkk-column-outer {
|
|
585
|
+
} /* Column outer wrapper */
|
|
586
|
+
.rkk-column {
|
|
587
|
+
} /* Column inner container */
|
|
588
|
+
.rkk-column-header {
|
|
589
|
+
} /* Column header area */
|
|
590
|
+
.rkk-column-content {
|
|
591
|
+
} /* Column scrollable area */
|
|
592
|
+
.rkk-column-content-list {
|
|
593
|
+
} /* Virtual / normal list */
|
|
594
|
+
.rkk-generic-item-wrapper {
|
|
595
|
+
} /* Wrapper around each card */
|
|
596
|
+
.rkk-card-outer {
|
|
597
|
+
} /* Card outer element */
|
|
598
|
+
.rkk-card-inner {
|
|
599
|
+
} /* Card inner draggable element */
|
|
600
|
+
.rkk-card-shadow {
|
|
601
|
+
} /* Card drop position indicator */
|
|
602
|
+
.rkk-column-shadow-container {
|
|
603
|
+
} /* Column drop indicator wrapper */
|
|
604
|
+
.rkk-column-shadow {
|
|
605
|
+
} /* Column drop position indicator */
|
|
606
|
+
.rkk-skeleton {
|
|
607
|
+
} /* Default skeleton card */
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## TypeScript Support
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
import type {
|
|
616
|
+
BoardData,
|
|
617
|
+
BoardItem,
|
|
618
|
+
ConfigMap,
|
|
619
|
+
CardRenderProps,
|
|
620
|
+
BoardProps,
|
|
621
|
+
} from "react-kanban-kit";
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## Contributing
|
|
625
|
+
|
|
626
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
627
|
+
|
|
628
|
+
## License
|
|
629
|
+
|
|
630
|
+
MIT © Hazem braiek
|
|
Binary file
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { BoardItem, DndState } from '../types';
|
|
3
|
+
export declare const CardShadow: React.MemoExoticComponent<({ height, customIndicator, }: {
|
|
4
|
+
height: number;
|
|
5
|
+
customIndicator?: React.ReactNode;
|
|
6
|
+
}) => import("react/jsx-runtime").JSX.Element>;
|
|
7
|
+
interface Props {
|
|
8
|
+
render: (props: {
|
|
9
|
+
data: BoardItem;
|
|
10
|
+
column: BoardItem;
|
|
11
|
+
index: number;
|
|
12
|
+
isDraggable: boolean;
|
|
13
|
+
}) => React.ReactNode;
|
|
14
|
+
data: BoardItem;
|
|
15
|
+
column: BoardItem;
|
|
16
|
+
index: number;
|
|
17
|
+
isDraggable: boolean;
|
|
18
|
+
onClick?: (e: React.MouseEvent<HTMLDivElement>, card: BoardItem) => void;
|
|
19
|
+
cardsGap?: number;
|
|
20
|
+
renderGap?: (column: BoardItem) => React.ReactNode;
|
|
21
|
+
onCardDndStateChange?: (info: DndState) => void;
|
|
22
|
+
renderCardDragIndicator?: (card: BoardItem, info: any) => React.ReactNode;
|
|
23
|
+
renderCardDragPreview?: (card: BoardItem, info: any) => React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
declare const Card: (props: Props) => import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export default Card;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Card';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
export type SkeletonAnimationType = "shimmer" | "pulse" | "wave";
|
|
3
|
+
interface CardSkeletonProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
style?: React.CSSProperties;
|
|
6
|
+
animationType?: SkeletonAnimationType;
|
|
7
|
+
}
|
|
8
|
+
declare const CardSkeleton: React.FC<CardSkeletonProps>;
|
|
9
|
+
export default CardSkeleton;
|