drag-and-drop

安装量: 37
排名: #19116

安装

npx skills add https://github.com/wodsmith/thewodapp --skill drag-and-drop

Drag and Drop with Pragmatic DnD This project uses @atlaskit/pragmatic-drag-and-drop for drag-and-drop functionality. Required Imports import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine" import { draggable , dropTargetForElements , type ElementDropTargetEventBasePayload , } from "@atlaskit/pragmatic-drag-and-drop/element/adapter" import { pointerOutsideOfPreview } from "@atlaskit/pragmatic-drag-and-drop/element/pointer-outside-of-preview" import { setCustomNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview" import { attachClosestEdge , type Edge , extractClosestEdge , } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge" import { DropIndicator } from "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box" Critical Pattern: Refs for Volatile State NEVER put volatile drag state in useEffect dependencies. This causes handlers to re-register on every state change. // BAD - re-registers handlers on every edge change const [ closestEdge , setClosestEdge ] = useState < Edge | null

( null ) useEffect ( ( ) => { // ...handlers using closestEdge } , [ closestEdge ] ) // Re-runs on every drag movement! // GOOD - use ref + useCallback for volatile state const [ closestEdge , setClosestEdge ] = useState < Edge | null

( null ) const closestEdgeRef = useRef < Edge | null

( null ) // Wrap in useCallback for lint compliance (exhaustive-deps) const updateClosestEdge = useCallback ( ( edge : Edge | null ) => { closestEdgeRef . current = edge setClosestEdge ( edge ) // Still update state for rendering } , [ ] ) useEffect ( ( ) => { // ...handlers read closestEdgeRef.current instead } , [ / stable deps / , updateClosestEdge ] ) // Include updateClosestEdge Import useCallback : import { useCallback , useEffect , useRef , useState } from "react" Basic Draggable Item Pattern function DraggableItem ( { item , index , instanceId , onDrop } ) { const ref = useRef < HTMLDivElement

( null ) const dragHandleRef = useRef < HTMLButtonElement

( null ) const [ isDragging , setIsDragging ] = useState ( false ) const [ closestEdge , setClosestEdge ] = useState < Edge | null

( null ) const closestEdgeRef = useRef < Edge | null

( null ) const updateClosestEdge = useCallback ( ( edge : Edge | null ) => { closestEdgeRef . current = edge setClosestEdge ( edge ) } , [ ] ) useEffect ( ( ) => { const element = ref . current const dragHandle = dragHandleRef . current if ( ! element || ! dragHandle ) return const itemData = { id : item . id , index , instanceId } return combine ( draggable ( { element : dragHandle , getInitialData : ( ) => itemData , onDragStart : ( ) => setIsDragging ( true ) , onDrop : ( ) => setIsDragging ( false ) , onGenerateDragPreview ( { nativeSetDragImage } ) { setCustomNativeDragPreview ( { nativeSetDragImage , getOffset : pointerOutsideOfPreview ( { x : "16px" , y : "8px" } ) , render ( { container } ) { const preview = document . createElement ( "div" ) preview . style . cssText = background: hsl(var(--background)); border: 2px solid hsl(var(--border)); border-radius: 6px; padding: 8px 12px; font-size: 14px; color: hsl(var(--foreground)); box-shadow: 0 2px 8px rgba(0,0,0,0.15); preview . textContent = item . label container . appendChild ( preview ) } , } ) } , } ) , dropTargetForElements ( { element , canDrop : ( { source } ) => source . data . instanceId === instanceId && source . data . index !== index , getData ( { input } ) { return attachClosestEdge ( itemData , { element , input , allowedEdges : [ "top" , "bottom" ] , } ) } , onDrag ( { source , self } : ElementDropTargetEventBasePayload ) { if ( source . data . index === index ) { updateClosestEdge ( null ) return } const edge = extractClosestEdge ( self . data ) const sourceIndex = source . data . index if ( typeof sourceIndex !== "number" ) return // Hide indicator when it would be redundant const isItemBeforeSource = index === sourceIndex - 1 const isItemAfterSource = index === sourceIndex + 1 const isDropIndicatorHidden = ( isItemBeforeSource && edge === "bottom" ) || ( isItemAfterSource && edge === "top" ) updateClosestEdge ( isDropIndicatorHidden ? null : edge ) } , onDragLeave : ( ) => updateClosestEdge ( null ) , onDrop ( { source } ) { const sourceIndex = source . data . index if ( typeof sourceIndex === "number" && sourceIndex !== index ) { const edge = closestEdgeRef . current // Read from ref! const targetIndex = edge === "top" ? index : index + 1 const adjustedTargetIndex = sourceIndex < targetIndex ? targetIndex - 1 : targetIndex onDrop ( sourceIndex , adjustedTargetIndex ) } updateClosestEdge ( null ) } , } ) , ) } , [ item . id , item . label , index , instanceId , onDrop , updateClosestEdge ] ) return ( < div ref = { ref } className = " relative "

{ closestEdge && < DropIndicator edge = { closestEdge } gap = " 2px " /> } < div className = { isDragging ? "opacity-50" : "" }

< button ref = { dragHandleRef } type = " button " aria-label = " Drag to reorder "

< GripVertical /> </ button

{ / Item content / } </ div

</ div

) } Instance ID for Multiple Lists Use Symbol to scope drag operations to a single list: function SortableList ( { items } ) { const [ instanceId ] = useState ( ( ) => Symbol ( "list" ) ) // Pass instanceId to each item } Reorder Handler const handleDrop = async ( sourceIndex : number , targetIndex : number ) => { const newItems = [ ... items ] const [ movedItem ] = newItems . splice ( sourceIndex , 1 ) if ( movedItem ) { newItems . splice ( targetIndex , 0 , movedItem ) const updated = newItems . map ( ( item , i ) => ( { ... item , position : i } ) ) setItems ( updated ) // Optimistic update await saveOrder ( updated ) // Persist } } Checklist Refs for volatile state (closestEdge, etc.) Wrap updateClosestEdge in useCallback (lint compliance) Include updateClosestEdge in useEffect deps Instance ID for list scoping Drop indicator with edge detection Custom drag preview Optimistic UI updates

返回排行榜