gitscope
← marzneshin / v0.6.5 / dashboard/src/common/components/ui/sortable/index.tsx
https://dev.kronoz.win/r/marzneshin.git
dashboard/src/common/components/ui/sortable/index.tsx · TypeScript
Blame Raw
  1import React from "react"
  2import type {
  3    DndContextProps,
  4    UniqueIdentifier,
  5} from "@dnd-kit/core";
  6import {
  7    closestCenter,
  8    DndContext,
  9    KeyboardSensor,
 10    MouseSensor,
 11    TouchSensor,
 12    useSensor,
 13    useSensors,
 14} from "@dnd-kit/core";
 15import {
 16    restrictToParentElement,
 17    restrictToVerticalAxis,
 18} from "@dnd-kit/modifiers";
 19import {
 20    arrayMove,
 21    SortableContext,
 22    verticalListSortingStrategy,
 23    type SortableContextProps,
 24} from "@dnd-kit/sortable";
 25import { SortableDragHandle } from "./drag-handle";
 26import { SortableItem } from "./item";
 27import { SortableOverlay } from "./overlay";
 28
 29interface SortableProps<TData extends { id: UniqueIdentifier }>
 30    extends DndContextProps {
 31    /**
 32     * An array of data items that the sortable component will render.
 33     * @example
 34     * value={[
 35     *   { id: 1, name: 'Item 1' },
 36     *   { id: 2, name: 'Item 2' },
 37     * ]}
 38     */
 39    value: TData[]
 40
 41    /**
 42     * An optional callback function that is called when the order of the data items changes.
 43     * It receives the new array of items as its argument.
 44     * @example
 45     * onValueChange={(items) => console.log(items)}
 46     */
 47    onValueChange?: (items: TData[]) => void
 48
 49    /**
 50     * An optional callback function that is called when an item is moved.
 51     * It receives an event object with `activeIndex` and `overIndex` properties, representing the original and new positions of the moved item.
 52     * This will override the default behavior of updating the order of the data items.
 53     * @type (event: { activeIndex: number; overIndex: number }) => void
 54     * @example
 55     * onMove={(event) => console.log(`Item moved from index ${event.activeIndex} to index ${event.overIndex}`)}
 56     */
 57    onMove?: (event: { activeIndex: number; overIndex: number }) => void
 58
 59    /**
 60     * A collision detection strategy that will be used to determine the closest sortable item.
 61     * @default closestCenter
 62     * @type DndContextProps["collisionDetection"]
 63     */
 64    collisionDetection?: DndContextProps["collisionDetection"]
 65
 66    /**
 67     * An array of modifiers that will be used to modify the behavior of the sortable component.
 68     * @default
 69     * [restrictToVerticalAxis, restrictToParentElement]
 70     * @type Modifier[]
 71     */
 72    modifiers?: DndContextProps["modifiers"]
 73
 74    /**
 75     * A sorting strategy that will be used to determine the new order of the data items.
 76     * @default verticalListSortingStrategy
 77     * @type SortableContextProps["strategy"]
 78     */
 79    strategy?: SortableContextProps["strategy"]
 80
 81    /**
 82     * An optional React node that is rendered on top of the sortable component.
 83     * It can be used to display additional information or controls.
 84     * @default null
 85     * @type React.ReactNode | null
 86     * @example
 87     * overlay={<Skeleton className="w-full h-8" />}
 88     */
 89    overlay?: React.ReactNode | null
 90}
 91
 92function Sortable<TData extends { id: UniqueIdentifier }>({
 93    value,
 94    onValueChange,
 95    collisionDetection = closestCenter,
 96    modifiers = [restrictToVerticalAxis, restrictToParentElement],
 97    strategy = verticalListSortingStrategy,
 98    onMove,
 99    children,
100    overlay,
101    ...props
102}: SortableProps<TData>) {
103    const [activeId, setActiveId] = React.useState<UniqueIdentifier | null>(null)
104
105    const sensors = useSensors(
106        useSensor(MouseSensor),
107        useSensor(TouchSensor),
108        useSensor(KeyboardSensor)
109    )
110
111    return (
112        <DndContext
113            modifiers={modifiers}
114            sensors={sensors}
115            onDragStart={({ active }) => setActiveId(active.id)}
116            onDragEnd={({ active, over }) => {
117                if (over && active.id !== over?.id) {
118                    const activeIndex = value.findIndex((item) => item.id === active.id)
119                    const overIndex = value.findIndex((item) => item.id === over.id)
120
121                    if (onMove) {
122                        onMove({ activeIndex, overIndex })
123                    } else {
124                        onValueChange?.(arrayMove(value, activeIndex, overIndex))
125                    }
126                }
127                setActiveId(null)
128            }}
129            onDragCancel={() => setActiveId(null)}
130            collisionDetection={collisionDetection}
131            {...props}
132        >
133            <SortableContext items={value} strategy={strategy}>
134                {children}
135            </SortableContext>
136            {overlay ? (
137                <SortableOverlay activeId={activeId}>{overlay}</SortableOverlay>
138            ) : null}
139        </DndContext>
140    )
141}
142
143
144export { Sortable, SortableDragHandle, SortableItem, SortableOverlay }