import { useDndMonitor } from '@dnd-kit/core' import { useSortable } from '@dnd-kit/sortable' import { GripVertical } from 'lucide-react' import type { CSSProperties, PropsWithChildren, ReactNode } from 'react' import { useEffect, useRef, useState } from 'react' import { Button, cn, DropdownMenu, DropdownMenuTrigger } from 'ui' const GRIP_WIDTH_REM = 1.5 const ACTIONS_WIDTH_REM = 1.75 const CONTROL_GAP_REM = 1 export const SortableSection = ({ id, children, actions, sectionWidth, gripClassName, gripDropdownContent, }: PropsWithChildren<{ id: string /** Caps the section at this width and centres it, with the controls alongside it. */ sectionWidth?: string gripClassName?: string actions?: ReactNode gripDropdownContent?: ReactNode }>) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id, }) const [menuOpen, setMenuOpen] = useState(false) const isDraggingRef = useRef(false) const openTimeoutRef = useRef>(undefined) useDndMonitor({ onDragStart: (event) => { if (event.active.id === id) { isDraggingRef.current = true clearTimeout(openTimeoutRef.current) setMenuOpen(false) } }, onDragEnd: (event) => { if (event.active.id === id) isDraggingRef.current = false }, onDragCancel: (event) => { if (event.active.id === id) isDraggingRef.current = false }, }) useEffect(() => () => clearTimeout(openTimeoutRef.current), []) // Carried twice — once as the controls, once as padding — so the section stays centred. const gutterRem = (actions ? ACTIONS_WIDTH_REM : 0) + GRIP_WIDTH_REM + CONTROL_GAP_REM const style: CSSProperties = { transform: transform ? `translate3d(${Math.round(transform.x)}px, ${Math.round(transform.y)}px, 0)` : undefined, transition, ...(sectionWidth && { marginInline: 'auto', maxWidth: `calc(${sectionWidth} + ${2 * gutterRem}rem)`, paddingRight: `${gutterRem}rem`, }), } return (
{actions} { clearTimeout(openTimeoutRef.current) if (!open) { setMenuOpen(false) return } openTimeoutRef.current = setTimeout(() => { if (!isDraggingRef.current) setMenuOpen(true) }, 150) }} >
{children}
) }