diff --git a/frontend/src/components/preview/PreviewComponent.tsx b/frontend/src/components/preview/PreviewComponent.tsx index c2231fe67..4c2ad653b 100644 --- a/frontend/src/components/preview/PreviewComponent.tsx +++ b/frontend/src/components/preview/PreviewComponent.tsx @@ -24,6 +24,14 @@ interface Props { const MOBILE_VIEWPORT_WIDTH = 375; export const DESKTOP_VIEWPORT_WIDTH = 1366; +// Chrome dimensions, measured at the *unscaled* viewport size so they +// scale down together with the rest of the frame (a shrunk browser +// window should look like a shrunk browser window, toolbar included). +const BROWSER_TOOLBAR_HEIGHT = 36; +const PHONE_BEZEL_WIDTH = 10; +const PHONE_NOTCH_HEIGHT = 24; +const PHONE_HOME_INDICATOR_HEIGHT = 20; + function PreviewComponent({ code, device, @@ -31,6 +39,7 @@ function PreviewComponent({ viewMode, }: Props) { const iframeRef = useRef(null); + const frameRef = useRef(null); const wrapperRef = useRef(null); // Don't update code more often than every 200ms. @@ -175,11 +184,14 @@ function PreviewComponent({ }, [inSelectAndEditMode]); // eslint-disable-line react-hooks/exhaustive-deps // Apply a fixed viewport per device and scale to fit the available pane. + // The transform/size is applied to the *frame* (chrome + iframe together) + // so the browser toolbar / phone bezel scale down in proportion, the same + // way a real shrunk browser window looks. useEffect(() => { const updateScale = () => { const wrapper = wrapperRef.current; - const iframe = iframeRef.current; - if (!wrapper || !iframe) return; + const frame = frameRef.current; + if (!wrapper || !frame) return; const viewportWidth = wrapper.clientWidth; const viewportHeight = wrapper.clientHeight; @@ -189,21 +201,21 @@ function PreviewComponent({ activeMode === "fit" ? Math.min(1, viewportWidth / DESKTOP_VIEWPORT_WIDTH) : 1; - const iframeHeight = scaleValue > 0 ? viewportHeight / scaleValue : viewportHeight; + const frameHeight = scaleValue > 0 ? viewportHeight / scaleValue : viewportHeight; onScaleChange?.(scaleValue); - iframe.style.width = `${DESKTOP_VIEWPORT_WIDTH}px`; - iframe.style.height = `${iframeHeight}px`; - iframe.style.transform = `scale(${scaleValue})`; - iframe.style.transformOrigin = "top left"; + frame.style.width = `${DESKTOP_VIEWPORT_WIDTH}px`; + frame.style.height = `${frameHeight}px`; + frame.style.transform = `scale(${scaleValue})`; + frame.style.transformOrigin = "top left"; return; } onScaleChange?.(1); - iframe.style.width = `${MOBILE_VIEWPORT_WIDTH}px`; - iframe.style.height = `${viewportHeight}px`; - iframe.style.transform = "scale(1)"; - iframe.style.transformOrigin = "top left"; + frame.style.width = `${MOBILE_VIEWPORT_WIDTH}px`; + frame.style.height = `${viewportHeight}px`; + frame.style.transform = "scale(1)"; + frame.style.transformOrigin = "top left"; }; updateScale(); @@ -306,16 +318,56 @@ function PreviewComponent({ ref={wrapperRef} className={`w-full h-full ${device === "mobile" ? "flex justify-center" : ""}`} > - + {device === "mobile" && ( +
+ +
)} - > + );