react-native-expo-kanban
Version:
kanban board for react native expo screens
120 lines (118 loc) • 5.61 kB
JavaScript
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions, FlatList, Text, View, } from "react-native";
import { GestureDetector } from "react-native-gesture-handler";
import Animated, { interpolate, useAnimatedRef, useAnimatedStyle, } from "react-native-reanimated";
import { DataCard } from "./components/DataCard";
import { useColumnPagination } from "./hooks/useColumnPagination";
import { useDragGesture } from "./hooks/useDragGesture";
import { DragContextValue } from "./utils/DraggedCardContext";
const ReactNativeKanbanBoard = (props) => {
const [toColumnIndex, setToColumnIndex] = useState(0);
const SCREEN_WIDTH = Dimensions.get("screen").width;
const columnContainerWidth = props.columnWidth ?? SCREEN_WIDTH * 0.8;
const scrollTriggerWidth = SCREEN_WIDTH * 0.3;
const edgeColumnOff = columnContainerWidth * 1.5 - SCREEN_WIDTH * 0.5;
const marginAlign = (SCREEN_WIDTH - columnContainerWidth) / 2;
const constants = {
columnContainerWidth,
scrollTriggerWidth,
edgeColumnOff,
marginAlign,
};
const columnPadding = props.gapBetweenColumns ?? 12;
const columnsHorizontalScrollRef = useAnimatedRef();
const itemsVerticalScrollEnabledRef = useRef(false);
const disableScroll = useCallback(() => {
itemsVerticalScrollEnabledRef.current = false;
columnsHorizontalScrollRef.current?.setNativeProps({
scrollEnabled: false,
});
}, [columnsHorizontalScrollRef, itemsVerticalScrollEnabledRef]);
function enableScrollers() {
columnsHorizontalScrollRef.current?.setNativeProps({ scrollEnabled: true });
itemsVerticalScrollEnabledRef.current = true;
}
const { paginate, updateCurrentColumnIndex } = useColumnPagination({
columnsHorizontalScrollRef,
constants,
});
const { pan, dragItem, dragX, dragY, setDragCard } = useDragGesture({
paginate,
toColumnIndex,
onDrop: enableScrollers,
onDragEndSuccess: props.onDragEnd,
});
useEffect(() => {
setTimeout(() => {
paginate("center");
}, 200);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.columnWidth]);
const animatedStyle = useAnimatedStyle(() => {
return {
position: "absolute",
top: dragItem?.y,
left: dragItem?.x,
width: dragItem?.width,
transform: [
{ translateY: dragY.value },
{ translateX: dragX.value },
{
rotate: interpolate(dragX.value, [-scrollTriggerWidth, 0, scrollTriggerWidth], [12, 0, 12], "extend") + "deg",
},
{
scale: interpolate(dragX.value, [-scrollTriggerWidth, 0, scrollTriggerWidth], [1.12, 1, 1.12], "extend"),
},
],
};
}, [dragItem]);
const renderColumn = ({ item: columnData, index: i, }) => {
const isPotentiallyBeingMoveTo = dragItem?.columnIndex !== undefined &&
i !== dragItem.columnIndex &&
toColumnIndex === i;
const isItemInFocusedColumn = i === toColumnIndex;
const renderCard = ({ item }) => {
const isBeingDragged = dragItem?.id === item.id;
return (<DataCard disableScroll={disableScroll} setDragCard={setDragCard} renderItem={props.renderItem} isDraggable={!dragItem && isItemInFocusedColumn} itemColumnIndex={i} item={item} isBeingDragged={isBeingDragged}/>);
};
return (<View key={i} style={[
props.columnContainerStyle,
{
margin: columnPadding,
padding: columnPadding,
width: columnContainerWidth - columnPadding * 2,
},
isPotentiallyBeingMoveTo ? props.columnContainerStyleOnDrag : {},
]}>
<View style={props.columnHeaderStyle}>
{props.renderHeader(columnData.header)}
<Text>{columnData.items.length}</Text>
</View>
<FlatList scrollEnabled={itemsVerticalScrollEnabledRef.current} data={columnData.items} renderItem={renderCard} keyExtractor={(_, index) => `${i}-${index}`} extraData={isItemInFocusedColumn} initialNumToRender={i === 0 ? 8 : 3} showsVerticalScrollIndicator={false}/>
</View>);
};
const onMomentumScrollEnd = (event) => {
const columnIndex = event.nativeEvent.contentOffset.x === 0
? 0
: Math.round((event.nativeEvent.contentOffset.x + marginAlign) /
columnContainerWidth);
updateCurrentColumnIndex(columnIndex);
setToColumnIndex(columnIndex);
};
return (<DragContextValue.Provider value={{
setDragCard,
dragCardY: dragItem?.id ? dragY : undefined,
dragCardX: dragItem?.id ? dragX : undefined,
dragCard: dragItem?.props,
}}>
<GestureDetector gesture={pan}>
<View>
<Animated.FlatList ref={columnsHorizontalScrollRef} horizontal pagingEnabled snapToInterval={columnContainerWidth} snapToAlignment={"center"} decelerationRate={"fast"} onMomentumScrollEnd={onMomentumScrollEnd} data={props.columnData} renderItem={renderColumn} contentContainerStyle={props.contentContainerStyle} style={props.containerStyle}/>
{dragItem?.props && (<Animated.View style={animatedStyle}>
{props.renderItem(dragItem.props, true)}
</Animated.View>)}
</View>
</GestureDetector>
</DragContextValue.Provider>);
};
export default ReactNativeKanbanBoard;