2.8 KB97 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {DragHandler} from './DragHandle';
9
10import * as stylex from '@stylexjs/stylex';
11import {ViewportOverlay} from 'isl-components/ViewportOverlay';
12import {getZoomLevel} from 'isl-components/zoom';
13import React, {useEffect, useRef} from 'react';
14
15const styles = stylex.create({
16 draggingElement: {
17 paddingLeft: 'var(--pad)',
18 background: 'var(--background)',
19 border: '1px solid var(--tooltip-border)',
20 boxShadow: '0px 2px 5px rgba(0, 0, 0, 0.2)',
21 },
22 hint: {
23 display: 'flex',
24 justifyContent: 'center',
25 marginTop: 'var(--pad)',
26 },
27});
28
29type DraggingOverlayProps = React.HTMLProps<HTMLDivElement> & {
30 /**
31 * Callback ref to update the position of the element.
32 *
33 * It is compatible with the `onDrag: DragHandler` property of `DragHandler`,
34 * or the `clientX`, `clientY` properties of the 'pointermove' event on
35 * `document.body`.
36 */
37 onDragRef: React.MutableRefObject<DragHandler | null>;
38
39 /** X offset. Default: `- var(--pad)`. */
40 dx?: string;
41
42 /** Y offset. Default: `- 50%`. */
43 dy?: string;
44
45 /** Extra "hint" message. Will be rendered as a tooltip. */
46 hint?: string | null;
47};
48
49/**
50 * Render children as the "dragging overlay".
51 *
52 * The callsite needs to update the content (children) and position of
53 * the dragging overlay. For performance, the position update requires
54 * the callsite to call `props.onDragRef.current` instead of using React
55 * props.
56 */
57export function DraggingOverlay(props: DraggingOverlayProps) {
58 const draggingDivRef = useRef<HTMLDivElement | null>(null);
59 const {key, children, onDragRef, dx = '- var(--pad)', dy = '- 50%', hint, ...rest} = props;
60
61 useEffect(() => {
62 const zoom = getZoomLevel();
63 onDragRef.current = (x, y, isDragging) => {
64 const draggingDiv = draggingDivRef.current;
65 if (draggingDiv != null) {
66 if (isDragging) {
67 Object.assign(draggingDiv.style, {
68 transform: `translate(calc(${Math.round(x / zoom)}px ${dx}), calc(${Math.round(
69 y / zoom,
70 )}px ${dy}))`,
71 opacity: '1',
72 });
73 } else {
74 draggingDiv.style.opacity = '0';
75 }
76 }
77 };
78 });
79
80 return (
81 <ViewportOverlay key={key}>
82 <div style={{width: 'fit-content', opacity: 0}} ref={draggingDivRef}>
83 <div {...stylex.props(styles.draggingElement)} {...rest}>
84 {children}
85 </div>
86 {hint != null && (
87 <div {...stylex.props(styles.hint)}>
88 <span className="tooltip" style={{height: 'fit-content'}}>
89 {hint}
90 </span>
91 </div>
92 )}
93 </div>
94 </ViewportOverlay>
95 );
96}
97