-
Notifications
You must be signed in to change notification settings - Fork 327
/
Copy pathDragModal.tsx
85 lines (75 loc) · 2.57 KB
/
DragModal.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/** @file Modal for confirming delete of any type of asset. */
import * as React from 'react'
import * as modalProvider from '#/providers/ModalProvider'
import Modal from '#/components/Modal'
import * as tailwindMerge from '#/utilities/tailwindMerge'
// =================
// === Constants ===
// =================
/** The default offset (up and to the right) of the drag element. */
const DEFAULT_OFFSET_PX = 16
// =================
// === DragModal ===
// =================
/** Props for a {@link DragModal}. */
export interface DragModalProps
extends Readonly<React.PropsWithChildren>,
Readonly<JSX.IntrinsicElements['div']> {
readonly event: React.DragEvent
readonly onDragEnd: () => void
readonly offsetPx?: number
readonly offsetXPx?: number
readonly offsetYPx?: number
}
/** A modal for confirming the deletion of an asset. */
export default function DragModal(props: DragModalProps) {
const {
event,
offsetPx,
offsetXPx = DEFAULT_OFFSET_PX,
offsetYPx = DEFAULT_OFFSET_PX,
children,
style,
className,
onDragEnd: onDragEndRaw,
...passthrough
} = props
const { unsetModal } = modalProvider.useSetModal()
const [left, setLeft] = React.useState(event.pageX - (offsetPx ?? offsetXPx))
const [top, setTop] = React.useState(event.pageY - (offsetPx ?? offsetYPx))
const onDragEndRef = React.useRef(onDragEndRaw)
onDragEndRef.current = onDragEndRaw
React.useEffect(() => {
const onDrag = (dragEvent: MouseEvent) => {
if (dragEvent.pageX !== 0 || dragEvent.pageY !== 0) {
setLeft(dragEvent.pageX - (offsetPx ?? offsetXPx))
setTop(dragEvent.pageY - (offsetPx ?? offsetYPx))
}
}
const onDragEnd = () => {
onDragEndRef.current()
unsetModal()
}
// Update position (non-FF)
document.addEventListener('drag', onDrag, { capture: true })
// Update position (FF)
document.addEventListener('dragover', onDrag, { capture: true })
document.addEventListener('dragend', onDragEnd, { capture: true })
return () => {
document.removeEventListener('drag', onDrag, { capture: true })
document.removeEventListener('dragover', onDrag, { capture: true })
document.removeEventListener('dragend', onDragEnd, { capture: true })
}
}, [offsetPx, offsetXPx, offsetYPx, unsetModal])
return (
<Modal className="pointer-events-none absolute size-full overflow-hidden">
<div
{...passthrough}
style={{ left, top, ...style }}
className={tailwindMerge.twMerge('relative w-min', className)}
>
{children}
</div>
</Modal>
)
}