actual/packages/loot-design/src/components/sort.js
Tom French dc53a74459
Separate external, monorepo and internal imports (#237)
* style: enforce ordering of imports

* style: sort imports in loot-core

* style: sort imports in desktop-client

* style: sort imports in loot-design

* style: manual fixes
2022-09-02 12:43:37 +01:00

139 lines
3.1 KiB
JavaScript

import React, {
useEffect,
useRef,
useLayoutEffect,
useMemo,
useState,
useContext
} from 'react';
import { useDrag, useDrop } from 'react-dnd';
import { colors } from '../style';
import { View } from './common';
export function useMergedRefs(ref1, ref2) {
return useMemo(() => {
function ref(value) {
[ref1, ref2].forEach(ref => {
if (typeof ref === 'function') {
ref(value);
} else if (ref != null) {
ref.current = value;
}
});
}
return ref;
}, [ref1, ref2]);
}
export function useDraggable({
item,
type,
makePreview,
children,
canDrag,
onDragChange
}) {
let _onDragChange = useRef(onDragChange);
//eslint-disable-next-line
const [{ isDragging }, dragRef] = useDrag({
item: { type, item },
collect: monitor => ({ isDragging: monitor.isDragging() }),
begin(monitor) {
_onDragChange.current({ state: 'start-preview', type, item });
setTimeout(() => {
_onDragChange.current({ state: 'start' });
}, 0);
},
end(item, monitor) {
_onDragChange.current({ state: 'end', type, item });
},
canDrag() {
return canDrag;
}
});
useLayoutEffect(() => {
_onDragChange.current = onDragChange;
});
return { dragRef };
}
export function useDroppable({ types, id, onDrop, onLongHover }) {
let ref = useRef(null);
let [dropPos, setDropPos] = useState(null);
// eslint-disable-next-line
let [{ isOver }, dropRef] = useDrop({
accept: types,
drop({ item }, monitor) {
onDrop(item.id, dropPos, id);
},
hover({ item, type }, monitor) {
let hoverBoundingRect = ref.current.getBoundingClientRect();
let hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
let clientOffset = monitor.getClientOffset();
let hoverClientY = clientOffset.y - hoverBoundingRect.top;
let pos = hoverClientY < hoverMiddleY ? 'top' : 'bottom';
setDropPos(pos);
},
collect(monitor) {
return { isOver: monitor.isOver() };
}
});
useEffect(() => {
let timeout;
if (onLongHover && isOver) {
timeout = setTimeout(onLongHover, 700);
}
return () => timeout && clearTimeout(timeout);
}, [isOver]);
return {
dropRef: useMergedRefs(dropRef, ref),
dropPos: isOver ? dropPos : null
};
}
export const DropHighlightPosContext = React.createContext(null);
export function DropHighlight({ pos, offset = {} }) {
if (pos == null) {
return null;
}
let itemPos = useContext(DropHighlightPosContext);
let topOffset = (itemPos === 'first' ? 2 : 0) + (offset.top || 0);
let bottomOffset = (itemPos === 'last' ? 2 : 0) + (offset.bottom || 0);
let posStyle =
pos === 'top' ? { top: -2 + topOffset } : { bottom: -1 + bottomOffset };
return (
<View
style={[
{
position: 'absolute',
left: 2,
right: 2,
borderRadius: 3,
height: 3,
background: `linear-gradient(90deg, ${colors.b4} 0%, ${colors.b5} 100%)`,
zIndex: 10000,
pointerEvents: 'none'
},
posStyle
]}
/>
);
}