"use client"; import React, { CSSProperties, ReactNode, useEffect, useMemo, useState, } from "react"; import { Active, DndContext, DragEndEvent } from "@dnd-kit/core"; import { restrictToHorizontalAxis, restrictToVerticalAxis, } from "@dnd-kit/modifiers"; import { SortableContext } from "@dnd-kit/sortable"; import { LexoRank } from "lexorank"; import { FreeMode, Mousewheel, Scrollbar } from "swiper/modules"; import { Swiper, SwiperSlide } from "swiper/react"; import { Box, Flex } from "@mantine/core"; import useDndSensors from "@/app/deals/hooks/useSensors"; import { SortableOverlay } from "@/components/dnd/SortableDnd/SortableOverlay"; import SortableItem from "@/components/dnd/SortableItem"; import { getNewLexorank, sortByLexorank } from "@/utils/lexorank"; import classes from "./SortableDnd.module.css"; type BaseItem = { id: number; lexorank: string; }; type Props = { initialItems: T[]; renderItem: ( item: T, renderDraggable?: (item: T) => ReactNode ) => ReactNode; renderDraggable?: (item: T) => ReactNode; // if not passed - the whole item renders as draggable dragHandleStyle?: CSSProperties; onDragEnd: (itemId: number, newLexorank: string) => void; onItemClick?: (item: T) => void; containerStyle?: CSSProperties; vertical?: boolean; disabled?: boolean; swiperEnabled?: boolean; }; const SortableDnd = ({ initialItems, renderItem, renderDraggable, dragHandleStyle, onDragEnd, onItemClick, containerStyle, vertical = false, disabled = false, swiperEnabled = false, }: Props) => { const [active, setActive] = useState(null); const [items, setItems] = useState([]); const activeItem = useMemo( () => initialItems.find(item => item.id === active?.id), [active, items] ); useEffect(() => { setItems(initialItems); }, [initialItems]); const sensors = useDndSensors(); const onDragEndLocal = ({ active, over }: DragEndEvent) => { if (!over || active.id === over?.id || !activeItem) { setActive(null); return; } const overIndex: number = items.findIndex(({ id }) => id === over.id); const activeIndex: number = items.findIndex( ({ id }) => id === activeItem.id ); let leftIndex = overIndex; let rightIndex = overIndex + 1; if (overIndex < activeIndex) { leftIndex = overIndex - 1; rightIndex = overIndex; } const leftLexorank: LexoRank | null = leftIndex >= 0 ? LexoRank.parse(items[leftIndex].lexorank) : null; const rightLexorank: LexoRank | null = rightIndex < items.length ? LexoRank.parse(items[rightIndex].lexorank) : null; const newLexorank = getNewLexorank( leftLexorank, rightLexorank ).toString(); items[activeIndex].lexorank = newLexorank; onDragEnd(items[activeIndex].id, newLexorank); const sortedItems = sortByLexorank(items); setItems([...sortedItems]); setActive(null); }; const renderWithSwiper = () => ( {items.map((item, index) => ( { if (!onItemClick) return; e.stopPropagation(); onItemClick(item); }}> renderItem(item, renderDraggable) } renderDraggable={ renderDraggable ? () => renderDraggable(item) : undefined } dragHandleStyle={dragHandleStyle} /> ))} ); const renderWithFlex = () => ( {items.map((item, index) => ( { if (!onItemClick) return; e.stopPropagation(); onItemClick(item); }}> renderItem(item, renderDraggable) } renderDraggable={ renderDraggable ? () => renderDraggable(item) : undefined } dragHandleStyle={dragHandleStyle} /> ))} ); const restrictModifier = vertical ? restrictToVerticalAxis : restrictToHorizontalAxis; return ( setActive(active)} onDragEnd={onDragEndLocal} onDragCancel={() => setActive(null)}> {swiperEnabled ? renderWithSwiper() : renderWithFlex()} {activeItem ? renderItem(activeItem, renderDraggable) : null} ); }; export default SortableDnd;