PluginProbe
Extendify / trunk
Extendify vtrunk
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
← All changes | src/Agent/components/layouts/SidebarLayout.jsx +74 -52 3.1.3 → trunk View file →
@@ -1,6 +1,12 @@
1 +import {
2 + CANVAS_PANE_WIDTH,
3 + CanvasPane,
4 + DOT_GRID,
5 + useCanvasSeat,
6 +} from '@agent/components/Canvas';
1 7 import { usePortal } from '@agent/hooks/usePortal';
2 -import { useGlobalStore } from '@agent/state/global';
8 +import { DESKTOP_MIN_WIDTH, useGlobalStore } from '@agent/state/global';
3 9 import { createPortal, useEffect, useRef } from '@wordpress/element';
4 10 import { __ } from '@wordpress/i18n';
5 11 import { close, Icon } from '@wordpress/icons';
6 12 import { motion } from 'framer-motion';
@@ -5,9 +11,10 @@
5 11 import { close, Icon } from '@wordpress/icons';
6 12 import { motion } from 'framer-motion';
7 13 import { OptionsPopover } from '../OptionsPopover';
8 14
9 -const SIDEBAR_WIDTH = 384; // 96 * 4 (w-96)
15 +const SIDEBAR_WIDTH = 384;
16 +const CANVAS_WIDTH = SIDEBAR_WIDTH + CANVAS_PANE_WIDTH;
10 17 const FRAME_WIDTH = 8; // border-8
11 18 const ANIMATE_TIME = 300;
12 19
13 20 export const SidebarLayout = ({ children }) => {
@@ -13,8 +20,10 @@
13 20 export const SidebarLayout = ({ children }) => {
14 21 const mountNode = usePortal('extendify-agent-sidebar-mount');
15 22 const frameNode = usePortal('extendify-agent-border-frame-mount');
16 23 const { open, setOpen } = useGlobalStore();
24 + const seat = useCanvasSeat();
25 + const beside = seat === 'beside';
17 26 useLayoutShift(open);
18 27
19 28 const closeAgent = () => {
20 29 setOpen(false);
@@ -47,11 +56,12 @@
47 56 borderRadius: 0,
48 57 },
49 58 };
50 59
60 + // Unclipped, the 9999px shadow covers the notification bar.
51 61 const frameBar = frameNode
52 62 ? createPortal(
53 - <div className="fixed inset-0 pointer-events-none z-high">
63 + <div className="fixed top-0 left-0 right-0 bottom-(--extendify-notification-bar-height,0px) overflow-hidden pointer-events-none z-high">
54 64 <motion.div
55 65 className="absolute overflow-hidden"
56 66 initial={false}
57 67 animate={open ? 'open' : 'closed'}
@@ -77,49 +87,70 @@
77 87 frameNode,
78 88 )
79 89 : null;
80 90
91 + // app/Agent/Skeleton.php mirrors this markup for the deferred mount; edit both.
81 92 const sidebar = mountNode
82 93 ? createPortal(
83 94 <motion.div
84 - style={{ width: SIDEBAR_WIDTH }}
85 - className=" fixed top-0 bottom-0 left-0 w-96 flex-col z-higher border-transparent border-8"
95 + className={`fixed top-0 bottom-[var(--extendify-notification-bar-height,0px)] left-0 border-transparent border-8 ${
96 + // An emptied panel belongs under the scrim with the rest of the page.
97 + seat === 'modal' ? 'z-high' : 'z-higher'
98 + }`}
86 99 id="extendify-agent-sidebar"
100 + data-extendify-agent-panel
101 + data-extendify-agent-emptied={seat === 'modal' ? '' : undefined}
87 102 initial={false}
88 103 inert={open ? undefined : ''}
89 - animate={{ x: open ? 0 : -SIDEBAR_WIDTH }}
104 + animate={{
105 + x: open ? 0 : -SIDEBAR_WIDTH,
106 + width: beside ? CANVAS_WIDTH : SIDEBAR_WIDTH,
107 + }}
90 108 transition={{ duration: ANIMATE_TIME / 1000, ease: 'easeInOut' }}
91 109 >
92 - <div className="h-full flex flex-col shadow-lg rounded-2xl overflow-hidden bg-white">
93 - <div className="group flex shrink-0 items-center justify-between overflow-hidden bg-banner-main text-banner-text">
94 - <div className="flex h-full grow items-center justify-between gap-1 p-0 py-2.5">
95 - <div className="flex h-5 px-4 max-w-36 overflow-hidden">
96 - <img
97 - className="max-h-full max-w-full object-contain"
98 - src={window.extSharedData.partnerLogo}
99 - alt={window.extSharedData.partnerName}
100 - />
110 + <div
111 + className={`relative h-full flex shadow-lg rounded-2xl overflow-hidden ${beside ? 'bg-gray-50' : 'bg-white'}`}
112 + style={beside ? DOT_GRID : undefined}
113 + >
114 + <div
115 + className="relative z-10 h-full flex shrink-0 flex-col rounded-2xl overflow-hidden bg-white shadow-lg"
116 + style={{ width: SIDEBAR_WIDTH - FRAME_WIDTH * 2 }}
117 + >
118 + <div className="group flex shrink-0 items-center justify-between overflow-hidden bg-banner-main text-banner-text">
119 + <div className="flex h-full grow items-center justify-between gap-1 p-0 py-2.5">
120 + <div className="flex h-5 px-4 max-w-36 overflow-hidden">
121 + <img
122 + className="max-h-full max-w-full object-contain"
123 + src={window.extSharedData.partnerLogo}
124 + alt={window.extSharedData.partnerName}
125 + />
126 + </div>
101 127 </div>
128 + <div className="flex gap-1 h-full items-center p-2">
129 + {seat ? null : (
130 + <>
131 + <OptionsPopover />
132 + <button
133 + type="button"
134 + className="relative z-10 flex justify-center h-6 w-6 items-center border-0 bg-banner-main text-banner-text outline-hidden ring-design-main focus:shadow-none focus:outline-hidden focus-visible:outline-design-main focus:ring-2 hover:opacity-80 rounded-sm"
135 + onClick={closeAgent}
136 + >
137 + <Icon
138 + className="pointer-events-none fill-current leading-none"
139 + icon={close}
140 + size={18}
141 + />
142 + <span className="sr-only">
143 + {__('Close window', 'extendify-local')}
144 + </span>
145 + </button>
146 + </>
147 + )}
148 + </div>
102 149 </div>
103 - <div className="flex gap-1 h-full items-center p-2">
104 - <OptionsPopover />
105 - <button
106 - type="button"
107 - className="relative z-10 flex justify-center h-6 w-6 items-center border-0 bg-banner-main text-banner-text outline-hidden ring-design-main focus:shadow-none focus:outline-hidden focus-visible:outline-design-main focus:ring-2 hover:opacity-80 rounded-sm"
108 - onClick={closeAgent}
109 - >
110 - <Icon
111 - className="pointer-events-none fill-current leading-none"
112 - icon={close}
113 - size={18}
114 - />
115 - <span className="sr-only">
116 - {__('Close window', 'extendify-local')}
117 - </span>
118 - </button>
119 - </div>
150 + {open ? children : null}
120 151 </div>
121 - {open ? children : null}
152 + <CanvasPane seat="beside" />
122 153 </div>
123 154 </motion.div>,
124 155 mountNode,
125 156 )
@@ -193,9 +224,10 @@
193 224
194 225 const applyScaling = () => {
195 226 if (!siteBlocks) return;
196 227
197 - if (open) {
228 + // Unmount leaves these styles, so scaling below the breakpoint is permanent.
229 + if (open && window.innerWidth >= DESKTOP_MIN_WIDTH) {
198 230 // Capture before `position: fixed` zeroes window.scrollY.
199 231 // Fall through to savedScroll.current so resize / strict-mode
200 232 // re-runs don't clobber it with the now-pinned scrollY (0).
201 233 const stashed = isFirstApply ? consumeStashedScroll() : 0;
@@ -205,17 +237,20 @@
205 237 const scale = (viewportWidth - SIDEBAR_WIDTH) / viewportWidth;
206 238
207 239 // Subtract 40 because translateY(40px) below pushes the element down.
208 240 const scaledHeight = (window.innerHeight - 40) / scale;
241 + // Divided by the scale because this height is in pre-scale units.
242 + const height =
243 + `calc(${scaledHeight}px - ` +
244 + `var(--extendify-notification-bar-height, 0px) / ${scale})`;
209 245
210 246 Object.assign(siteBlocks.style, {
211 247 transformOrigin: 'top left',
212 248 transform: `translateX(${SIDEBAR_WIDTH}px) translateY(40px) scale(${scale})`,
213 - height: `${scaledHeight}px`,
249 + height,
214 250 overflowY: 'auto',
215 251 // `auto` so the scrollTop below is instant; `smooth` would animate from 0.
216 252 scrollBehavior: 'auto',
217 - colorScheme: 'light',
218 253 });
219 254 // Force layout so scrollTop respects the new height/overflow.
220 255 void siteBlocks.scrollHeight;
221 256 siteBlocks.scrollTop = savedScroll.current;
@@ -240,9 +275,8 @@
240 275 height: '',
241 276 overflowY: '',
242 277 maxWidth: '100vw',
243 278 scrollBehavior: '',
244 - colorScheme: '',
245 279 });
246 280 if (stickyHeader) {
247 281 stickyHeader.style.removeProperty(
248 282 '--wp-admin--admin-bar--position-offset',
@@ -278,11 +312,8 @@
278 312 firstRun.current = false;
279 313 }
280 314
281 315 const raf = requestAnimationFrame(() => {
282 - const fw = open ? `${FRAME_WIDTH}px` : '0px';
283 - const ml = open ? `${SIDEBAR_WIDTH}px` : '0px';
284 -
285 316 applyScaling();
286 317
287 318 // External contract: no in-repo listener by design — lets host-page
288 319 // consumers react to the agent reflowing the viewport.
@@ -291,20 +322,11 @@
291 322 detail: { open },
292 323 }),
293 324 );
294 325
295 - if (wpadminbar) {
296 - Object.assign(wpadminbar.style, {
297 - marginTop: fw,
298 - marginRight: fw,
299 - marginBottom: '0px',
300 - marginLeft: ml,
301 - borderRadius: open ? '8px 8px 0 0' : '0',
302 - maxWidth: open
303 - ? `calc(100% - ${SIDEBAR_WIDTH + FRAME_WIDTH}px)`
304 - : '100%',
305 - });
306 - }
326 + // agent.css carries the bar offsets, so they drop with the
327 + // breakpoint rather than with this component.
328 + document.documentElement.classList.toggle('extendify-agent-docked', open);
307 329 });
308 330
309 331 window.addEventListener('resize', applyScaling);
310 332