drag-and-drop

Drag and Drop with Pragmatic DnD

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "drag-and-drop" with this command: npx skills add wodsmith/thewodapp/wodsmith-thewodapp-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 &#x26;&#x26; 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 &#x26;&#x26; edge === "bottom") ||
        (isItemAfterSource &#x26;&#x26; edge === "top")

      updateClosestEdge(isDropIndicatorHidden ? null : edge)
    },
    onDragLeave: () => updateClosestEdge(null),
    onDrop({ source }) {
      const sourceIndex = source.data.index
      if (typeof sourceIndex === "number" &#x26;&#x26; sourceIndex !== index) {
        const edge = closestEdgeRef.current // Read from ref!
        const targetIndex = edge === "top" ? index : index + 1
        const adjustedTargetIndex =
          sourceIndex &#x3C; 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

ralph-mode

No summary provided by upstream source.

Repository SourceNeeds Review
General

skill-creator

No summary provided by upstream source.

Repository SourceNeeds Review
General

ll-feishu-audio

飞书语音交互技能。支持语音消息自动识别、AI 处理、语音回复全流程。需要配置 FEISHU_APP_ID 和 FEISHU_APP_SECRET 环境变量。使用 faster-whisper 进行语音识别,Edge TTS 进行语音合成,自动转换 OPUS 格式并通过飞书发送。适用于飞书平台的语音对话场景。

Archived SourceRecently Updated
General

test_skill

import json import tkinter as tk from tkinter import messagebox, simpledialog

Archived SourceRecently Updated