diff --git a/src/app/admin/_components/ArrayEditor.tsx b/src/app/admin/_components/ArrayEditor.tsx index fdc8812..f58b57b 100644 --- a/src/app/admin/_components/ArrayEditor.tsx +++ b/src/app/admin/_components/ArrayEditor.tsx @@ -41,24 +41,64 @@ export function ArrayEditor({ onChange(items.filter((_, i) => i !== index)); } - const handleMouseDown = useCallback( - (e: React.MouseEvent, index: number) => { - e.preventDefault(); + const startDrag = useCallback( + (clientX: number, clientY: number, index: number) => { const el = itemRefs.current[index]; if (!el) return; const rect = el.getBoundingClientRect(); setDragIndex(index); setInsertAt(index); - setMousePos({ x: e.clientX, y: e.clientY }); + setMousePos({ x: clientX, y: clientY }); setDragSize({ w: rect.width, h: rect.height }); - setGrabOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + setGrabOffset({ x: clientX - rect.left, y: clientY - rect.top }); }, [] ); + const handleGripMouseDown = useCallback( + (e: React.MouseEvent, index: number) => { + e.preventDefault(); + startDrag(e.clientX, e.clientY, index); + }, + [startDrag] + ); + + const handleCardMouseDown = useCallback( + (e: React.MouseEvent, index: number) => { + // Don't drag from interactive elements + const tag = (e.target as HTMLElement).closest("input, textarea, select, button, a, [role='switch']"); + if (tag) return; + e.preventDefault(); + + const x = e.clientX; + const y = e.clientY; + const pendingIndex = index; + + function onMove(ev: MouseEvent) { + const dx = ev.clientX - x; + const dy = ev.clientY - y; + // Start drag after 8px movement + if (Math.abs(dx) > 8 || Math.abs(dy) > 8) { + cleanup(); + startDrag(ev.clientX, ev.clientY, pendingIndex); + } + } + function onUp() { cleanup(); } + function cleanup() { + window.removeEventListener("mousemove", onMove); + window.removeEventListener("mouseup", onUp); + } + window.addEventListener("mousemove", onMove); + window.addEventListener("mouseup", onUp); + }, + [startDrag] + ); + useEffect(() => { if (dragIndex === null) return; + document.body.style.userSelect = "none"; + function onMouseMove(e: MouseEvent) { setMousePos({ x: e.clientX, y: e.clientY }); @@ -99,6 +139,7 @@ export function ArrayEditor({ window.addEventListener("mousemove", onMouseMove); window.addEventListener("mouseup", onMouseUp); return () => { + document.body.style.userSelect = ""; window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); }; @@ -110,12 +151,13 @@ export function ArrayEditor({
{ itemRefs.current[i] = el; }} + onMouseDown={(e) => handleCardMouseDown(e, i)} className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3" >
handleMouseDown(e, i)} + onMouseDown={(e) => handleGripMouseDown(e, i)} >
@@ -160,12 +202,13 @@ export function ArrayEditor({
{ itemRefs.current[i] = el; }} + onMouseDown={(e) => handleCardMouseDown(e, i)} className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3" >
handleMouseDown(e, i)} + onMouseDown={(e) => handleGripMouseDown(e, i)} >
diff --git a/src/app/admin/team/page.tsx b/src/app/admin/team/page.tsx index a81051f..790ec59 100644 --- a/src/app/admin/team/page.tsx +++ b/src/app/admin/team/page.tsx @@ -49,24 +49,62 @@ export default function TeamEditorPage() { setTimeout(() => setSaved(false), 2000); }, []); - const handleMouseDown = useCallback( - (e: React.MouseEvent, index: number) => { - e.preventDefault(); + const startDrag = useCallback( + (clientX: number, clientY: number, index: number) => { const el = itemRefs.current[index]; if (!el) return; const rect = el.getBoundingClientRect(); setDragIndex(index); setInsertAt(index); - setMousePos({ x: e.clientX, y: e.clientY }); + setMousePos({ x: clientX, y: clientY }); setDragSize({ w: rect.width, h: rect.height }); - setGrabOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top }); + setGrabOffset({ x: clientX - rect.left, y: clientY - rect.top }); }, [] ); + const handleGripMouseDown = useCallback( + (e: React.MouseEvent, index: number) => { + e.preventDefault(); + startDrag(e.clientX, e.clientY, index); + }, + [startDrag] + ); + + const handleCardMouseDown = useCallback( + (e: React.MouseEvent, index: number) => { + const tag = (e.target as HTMLElement).closest("input, textarea, select, button, a, [role='switch']"); + if (tag) return; + e.preventDefault(); + + const x = e.clientX; + const y = e.clientY; + const pendingIndex = index; + + function onMove(ev: MouseEvent) { + const dx = ev.clientX - x; + const dy = ev.clientY - y; + if (Math.abs(dx) > 8 || Math.abs(dy) > 8) { + cleanup(); + startDrag(ev.clientX, ev.clientY, pendingIndex); + } + } + function onUp() { cleanup(); } + function cleanup() { + window.removeEventListener("mousemove", onMove); + window.removeEventListener("mouseup", onUp); + } + window.addEventListener("mousemove", onMove); + window.addEventListener("mouseup", onUp); + }, + [startDrag] + ); + useEffect(() => { if (dragIndex === null) return; + document.body.style.userSelect = "none"; + function onMouseMove(e: MouseEvent) { setMousePos({ x: e.clientX, y: e.clientY }); @@ -107,6 +145,7 @@ export default function TeamEditorPage() { window.addEventListener("mousemove", onMouseMove); window.addEventListener("mouseup", onMouseUp); return () => { + document.body.style.userSelect = ""; window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); }; @@ -137,11 +176,12 @@ export default function TeamEditorPage() {
{ itemRefs.current[i] = el; }} + onMouseDown={(e) => handleCardMouseDown(e, i)} className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2" >
handleMouseDown(e, i)} + onMouseDown={(e) => handleGripMouseDown(e, i)} >
@@ -196,11 +236,12 @@ export default function TeamEditorPage() {
{ itemRefs.current[i] = el; }} + onMouseDown={(e) => handleCardMouseDown(e, i)} className="flex items-center gap-4 rounded-lg border border-white/10 bg-neutral-900/50 p-3 mb-2" >
handleMouseDown(e, i)} + onMouseDown={(e) => handleGripMouseDown(e, i)} >