SortableSection.tsx36 lines · main
| 1 | import { useSortable } from '@dnd-kit/sortable' |
| 2 | import { GripVertical } from 'lucide-react' |
| 3 | import type { CSSProperties, ReactNode } from 'react' |
| 4 | import { cn } from 'ui' |
| 5 | |
| 6 | type SortableSectionProps = { |
| 7 | id: string |
| 8 | children: ReactNode |
| 9 | } |
| 10 | |
| 11 | export const SortableSection = ({ id, children }: SortableSectionProps) => { |
| 12 | const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ |
| 13 | id, |
| 14 | }) |
| 15 | |
| 16 | const style: CSSProperties = { |
| 17 | transform: transform |
| 18 | ? `translate3d(${Math.round(transform.x)}px, ${Math.round(transform.y)}px, 0)` |
| 19 | : undefined, |
| 20 | transition, |
| 21 | } |
| 22 | |
| 23 | return ( |
| 24 | <div ref={setNodeRef} style={style} className="relative will-change-transform"> |
| 25 | <button |
| 26 | aria-label="Drag to reorder section" |
| 27 | className="absolute -left-6 top-2 text-foreground-muted hover:text-foreground focus:outline-hidden cursor-grab active:cursor-grabbing" |
| 28 | {...attributes} |
| 29 | {...listeners} |
| 30 | > |
| 31 | <GripVertical size={14} /> |
| 32 | </button> |
| 33 | <div className={cn(isDragging && 'opacity-70')}>{children}</div> |
| 34 | </div> |
| 35 | ) |
| 36 | } |