@lotics/ui 2.6.1 → 3.0.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/package.json +1 -15
- package/src/react_native.d.ts +2 -2
- package/src/cell_date.tsx +0 -30
- package/src/cell_date_format.test.ts +0 -32
- package/src/cell_date_format.ts +0 -73
- package/src/cell_number.test.ts +0 -42
- package/src/cell_number.tsx +0 -25
- package/src/cell_number_format.ts +0 -42
- package/src/cell_select.tsx +0 -68
- package/src/cell_text.tsx +0 -45
- package/src/grid/data_grid.tsx +0 -2003
- package/src/grid/data_grid_columns.test.ts +0 -72
- package/src/grid/data_grid_columns.ts +0 -30
- package/src/grid/data_grid_context.ts +0 -119
- package/src/grid/dispatch_safely.ts +0 -39
- package/src/grid/engine.module.css +0 -114
- package/src/grid/engine.tsx +0 -1042
- package/src/grid/helpers.ts +0 -205
- package/src/grid/layout.test.ts +0 -515
- package/src/grid/layout.ts +0 -425
- package/src/grid/recycling.test.ts +0 -236
- package/src/grid/recycling.ts +0 -172
- package/src/grid/row_cell.module.css +0 -105
- package/src/grid/row_cell.tsx +0 -313
- package/src/grid/search_highlight.ts +0 -71
- package/src/grid/select_cell.tsx +0 -58
- package/src/grid/select_group_summary_cell.tsx +0 -76
- package/src/grid/select_header_cell.tsx +0 -32
- package/src/grid/skeleton_row.module.css +0 -34
- package/src/grid/skeleton_row.tsx +0 -20
- package/src/grid/use_grid_groups.ts +0 -311
- package/src/grid/use_scroll_to_cell.ts +0 -135
- package/src/grid/use_virtual_grid.ts +0 -383
- package/src/grid/visibility.test.ts +0 -208
- package/src/grid/visibility.ts +0 -77
- package/src/kanban/constants.ts +0 -18
- package/src/kanban/default_renderers.tsx +0 -160
- package/src/kanban/drag_preview.tsx +0 -157
- package/src/kanban/index.ts +0 -13
- package/src/kanban/insert_card_zone.tsx +0 -135
- package/src/kanban/kanban_board.tsx +0 -635
- package/src/kanban/kanban_card.tsx +0 -321
- package/src/kanban/kanban_column.tsx +0 -499
- package/src/kanban/placeholders.tsx +0 -54
- package/src/kanban/types.ts +0 -116
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
FlatList,
|
|
4
|
-
GestureResponderEvent,
|
|
5
|
-
LayoutChangeEvent,
|
|
6
|
-
ListRenderItem,
|
|
7
|
-
NativeScrollEvent,
|
|
8
|
-
NativeSyntheticEvent,
|
|
9
|
-
Pressable,
|
|
10
|
-
StyleSheet,
|
|
11
|
-
View,
|
|
12
|
-
} from "react-native";
|
|
13
|
-
import { colors } from "../colors";
|
|
14
|
-
import {
|
|
15
|
-
KanbanItem,
|
|
16
|
-
KanbanRenderAddCardPlaceholderProps,
|
|
17
|
-
KanbanRenderInsertCardButtonProps,
|
|
18
|
-
KanbanRenderCardProps,
|
|
19
|
-
KanbanRenderCollapsedColumnProps,
|
|
20
|
-
KanbanRenderColumnHeaderProps,
|
|
21
|
-
} from "./types";
|
|
22
|
-
import { KanbanCard } from "./kanban_card";
|
|
23
|
-
import { CardPlaceholder, ColumnPlaceholder } from "./placeholders";
|
|
24
|
-
import { COLUMN_CONTENT_PADDING, DRAG_THRESHOLD } from "./constants";
|
|
25
|
-
import type { ColumnRegistration } from "./kanban_board";
|
|
26
|
-
|
|
27
|
-
// Memoized separator component to prevent re-renders
|
|
28
|
-
const ItemSeparator = memo(function ItemSeparator({ height }: { height: number }) {
|
|
29
|
-
const style = useMemo(() => ({ height }), [height]);
|
|
30
|
-
return <View style={style} />;
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
// Memoized footer placeholder component
|
|
34
|
-
const FooterPlaceholder = memo(function FooterPlaceholder({ height }: { height: number }) {
|
|
35
|
-
return <CardPlaceholder height={height} />;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// Stable key extractor (defined outside component to avoid recreation)
|
|
39
|
-
const keyExtractor = <T,>(item: KanbanItem<T>) => item.id;
|
|
40
|
-
|
|
41
|
-
interface KanbanColumnProps<T> {
|
|
42
|
-
columnKey: string;
|
|
43
|
-
title: string;
|
|
44
|
-
index: number;
|
|
45
|
-
items: KanbanItem<T>[];
|
|
46
|
-
renderCard: (props: KanbanRenderCardProps<T>) => React.ReactNode;
|
|
47
|
-
renderColumnHeader: (props: KanbanRenderColumnHeaderProps) => React.ReactNode;
|
|
48
|
-
renderAddCardPlaceholder: (props: KanbanRenderAddCardPlaceholderProps) => React.ReactNode;
|
|
49
|
-
renderInsertCardButton?: (props: KanbanRenderInsertCardButtonProps) => React.ReactNode;
|
|
50
|
-
renderCollapsedColumn: (props: KanbanRenderCollapsedColumnProps) => React.ReactNode;
|
|
51
|
-
isDragging: boolean;
|
|
52
|
-
isDropTarget: boolean;
|
|
53
|
-
/** True when any card is being dragged (disables insert zones) */
|
|
54
|
-
isDragInProgress: boolean;
|
|
55
|
-
cardDragId: string | null;
|
|
56
|
-
cardDropTargetIndex: number | null;
|
|
57
|
-
/** Height of the card being dragged (for placeholder sizing) */
|
|
58
|
-
draggedCardHeight: number | null;
|
|
59
|
-
/** Show full-column highlight when a card is being dropped (column-only mode) */
|
|
60
|
-
showCardDropHighlight: boolean;
|
|
61
|
-
columnWidth: number;
|
|
62
|
-
/** Width to use for drop target placeholder (matches dragged column's width) */
|
|
63
|
-
placeholderWidth: number;
|
|
64
|
-
/** Height to use for drop target placeholder (matches dragged column's height) */
|
|
65
|
-
placeholderHeight?: number;
|
|
66
|
-
itemHeight: number | ((item: T, index: number) => number);
|
|
67
|
-
itemGap: number;
|
|
68
|
-
onCardPress?: (cardId: string, item: T, columnKey: string) => void;
|
|
69
|
-
onAddCard?: (columnKey: string) => void;
|
|
70
|
-
onAddCardAtIndex?: (columnKey: string, index: number) => void;
|
|
71
|
-
/** Whether cards in this column can be dragged */
|
|
72
|
-
cardDraggable: boolean;
|
|
73
|
-
/** Whether this column can be reordered by dragging its header */
|
|
74
|
-
columnDraggable: boolean;
|
|
75
|
-
startCardDrag: (
|
|
76
|
-
cardId: string,
|
|
77
|
-
columnKey: string,
|
|
78
|
-
index: number,
|
|
79
|
-
startX: number,
|
|
80
|
-
startY: number,
|
|
81
|
-
rect: { x: number; y: number; width: number; height: number },
|
|
82
|
-
) => void;
|
|
83
|
-
startColumnDrag: (
|
|
84
|
-
columnKey: string,
|
|
85
|
-
index: number,
|
|
86
|
-
startX: number,
|
|
87
|
-
startY: number,
|
|
88
|
-
rect: { x: number; y: number; width: number; height: number },
|
|
89
|
-
) => void;
|
|
90
|
-
registerColumn: (columnKey: string, data: ColumnRegistration | null) => void;
|
|
91
|
-
updateColumnScroll: (columnKey: string, scrollY: number) => void;
|
|
92
|
-
onPointerEnter: (columnKey: string) => void;
|
|
93
|
-
isMinimized: boolean;
|
|
94
|
-
onMinimizeToggle: (columnKey: string) => void;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function KanbanColumnInner<T>({
|
|
98
|
-
columnKey,
|
|
99
|
-
title,
|
|
100
|
-
index,
|
|
101
|
-
items,
|
|
102
|
-
renderCard,
|
|
103
|
-
renderColumnHeader,
|
|
104
|
-
renderAddCardPlaceholder,
|
|
105
|
-
renderInsertCardButton,
|
|
106
|
-
renderCollapsedColumn,
|
|
107
|
-
isDragging,
|
|
108
|
-
isDropTarget,
|
|
109
|
-
isDragInProgress,
|
|
110
|
-
cardDragId,
|
|
111
|
-
cardDropTargetIndex,
|
|
112
|
-
draggedCardHeight,
|
|
113
|
-
showCardDropHighlight,
|
|
114
|
-
columnWidth,
|
|
115
|
-
placeholderWidth,
|
|
116
|
-
placeholderHeight,
|
|
117
|
-
itemHeight,
|
|
118
|
-
itemGap,
|
|
119
|
-
onCardPress,
|
|
120
|
-
onAddCard,
|
|
121
|
-
onAddCardAtIndex,
|
|
122
|
-
cardDraggable,
|
|
123
|
-
columnDraggable,
|
|
124
|
-
startCardDrag,
|
|
125
|
-
startColumnDrag,
|
|
126
|
-
registerColumn,
|
|
127
|
-
updateColumnScroll,
|
|
128
|
-
onPointerEnter,
|
|
129
|
-
isMinimized,
|
|
130
|
-
onMinimizeToggle,
|
|
131
|
-
}: KanbanColumnProps<T>) {
|
|
132
|
-
const containerRef = useRef<View>(null);
|
|
133
|
-
const listRef = useRef<FlatList>(null);
|
|
134
|
-
const headerHeightRef = useRef(0);
|
|
135
|
-
|
|
136
|
-
const cardDragIdRef = useRef(cardDragId);
|
|
137
|
-
const cardDropTargetIndexRef = useRef(cardDropTargetIndex);
|
|
138
|
-
const itemsLengthRef = useRef(items.length);
|
|
139
|
-
const draggedCardHeightRef = useRef(draggedCardHeight);
|
|
140
|
-
|
|
141
|
-
cardDragIdRef.current = cardDragId;
|
|
142
|
-
cardDropTargetIndexRef.current = cardDropTargetIndex;
|
|
143
|
-
itemsLengthRef.current = items.length;
|
|
144
|
-
draggedCardHeightRef.current = draggedCardHeight;
|
|
145
|
-
|
|
146
|
-
// Column drag threshold state (similar to card dragging)
|
|
147
|
-
const [isHeaderPressing, setIsHeaderPressing] = useState(false);
|
|
148
|
-
const headerPressStartRef = useRef<{ x: number; y: number } | null>(null);
|
|
149
|
-
const hasColumnDraggedRef = useRef(false);
|
|
150
|
-
const columnRectRef = useRef<{
|
|
151
|
-
x: number;
|
|
152
|
-
y: number;
|
|
153
|
-
width: number;
|
|
154
|
-
height: number;
|
|
155
|
-
} | null>(null);
|
|
156
|
-
|
|
157
|
-
const getHeight = useCallback(
|
|
158
|
-
(item: T, idx: number): number => {
|
|
159
|
-
return typeof itemHeight === "function" ? itemHeight(item, idx) : itemHeight;
|
|
160
|
-
},
|
|
161
|
-
[itemHeight],
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
useEffect(() => {
|
|
165
|
-
const element = containerRef.current as unknown as HTMLElement | null;
|
|
166
|
-
if (element && (listRef.current || isMinimized)) {
|
|
167
|
-
registerColumn(columnKey, {
|
|
168
|
-
element,
|
|
169
|
-
headerHeight: isMinimized ? 0 : headerHeightRef.current,
|
|
170
|
-
listRef: listRef.current, // null when minimized, FlatList when expanded
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
return () => registerColumn(columnKey, null);
|
|
174
|
-
}, [columnKey, registerColumn, isMinimized]);
|
|
175
|
-
|
|
176
|
-
const handleHeaderLayout = useCallback(
|
|
177
|
-
(event: LayoutChangeEvent) => {
|
|
178
|
-
headerHeightRef.current = event.nativeEvent.layout.height;
|
|
179
|
-
const element = containerRef.current as unknown as HTMLElement | null;
|
|
180
|
-
if (element && listRef.current) {
|
|
181
|
-
registerColumn(columnKey, {
|
|
182
|
-
element,
|
|
183
|
-
headerHeight: headerHeightRef.current,
|
|
184
|
-
listRef: listRef.current,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
},
|
|
188
|
-
[columnKey, registerColumn],
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
// Handle column drag with threshold (same as cards)
|
|
192
|
-
useEffect(() => {
|
|
193
|
-
if (!isHeaderPressing) return;
|
|
194
|
-
|
|
195
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
196
|
-
if (!headerPressStartRef.current || hasColumnDraggedRef.current) return;
|
|
197
|
-
|
|
198
|
-
const dx = Math.abs(e.clientX - headerPressStartRef.current.x);
|
|
199
|
-
const dy = Math.abs(e.clientY - headerPressStartRef.current.y);
|
|
200
|
-
|
|
201
|
-
if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
|
|
202
|
-
hasColumnDraggedRef.current = true;
|
|
203
|
-
if (columnRectRef.current) {
|
|
204
|
-
startColumnDrag(columnKey, index, e.clientX, e.clientY, columnRectRef.current);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const handleMouseUp = () => {
|
|
210
|
-
setIsHeaderPressing(false);
|
|
211
|
-
headerPressStartRef.current = null;
|
|
212
|
-
columnRectRef.current = null;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
216
|
-
window.addEventListener("mouseup", handleMouseUp);
|
|
217
|
-
|
|
218
|
-
return () => {
|
|
219
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
220
|
-
window.removeEventListener("mouseup", handleMouseUp);
|
|
221
|
-
};
|
|
222
|
-
}, [isHeaderPressing, columnKey, index, startColumnDrag]);
|
|
223
|
-
|
|
224
|
-
const handleHeaderPressIn = useCallback(
|
|
225
|
-
(event: GestureResponderEvent) => {
|
|
226
|
-
if (!columnDraggable) return;
|
|
227
|
-
// Use clientX/clientY (viewport-relative) for consistency with mousemove events
|
|
228
|
-
const clientX = event.nativeEvent.pageX - window.scrollX;
|
|
229
|
-
const clientY = event.nativeEvent.pageY - window.scrollY;
|
|
230
|
-
headerPressStartRef.current = { x: clientX, y: clientY };
|
|
231
|
-
hasColumnDraggedRef.current = false;
|
|
232
|
-
|
|
233
|
-
const element = containerRef.current as unknown as HTMLElement | null;
|
|
234
|
-
if (element) {
|
|
235
|
-
const rect = element.getBoundingClientRect();
|
|
236
|
-
columnRectRef.current = rect;
|
|
237
|
-
setIsHeaderPressing(true);
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
[columnDraggable],
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
const handleScroll = useCallback(
|
|
244
|
-
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
245
|
-
updateColumnScroll(columnKey, event.nativeEvent.contentOffset.y);
|
|
246
|
-
},
|
|
247
|
-
[columnKey, updateColumnScroll],
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
const handlePointerEnter = useCallback(() => {
|
|
251
|
-
onPointerEnter(columnKey);
|
|
252
|
-
}, [columnKey, onPointerEnter]);
|
|
253
|
-
|
|
254
|
-
const handleMinimizeToggle = useCallback(() => {
|
|
255
|
-
onMinimizeToggle(columnKey);
|
|
256
|
-
}, [columnKey, onMinimizeToggle]);
|
|
257
|
-
|
|
258
|
-
const handleMinimizedPress = useCallback(() => {
|
|
259
|
-
onMinimizeToggle(columnKey);
|
|
260
|
-
}, [columnKey, onMinimizeToggle]);
|
|
261
|
-
|
|
262
|
-
const flatListGetItemLayout = useCallback(
|
|
263
|
-
(_: ArrayLike<KanbanItem<T>> | null | undefined, itemIndex: number) => {
|
|
264
|
-
if (typeof itemHeight === "number") {
|
|
265
|
-
return {
|
|
266
|
-
length: itemHeight,
|
|
267
|
-
offset: itemIndex * (itemHeight + itemGap),
|
|
268
|
-
index: itemIndex,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
let offset = 0;
|
|
272
|
-
for (let i = 0; i < itemIndex; i++) {
|
|
273
|
-
offset += getHeight(items[i].data, i) + itemGap;
|
|
274
|
-
}
|
|
275
|
-
return {
|
|
276
|
-
length: getHeight(items[itemIndex]?.data, itemIndex),
|
|
277
|
-
offset,
|
|
278
|
-
index: itemIndex,
|
|
279
|
-
};
|
|
280
|
-
},
|
|
281
|
-
[items, itemHeight, itemGap, getHeight],
|
|
282
|
-
);
|
|
283
|
-
|
|
284
|
-
// O(1) lookup map for item id -> index
|
|
285
|
-
const itemIndexMap = useMemo(() => {
|
|
286
|
-
const map = new Map<string, number>();
|
|
287
|
-
items.forEach((item, idx) => map.set(item.id, idx));
|
|
288
|
-
return map;
|
|
289
|
-
}, [items]);
|
|
290
|
-
|
|
291
|
-
// Separator that hides when after dragged card that renders null
|
|
292
|
-
// When onAddCardAtIndex is provided, cards render their own gaps with insert zones
|
|
293
|
-
const renderSeparator = useCallback(
|
|
294
|
-
(props: { leadingItem: KanbanItem<T> | null }) => {
|
|
295
|
-
// When insert zones are enabled, cards render their own gaps
|
|
296
|
-
if (onAddCardAtIndex) {
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (!props.leadingItem) {
|
|
301
|
-
return <ItemSeparator height={itemGap} />;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const isLeadingDragging = cardDragIdRef.current === props.leadingItem.id;
|
|
305
|
-
const leadingIndex = itemIndexMap.get(props.leadingItem.id) ?? -1;
|
|
306
|
-
|
|
307
|
-
if (isLeadingDragging) {
|
|
308
|
-
// Dragged card renders null unless it's also the drop target
|
|
309
|
-
const isLeadingDropTarget = cardDropTargetIndexRef.current === leadingIndex;
|
|
310
|
-
|
|
311
|
-
// Skip separator after dragged card that renders null
|
|
312
|
-
if (!isLeadingDropTarget) {
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return <ItemSeparator height={itemGap} />;
|
|
318
|
-
},
|
|
319
|
-
[itemGap, itemIndexMap, onAddCardAtIndex],
|
|
320
|
-
);
|
|
321
|
-
|
|
322
|
-
const renderItem: ListRenderItem<KanbanItem<T>> = useCallback(
|
|
323
|
-
({ item, index: itemIndex }) => {
|
|
324
|
-
const isCardDragging = cardDragIdRef.current === item.id;
|
|
325
|
-
const isCardDropTarget = cardDropTargetIndexRef.current === itemIndex;
|
|
326
|
-
const height = getHeight(item.data, itemIndex);
|
|
327
|
-
|
|
328
|
-
return (
|
|
329
|
-
<KanbanCard
|
|
330
|
-
id={item.id}
|
|
331
|
-
columnKey={columnKey}
|
|
332
|
-
index={itemIndex}
|
|
333
|
-
item={item.data}
|
|
334
|
-
renderCard={renderCard}
|
|
335
|
-
isDragging={isCardDragging}
|
|
336
|
-
isDropTarget={isCardDropTarget}
|
|
337
|
-
isDragInProgress={isDragInProgress}
|
|
338
|
-
cardDraggable={cardDraggable}
|
|
339
|
-
onCardPress={onCardPress}
|
|
340
|
-
onAddCardAtIndex={onAddCardAtIndex}
|
|
341
|
-
renderInsertCardButton={renderInsertCardButton}
|
|
342
|
-
startCardDrag={startCardDrag}
|
|
343
|
-
placeholderHeight={height}
|
|
344
|
-
// Only pass draggedCardHeight to drop target - prevents all cards from re-rendering
|
|
345
|
-
draggedCardHeight={isCardDropTarget ? draggedCardHeightRef.current : null}
|
|
346
|
-
itemGap={itemGap}
|
|
347
|
-
/>
|
|
348
|
-
);
|
|
349
|
-
},
|
|
350
|
-
[
|
|
351
|
-
columnKey,
|
|
352
|
-
renderCard,
|
|
353
|
-
onCardPress,
|
|
354
|
-
onAddCardAtIndex,
|
|
355
|
-
renderInsertCardButton,
|
|
356
|
-
cardDraggable,
|
|
357
|
-
startCardDrag,
|
|
358
|
-
getHeight,
|
|
359
|
-
itemGap,
|
|
360
|
-
isDragInProgress,
|
|
361
|
-
],
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
const handleAddCard = useCallback(() => {
|
|
365
|
-
onAddCard?.(columnKey);
|
|
366
|
-
}, [onAddCard, columnKey]);
|
|
367
|
-
|
|
368
|
-
// Stable footer renderer using memoized component
|
|
369
|
-
const renderFooter = useCallback(() => {
|
|
370
|
-
if (cardDropTargetIndexRef.current === null) return null;
|
|
371
|
-
if (cardDropTargetIndexRef.current !== itemsLengthRef.current) return null;
|
|
372
|
-
|
|
373
|
-
const height = draggedCardHeightRef.current ?? 60;
|
|
374
|
-
return <FooterPlaceholder height={height} />;
|
|
375
|
-
}, []);
|
|
376
|
-
|
|
377
|
-
// Tells FlatList when to re-render items (refs don't trigger re-renders)
|
|
378
|
-
const listExtraData = useMemo(
|
|
379
|
-
() => [cardDragId, cardDropTargetIndex, draggedCardHeight],
|
|
380
|
-
[cardDragId, cardDropTargetIndex, draggedCardHeight],
|
|
381
|
-
);
|
|
382
|
-
|
|
383
|
-
// Stable container styles
|
|
384
|
-
const containerStyle = useMemo(() => [styles.container, { width: columnWidth }], [columnWidth]);
|
|
385
|
-
|
|
386
|
-
// List content style - remove top padding when insert zones are enabled
|
|
387
|
-
// since first card renders its own gap
|
|
388
|
-
const listContentStyle = useMemo(
|
|
389
|
-
() => (onAddCardAtIndex ? [styles.listContent, { paddingTop: 0 }] : styles.listContent),
|
|
390
|
-
[onAddCardAtIndex],
|
|
391
|
-
);
|
|
392
|
-
const minimizedContainerStyle = useMemo(
|
|
393
|
-
() => ({ width: columnWidth, height: "100%" as const }),
|
|
394
|
-
[columnWidth],
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
if (isDragging) {
|
|
398
|
-
return isDropTarget ? (
|
|
399
|
-
<ColumnPlaceholder width={placeholderWidth} height={placeholderHeight} />
|
|
400
|
-
) : null;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (isMinimized) {
|
|
404
|
-
const showDropIndicator = showCardDropHighlight || cardDropTargetIndex !== null;
|
|
405
|
-
|
|
406
|
-
return (
|
|
407
|
-
<>
|
|
408
|
-
{isDropTarget && <ColumnPlaceholder width={placeholderWidth} height={placeholderHeight} />}
|
|
409
|
-
<View
|
|
410
|
-
ref={containerRef}
|
|
411
|
-
style={minimizedContainerStyle}
|
|
412
|
-
onPointerEnter={handlePointerEnter}
|
|
413
|
-
>
|
|
414
|
-
{renderCollapsedColumn({
|
|
415
|
-
columnKey,
|
|
416
|
-
title,
|
|
417
|
-
itemCount: items.length,
|
|
418
|
-
showDropIndicator,
|
|
419
|
-
onExpand: handleMinimizedPress,
|
|
420
|
-
})}
|
|
421
|
-
</View>
|
|
422
|
-
</>
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return (
|
|
427
|
-
<>
|
|
428
|
-
{isDropTarget && <ColumnPlaceholder width={placeholderWidth} height={placeholderHeight} />}
|
|
429
|
-
<View ref={containerRef} style={containerStyle} onPointerEnter={handlePointerEnter}>
|
|
430
|
-
<Pressable onLayout={handleHeaderLayout} onPressIn={handleHeaderPressIn}>
|
|
431
|
-
{renderColumnHeader({
|
|
432
|
-
columnKey,
|
|
433
|
-
title,
|
|
434
|
-
itemCount: items.length,
|
|
435
|
-
isMinimized,
|
|
436
|
-
onMinimizeToggle: handleMinimizeToggle,
|
|
437
|
-
})}
|
|
438
|
-
</Pressable>
|
|
439
|
-
<FlatList
|
|
440
|
-
ref={listRef}
|
|
441
|
-
data={items}
|
|
442
|
-
extraData={listExtraData}
|
|
443
|
-
keyExtractor={keyExtractor}
|
|
444
|
-
renderItem={renderItem}
|
|
445
|
-
contentContainerStyle={listContentStyle}
|
|
446
|
-
ItemSeparatorComponent={renderSeparator}
|
|
447
|
-
ListFooterComponent={renderFooter}
|
|
448
|
-
showsVerticalScrollIndicator={true}
|
|
449
|
-
onScroll={handleScroll}
|
|
450
|
-
scrollEventThrottle={16}
|
|
451
|
-
getItemLayout={flatListGetItemLayout}
|
|
452
|
-
// Performance optimizations
|
|
453
|
-
removeClippedSubviews={false} // Keep false for smooth drag animations
|
|
454
|
-
maxToRenderPerBatch={10}
|
|
455
|
-
updateCellsBatchingPeriod={50}
|
|
456
|
-
windowSize={5} // Render 5 screens worth (enough for most columns)
|
|
457
|
-
initialNumToRender={15}
|
|
458
|
-
/>
|
|
459
|
-
{onAddCard && (
|
|
460
|
-
<View style={styles.addCardFooter}>
|
|
461
|
-
{renderAddCardPlaceholder({
|
|
462
|
-
columnKey,
|
|
463
|
-
onPress: handleAddCard,
|
|
464
|
-
})}
|
|
465
|
-
</View>
|
|
466
|
-
)}
|
|
467
|
-
{showCardDropHighlight && <View style={styles.cardDropHighlight} />}
|
|
468
|
-
</View>
|
|
469
|
-
</>
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
export const KanbanColumn = memo(KanbanColumnInner) as typeof KanbanColumnInner;
|
|
474
|
-
|
|
475
|
-
const styles = StyleSheet.create({
|
|
476
|
-
container: {
|
|
477
|
-
backgroundColor: colors.zinc[50],
|
|
478
|
-
borderRadius: 16,
|
|
479
|
-
maxHeight: "100%",
|
|
480
|
-
},
|
|
481
|
-
listContent: {
|
|
482
|
-
padding: COLUMN_CONTENT_PADDING,
|
|
483
|
-
},
|
|
484
|
-
addCardFooter: {
|
|
485
|
-
padding: COLUMN_CONTENT_PADDING,
|
|
486
|
-
paddingTop: 0,
|
|
487
|
-
},
|
|
488
|
-
cardDropHighlight: {
|
|
489
|
-
position: "absolute",
|
|
490
|
-
top: 0,
|
|
491
|
-
left: 0,
|
|
492
|
-
right: 0,
|
|
493
|
-
bottom: 0,
|
|
494
|
-
backgroundColor: colors.blue[100],
|
|
495
|
-
opacity: 0.5,
|
|
496
|
-
borderRadius: 8,
|
|
497
|
-
pointerEvents: "none",
|
|
498
|
-
},
|
|
499
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import React, { memo } from "react";
|
|
2
|
-
import { StyleSheet, View } from "react-native";
|
|
3
|
-
import { colors } from "../colors";
|
|
4
|
-
|
|
5
|
-
interface CardPlaceholderProps {
|
|
6
|
-
height: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Placeholder shown when dragging a card to a new position
|
|
11
|
-
*/
|
|
12
|
-
export const CardPlaceholder = memo(function CardPlaceholder({ height }: CardPlaceholderProps) {
|
|
13
|
-
return <View style={[styles.cardPlaceholder, { height }]} />;
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
interface ColumnPlaceholderProps {
|
|
17
|
-
width: number;
|
|
18
|
-
height?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Placeholder shown when dragging a column to a new position
|
|
23
|
-
*/
|
|
24
|
-
export const ColumnPlaceholder = memo(function ColumnPlaceholder({
|
|
25
|
-
width,
|
|
26
|
-
height,
|
|
27
|
-
}: ColumnPlaceholderProps) {
|
|
28
|
-
return (
|
|
29
|
-
<View style={[styles.columnPlaceholder, { width, height }]}>
|
|
30
|
-
<View style={styles.columnPlaceholderContent} />
|
|
31
|
-
</View>
|
|
32
|
-
);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const styles = StyleSheet.create({
|
|
36
|
-
cardPlaceholder: {
|
|
37
|
-
backgroundColor: colors.blue[50],
|
|
38
|
-
borderRadius: 8,
|
|
39
|
-
borderWidth: 2,
|
|
40
|
-
borderColor: colors.blue[300],
|
|
41
|
-
boxSizing: "border-box",
|
|
42
|
-
},
|
|
43
|
-
columnPlaceholder: {
|
|
44
|
-
minHeight: 200,
|
|
45
|
-
backgroundColor: colors.blue[50],
|
|
46
|
-
borderRadius: 8,
|
|
47
|
-
borderWidth: 2,
|
|
48
|
-
borderColor: colors.blue[300],
|
|
49
|
-
},
|
|
50
|
-
columnPlaceholderContent: {
|
|
51
|
-
flex: 1,
|
|
52
|
-
minHeight: 100,
|
|
53
|
-
},
|
|
54
|
-
});
|
package/src/kanban/types.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Represents a single item/card in the Kanban board
|
|
3
|
-
*/
|
|
4
|
-
export interface KanbanItem<T = unknown> {
|
|
5
|
-
id: string;
|
|
6
|
-
data: T;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Represents a column in the Kanban board
|
|
11
|
-
*/
|
|
12
|
-
export interface KanbanColumn<T = unknown> {
|
|
13
|
-
key: string;
|
|
14
|
-
title: string;
|
|
15
|
-
items: KanbanItem<T>[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Props passed to the renderCard function
|
|
20
|
-
*/
|
|
21
|
-
export interface KanbanRenderCardProps<T = unknown> {
|
|
22
|
-
item: T;
|
|
23
|
-
columnKey: string;
|
|
24
|
-
id: string;
|
|
25
|
-
hovered: boolean;
|
|
26
|
-
isDragging: boolean;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Props passed to the renderColumnHeader function
|
|
31
|
-
*/
|
|
32
|
-
export interface KanbanRenderColumnHeaderProps {
|
|
33
|
-
columnKey: string;
|
|
34
|
-
title: string;
|
|
35
|
-
itemCount: number;
|
|
36
|
-
isMinimized: boolean;
|
|
37
|
-
onMinimizeToggle: () => void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Information about the current drag operation
|
|
42
|
-
*/
|
|
43
|
-
export interface DragInfo {
|
|
44
|
-
type: "card" | "column";
|
|
45
|
-
id: string;
|
|
46
|
-
sourceColumnKey: string;
|
|
47
|
-
sourceIndex: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Information about the current drop target
|
|
52
|
-
*/
|
|
53
|
-
export interface DropTarget {
|
|
54
|
-
type: "card" | "column";
|
|
55
|
-
columnKey: string;
|
|
56
|
-
index: number;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Drag preview dimensions and offset
|
|
61
|
-
*/
|
|
62
|
-
export interface DragPreviewInfo {
|
|
63
|
-
width: number;
|
|
64
|
-
height: number;
|
|
65
|
-
offsetX: number;
|
|
66
|
-
offsetY: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Result of a card move operation
|
|
71
|
-
*/
|
|
72
|
-
export interface CardMoveResult<T = unknown> {
|
|
73
|
-
cardId: string;
|
|
74
|
-
cardData: T;
|
|
75
|
-
sourceColumnKey: string;
|
|
76
|
-
sourceIndex: number;
|
|
77
|
-
targetColumnKey: string;
|
|
78
|
-
targetIndex: number;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Result of a column move operation
|
|
83
|
-
*/
|
|
84
|
-
export interface ColumnMoveResult {
|
|
85
|
-
columnKey: string;
|
|
86
|
-
sourceIndex: number;
|
|
87
|
-
targetIndex: number;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Props passed to the renderAddCardPlaceholder function
|
|
92
|
-
*/
|
|
93
|
-
export interface KanbanRenderAddCardPlaceholderProps {
|
|
94
|
-
columnKey: string;
|
|
95
|
-
onPress: () => void;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Props passed to the renderInsertCardButton function
|
|
100
|
-
*/
|
|
101
|
-
export interface KanbanRenderInsertCardButtonProps {
|
|
102
|
-
columnKey: string;
|
|
103
|
-
index: number;
|
|
104
|
-
onPress: () => void;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Props passed to the renderCollapsedColumn function
|
|
109
|
-
*/
|
|
110
|
-
export interface KanbanRenderCollapsedColumnProps {
|
|
111
|
-
columnKey: string;
|
|
112
|
-
title: string;
|
|
113
|
-
itemCount: number;
|
|
114
|
-
showDropIndicator: boolean;
|
|
115
|
-
onExpand: () => void;
|
|
116
|
-
}
|