pstream is dead; long live pstream
taciturnaxolotl.github.io/pstream-ng/
1import {
2 DndContext,
3 DragEndEvent,
4 KeyboardSensor,
5 MouseSensor,
6 TouchSensor,
7 closestCenter,
8 useSensor,
9 useSensors,
10} from "@dnd-kit/core";
11import {
12 restrictToParentElement,
13 restrictToVerticalAxis,
14} from "@dnd-kit/modifiers";
15import {
16 SortableContext,
17 arrayMove,
18 sortableKeyboardCoordinates,
19 useSortable,
20 verticalListSortingStrategy,
21} from "@dnd-kit/sortable";
22import { CSS } from "@dnd-kit/utilities";
23import classNames from "classnames";
24
25import { Icon, Icons } from "../Icon";
26
27export interface Item {
28 id: string;
29 name: string;
30 disabled?: boolean;
31}
32
33function SortableItem(props: { item: Item }) {
34 const { attributes, listeners, setNodeRef, transform, transition } =
35 useSortable({ id: props.item.id });
36
37 const style = {
38 transform: CSS.Transform.toString(transform),
39 transition,
40 };
41
42 return (
43 <div
44 ref={setNodeRef}
45 style={style}
46 {...attributes}
47 {...listeners}
48 className={classNames(
49 "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg touch-manipulation",
50 props.item.disabled && "opacity-50",
51 transform ? "cursor-grabbing" : "cursor-grab",
52 )}
53 >
54 <span className="flex-1 text-white font-bold">{props.item.name}</span>
55 {props.item.disabled && <Icon icon={Icons.UNPLUG} />}
56 <Icon icon={Icons.MENU} />
57 </div>
58 );
59}
60
61export function SortableList(props: {
62 items: Item[];
63 setItems: (items: Item[]) => void;
64}) {
65 const sensors = useSensors(
66 useSensor(TouchSensor, {
67 activationConstraint: {
68 delay: 75,
69 tolerance: 1,
70 },
71 }),
72 useSensor(MouseSensor),
73 useSensor(KeyboardSensor, {
74 coordinateGetter: sortableKeyboardCoordinates,
75 }),
76 );
77
78 const handleDragEnd = (event: DragEndEvent) => {
79 const { active, over } = event;
80 if (!over) return;
81 if (active.id !== over.id) {
82 const currentItems = props.items;
83 const oldIndex = currentItems.findIndex((item) => item.id === active.id);
84 const newIndex = currentItems.findIndex((item) => item.id === over.id);
85 const newItems = arrayMove(currentItems, oldIndex, newIndex);
86 props.setItems(newItems);
87 }
88 };
89
90 return (
91 <DndContext
92 sensors={sensors}
93 collisionDetection={closestCenter}
94 onDragEnd={handleDragEnd}
95 modifiers={[restrictToVerticalAxis, restrictToParentElement]}
96 >
97 <SortableContext
98 items={props.items}
99 strategy={verticalListSortingStrategy}
100 >
101 <div className="flex flex-col gap-2">
102 {props.items.map((item) => (
103 <SortableItem key={item.id} item={item} />
104 ))}
105 </div>
106 </SortableContext>
107 </DndContext>
108 );
109}