UNPKG

3.55 kBJavaScriptView Raw
1import React, { useRef, useEffect, useState } from 'react';
2import { useSpring, animated, to } from '@react-spring/web';
3import { useDrag } from '@use-gesture/react';
4import { mergeProps } from '../../utils/with-default-props';
5import { withNativeProps } from '../../utils/native-props';
6const classPrefix = `adm-floating-bubble`;
7const defaultProps = {
8 axis: 'y',
9 defaultOffset: {
10 x: 0,
11 y: 0
12 }
13};
14export const FloatingBubble = p => {
15 const props = mergeProps(defaultProps, p);
16 const boundaryRef = useRef(null);
17 const buttonRef = useRef(null);
18 const [innerValue, setInnerValue] = useState(props.offset === undefined ? props.defaultOffset : props.offset);
19 useEffect(() => {
20 if (props.offset === undefined) return;
21 api.start({
22 x: props.offset.x,
23 y: props.offset.y
24 });
25 }, [props.offset]);
26 /**
27 * memoize the `to` function
28 * inside a component that renders frequently
29 * to prevent an unintended restart
30 */
31 const [{
32 x,
33 y,
34 opacity
35 }, api] = useSpring(() => ({
36 x: innerValue.x,
37 y: innerValue.y,
38 opacity: 1
39 }));
40 const bind = useDrag(state => {
41 var _a;
42 let nextX = state.offset[0];
43 let nextY = state.offset[1];
44 if (state.last && props.magnetic) {
45 const boundary = boundaryRef.current;
46 const button = buttonRef.current;
47 if (!boundary || !button) return;
48 const boundaryRect = boundary.getBoundingClientRect();
49 const buttonRect = button.getBoundingClientRect();
50 if (props.magnetic === 'x') {
51 const compensation = x.goal - x.get();
52 const leftDistance = buttonRect.left + compensation - boundaryRect.left;
53 const rightDistance = boundaryRect.right - (buttonRect.right + compensation);
54 if (rightDistance <= leftDistance) {
55 nextX += rightDistance;
56 } else {
57 nextX -= leftDistance;
58 }
59 } else if (props.magnetic === 'y') {
60 const compensation = y.goal - y.get();
61 const topDistance = buttonRect.top + compensation - boundaryRect.top;
62 const bottomDistance = boundaryRect.bottom - (buttonRect.bottom + compensation);
63 if (bottomDistance <= topDistance) {
64 nextY += bottomDistance;
65 } else {
66 nextY -= topDistance;
67 }
68 }
69 }
70 const nextOffest = {
71 x: nextX,
72 y: nextY
73 };
74 if (props.offset === undefined) {
75 // Uncontrolled mode
76 api.start(nextOffest);
77 } else {
78 setInnerValue(nextOffest);
79 }
80 (_a = props.onOffsetChange) === null || _a === void 0 ? void 0 : _a.call(props, nextOffest);
81 // active status
82 api.start({
83 opacity: state.active ? 0.8 : 1
84 });
85 }, {
86 axis: props.axis === 'xy' ? undefined : props.axis,
87 pointer: {
88 touch: true
89 },
90 // the component won't trigger drag logic if the user just clicked on the component.
91 filterTaps: true,
92 // set constraints to the user gesture
93 bounds: boundaryRef,
94 from: () => [x.get(), y.get()]
95 });
96 return withNativeProps(props, React.createElement("div", {
97 className: classPrefix
98 }, React.createElement("div", {
99 className: `${classPrefix}-boundary-outer`
100 }, React.createElement("div", {
101 className: `${classPrefix}-boundary`,
102 ref: boundaryRef
103 })), React.createElement(animated.div, Object.assign({}, bind(), {
104 style: {
105 opacity,
106 transform: to([x, y], (x, y) => `translate(${x}px, ${y}px)`)
107 },
108 onClick: props.onClick,
109 className: `${classPrefix}-button`,
110 ref: buttonRef
111 }), props.children)));
112};
\No newline at end of file