refactor: moved dnd part from Funnel into FunnelDnd

This commit is contained in:
2025-08-06 18:21:07 +04:00
parent 96c53380e0
commit 4b843d8e5d
23 changed files with 410 additions and 287 deletions

View File

@ -0,0 +1,42 @@
import React, { ReactNode } from "react";
import { useDroppable } from "@dnd-kit/core";
import {
SortableContext,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { Stack } from "@mantine/core";
import { BaseDraggable } from "@/components/dnd/types/types";
type Props<TItem> = {
id: string;
items: TItem[];
renderItem: (item: TItem) => ReactNode;
children?: ReactNode;
};
const FunnelColumn = <TItem extends BaseDraggable>({
id,
items,
renderItem,
children,
}: Props<TItem>) => {
const { setNodeRef } = useDroppable({ id });
return (
<>
{children}
<SortableContext
id={id}
items={items}
strategy={verticalListSortingStrategy}>
<Stack
gap="xs"
ref={setNodeRef}>
{items.map(renderItem)}
</Stack>
</SortableContext>
</>
);
};
export default FunnelColumn;

View File

@ -0,0 +1,126 @@
"use client";
import React, { ReactNode } from "react";
import {
closestCorners,
DndContext,
DragEndEvent,
DragOverEvent,
DragStartEvent,
} from "@dnd-kit/core";
import {
horizontalListSortingStrategy,
SortableContext,
} from "@dnd-kit/sortable";
import { Group, ScrollArea } from "@mantine/core";
import useDndSensors from "@/app/deals/hooks/useSensors";
import SortableItem from "@/components/dnd/SortableItem";
import { BaseDraggable } from "@/components/dnd/types/types";
import FunnelColumn from "./FunnelColumn";
import FunnelOverlay from "./FunnelOverlay";
type Props<TContainer, TItem> = {
containers: TContainer[];
items: TItem[];
onDragStart: (event: DragStartEvent) => void;
onDragOver: (event: DragOverEvent) => void;
onDragEnd: (event: DragEndEvent) => void;
renderContainer: (container: TContainer, children: ReactNode) => ReactNode;
renderContainerOverlay: (
container: TContainer,
children: ReactNode
) => ReactNode;
renderItem: (item: TItem) => ReactNode;
renderItemOverlay: (item: TItem) => ReactNode;
getContainerId: (container: TContainer) => string;
getItemsByContainer: (container: TContainer, items: TItem[]) => TItem[];
activeContainer: TContainer | null;
activeItem: TItem | null;
};
const FunnelDnd = <
TContainer extends BaseDraggable,
TItem extends BaseDraggable,
>({
containers,
items,
onDragStart,
onDragOver,
onDragEnd,
renderContainer,
renderContainerOverlay,
renderItem,
renderItemOverlay,
getContainerId,
getItemsByContainer,
activeContainer,
activeItem,
}: Props<TContainer, TItem>) => {
const sensors = useDndSensors();
return (
<ScrollArea
offsetScrollbars="x"
scrollbarSize="0.5rem">
<DndContext
sensors={sensors}
collisionDetection={closestCorners}
onDragStart={onDragStart}
onDragOver={onDragOver}
onDragEnd={onDragEnd}>
<SortableContext
items={containers.map(getContainerId)}
strategy={horizontalListSortingStrategy}>
<Group
gap="xs"
wrap="nowrap"
align="start">
{containers.map(container => {
const containerItems = getItemsByContainer(
container,
items
);
const containerId = getContainerId(container);
return (
<SortableItem
key={containerId}
id={containerId}>
{renderContainer(
container,
<FunnelColumn
id={containerId}
items={containerItems}
renderItem={renderItem}
/>
)}
</SortableItem>
);
})}
<FunnelOverlay
activeContainer={activeContainer}
activeItem={activeItem}
renderContainer={container => {
const containerItems = getItemsByContainer(
container,
items
);
const containerId = getContainerId(container);
return renderContainerOverlay(
container,
<FunnelColumn
id={containerId}
items={containerItems}
renderItem={renderItem}
/>
);
}}
renderItem={renderItemOverlay}
/>
</Group>
</SortableContext>
</DndContext>
</ScrollArea>
);
};
export default FunnelDnd;

View File

@ -0,0 +1,30 @@
import React, { ReactNode } from "react";
import { defaultDropAnimation, DragOverlay } from "@dnd-kit/core";
type Props<TContainer, TItem> = {
activeContainer: TContainer | null;
activeItem: TItem | null;
renderContainer: (container: TContainer) => ReactNode;
renderItem: (item: TItem) => ReactNode;
};
const FunnelOverlay = <TContainer, TItem>({
activeContainer,
activeItem,
renderContainer,
renderItem,
}: Props<TContainer, TItem>) => {
return (
<DragOverlay dropAnimation={defaultDropAnimation}>
<div style={{ cursor: "grabbing" }}>
{activeItem
? renderItem(activeItem)
: activeContainer
? renderContainer(activeContainer)
: null}
</div>
</DragOverlay>
);
};
export default FunnelOverlay;

View File

@ -0,0 +1,3 @@
import FunnelDnd from "./FunnelDnd";
export default FunnelDnd;