👁️
1import {
2 DndContext,
3 type DragCancelEvent,
4 type DragEndEvent,
5 KeyboardSensor,
6 PointerSensor,
7 TouchSensor,
8 useSensor,
9 useSensors,
10} from "@dnd-kit/core";
11import { type ReactNode, useId, useState } from "react";
12
13interface DragDropProviderProps {
14 children: ReactNode;
15 onDragEnd: (event: DragEndEvent) => void;
16 onDragCancel?: (event: DragCancelEvent) => void;
17}
18
19export function DragDropProvider({
20 children,
21 onDragEnd,
22 onDragCancel,
23}: DragDropProviderProps) {
24 const dndContextId = useId();
25
26 // WARN: Screen size is checked once on mount and never updated.
27 // dnd-kit's useSensors doesn't support dynamic sensor changes.
28 // This will break on foldable phones that change size mid-session.
29 // Fix requires either dnd-kit fix or remounting DndContext on resize.
30 const [isLargeScreen] = useState(() => {
31 if (typeof window === "undefined") return true;
32 return window.matchMedia("(min-width: 768px)").matches;
33 });
34
35 const pointerSensor = useSensor(PointerSensor, {
36 activationConstraint: {
37 distance: 8,
38 },
39 });
40
41 const touchSensor = useSensor(TouchSensor, {
42 activationConstraint: {
43 delay: 250,
44 tolerance: 5,
45 },
46 });
47
48 const keyboardSensor = useSensor(KeyboardSensor);
49
50 // Only include touch sensor on larger screens (tablets, laptops)
51 // On small screens (<768px), touch scrolls instead of dragging
52 const sensors = useSensors(
53 pointerSensor,
54 ...(isLargeScreen ? [touchSensor] : []),
55 keyboardSensor,
56 );
57
58 return (
59 <DndContext
60 id={dndContextId}
61 sensors={sensors}
62 onDragEnd={onDragEnd}
63 onDragCancel={onDragCancel}
64 >
65 {children}
66 </DndContext>
67 );
68}