'use client'; import * as React from 'react'; import { cn } from '@djangocfg/ui-core/lib'; import { GripVertical } from 'lucide-react'; import { SortableProvider, useSortableContext } from '../context'; import type { SortableProps, SortableItemProps, SortableHandleProps, } from '../types'; export function Sortable({ children, items, onReorder, axis = 'vertical', handleOnly = true, className, }: SortableProps) { const [activeId, setActiveId] = React.useState(null); const [overId, setOverId] = React.useState(null); const dragStartPos = React.useRef<{ x: number; y: number } | null>(null); const dragItemIndex = React.useRef(-1); const containerRef = React.useRef(null); const moveItem = React.useCallback( (activeId: string, overId: string) => { const activeIndex = items.findIndex((i) => i.id === activeId); const overIndex = items.findIndex((i) => i.id === overId); if (activeIndex === -1 || overIndex === -1 || activeIndex === overIndex) return; const newItems = [...items]; const [removed] = newItems.splice(activeIndex, 1); newItems.splice(overIndex, 0, removed); onReorder(newItems); }, [items, onReorder] ); const value = React.useMemo( () => ({ items, activeId, axis, handleOnly, setActiveId, moveItem, }), [items, activeId, axis, handleOnly, moveItem] ); return (
{children}
); } Sortable.displayName = 'Sortable'; export function SortableItem({ id, children, className, ...props }: SortableItemProps) { const { activeId, axis, handleOnly, setActiveId, moveItem } = useSortableContext(); const isActive = activeId === id; const itemRef = React.useRef(null); const onPointerDown = React.useCallback( (e: React.PointerEvent) => { if (handleOnly) return; // drag only via handle const target = e.target as HTMLElement; if (target.closest('[data-slot="sortable-handle"]')) return; e.preventDefault(); setActiveId(id); }, [handleOnly, id, setActiveId] ); const onPointerEnter = React.useCallback(() => { if (activeId && activeId !== id) { moveItem(activeId, id); } }, [activeId, id, moveItem]); return (
{children}
); } SortableItem.displayName = 'SortableItem'; export function SortableHandle({ children, className, ...props }: SortableHandleProps) { const { setActiveId } = useSortableContext(); const onPointerDown = React.useCallback( (e: React.PointerEvent) => { e.preventDefault(); const item = (e.currentTarget as HTMLElement).closest('[data-id]'); if (item) { const id = item.getAttribute('data-id'); if (id) setActiveId(id); } }, [setActiveId] ); return (
{children ?? }
); } SortableHandle.displayName = 'SortableHandle';