PluginProbe
Extendify / 3.0.6
Extendify v3.0.6
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Agent / components / layouts / DragResizeLayout.jsx

DragResizeLayout.jsx in Extendify 3.0.6, at src/Agent/components/layouts/DragResizeLayout.jsx

158 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { useDraggable } from '@agent/hooks/useDraggable';
2 import { usePortal } from '@agent/hooks/usePortal';
3 import { useResizable } from '@agent/hooks/useResizable';
4 import { useGlobalStore } from '@agent/state/global';
5 import { usePositionStore } from '@agent/state/position';
6 import {
7 createPortal,
8 useCallback,
9 useEffect,
10 useState,
11 } from '@wordpress/element';
12 import { __ } from '@wordpress/i18n';
13 import { close, Icon } from '@wordpress/icons';
14 import { AnimatePresence, motion } from 'framer-motion';
15
16 // TODO: some of the bound checking isn't working that well.
17 // For example, the user shrinks the height of the browser.
18 // On reload this should at least put the window in bounds.
19 // Maybe a resize observer would be useful.
20
21 export const DragResizeLayout = ({ children }) => {
22 const [el, setEl] = useState(null);
23 const mountNode = usePortal('extendify-agent-mount');
24 const { open, setOpen } = useGlobalStore();
25
26 // So it will re-render the hooks when mounted
27 const ref = useCallback(
28 (node) => requestAnimationFrame(() => setEl(node)),
29 [],
30 );
31 useDraggable({
32 el,
33 open,
34 initialPosition: usePositionStore.getState(),
35 onDragEnd: (x, y) => {
36 usePositionStore.getState().setPosition(x, y);
37 window.dispatchEvent(
38 new CustomEvent('extendify-agent:drag-end', { detail: { x, y } }),
39 );
40 },
41 });
42 useResizable({
43 el,
44 open,
45 initialSize: usePositionStore.getState(),
46 onResizeEnd: (width, height) => {
47 usePositionStore.setState({ width, height });
48 window.dispatchEvent(
49 new CustomEvent('extendify-agent:resize-end', {
50 detail: { width, height },
51 }),
52 );
53 },
54 });
55
56 useEffect(() => {
57 if (!el || !open) return;
58 // If it's not intersecting, close it
59 const observer = new IntersectionObserver((entries) => {
60 if (!entries[0].isIntersecting) setOpen(false);
61 });
62 observer.observe(el);
63 return () => observer.disconnect();
64 }, [el, open, setOpen]);
65
66 const closeAgent = () => {
67 setOpen(false);
68 window.dispatchEvent(new CustomEvent('extendify-agent:closed-button'));
69 };
70
71 if (!mountNode || !open) return null;
72
73 return createPortal(
74 <AnimatePresence>
75 <motion.div
76 key="agent-popout-modal"
77 id="extendify-agent-popout-modal"
78 initial={{ opacity: 0 }}
79 animate={{ opacity: 1 }}
80 exit={{ y: 0, opacity: 0 }}
81 transition={{ duration: 0.4, delay: 0.1 }}
82 className="fixed bottom-0 right-0 z-higher flex max-h-full max-w-full flex-col rounded-lg border border-solid border-gray-300 bg-white shadow-2xl-flipped rtl:left-0 rtl:right-auto"
83 ref={ref}
84 >
85 <div className="group flex shrink-0 items-center justify-between overflow-hidden rounded-t-[calc(0.5rem-1px)] bg-banner-main text-banner-text">
86 <div
87 data-extendify-agent-handle
88 draggable
89 className="flex h-full grow cursor-grab active:cursor-grabbing select-none items-center justify-between gap-1 p-0 py-3"
90 >
91 <div className="flex h-5 px-4 max-w-36 overflow-hidden">
92 <img
93 className="max-h-full max-w-full object-contain"
94 src={window.extSharedData.partnerLogo}
95 alt={window.extSharedData.partnerName}
96 />
97 </div>
98 <div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
99 <DragButton />
100 </div>
101 </div>
102 <button
103 type="button"
104 className="relative z-10 flex h-full items-center rounded-none border-0 bg-banner-main py-3 pe-4 ps-2 text-banner-text outline-hidden ring-design-main focus:shadow-none focus:outline-hidden focus-visible:outline-design-main"
105 onClick={closeAgent}
106 >
107 <Icon
108 className="pointer-events-none fill-current leading-none"
109 icon={close}
110 size={18}
111 />
112 <span className="sr-only">
113 {__('Close window', 'extendify-local')}
114 </span>
115 </button>
116 </div>
117 {children}
118 <div
119 data-extendify-agent-resize
120 className="absolute -bottom-2 -right-2 z-high h-6 w-6 group"
121 >
122 <div className="h-6 w-6 cursor-se-resize group-active:cursor-nwse-resize" />
123 </div>
124 </motion.div>
125 </AnimatePresence>,
126 mountNode,
127 );
128 };
129
130 const DragButton = (props) => {
131 return (
132 <div style={{ userSelect: 'none' }} className="relative flex" {...props}>
133 <Icon
134 className="pointer-events-none text-banner-text"
135 icon={
136 <svg
137 xmlns="http://www.w3.org/2000/svg"
138 viewBox="0 0 24 24"
139 width="24"
140 height="24"
141 className="pointer-events-none"
142 aria-hidden="true"
143 focusable="false"
144 >
145 {/* hardcoded to use currentColor */}
146 <path
147 fill="currentColor"
148 d="M8 7h2V5H8v2zm0 6h2v-2H8v2zm0 6h2v-2H8v2zm6-14v2h2V5h-2zm0 8h2v-2h-2v2zm0 6h2v-2h-2v2z"
149 ></path>
150 </svg>
151 }
152 size={24}
153 />
154 <span className="sr-only">{__('Drag to move', 'extendify-local')}</span>
155 </div>
156 );
157 };
158