Fix mobile preview scaling to fit container (Vibe Kanban) (#2057)
* The fix is in place. Let me summarize the changes made: ## Summary Fixed the mobile preview browser by implementing automatic scaling when the phone frame doesn't fit in the container. ### Changes made: **`frontend/src/components/ui-new/views/PreviewBrowser.tsx`:** 1. Exported constants `MOBILE_WIDTH`, `MOBILE_HEIGHT`, and `PHONE_FRAME_PADDING` for use in the container 2. Added `mobileScale` prop to the component interface 3. Changed the content area's overflow from always `overflow-auto` to `overflow-hidden` when in mobile mode (to prevent scrollbars when the scaled content is smaller) 4. Applied CSS `transform: scale()` to the phone frame when `mobileScale < 1`, using `origin-center` to scale from the center **`frontend/src/components/ui-new/containers/PreviewBrowserContainer.tsx`:** 1. Imported `useLayoutEffect` and the exported constants from `PreviewBrowser` 2. Added `mobileScale` state that calculates the appropriate scale factor 3. Added a `useLayoutEffect` that: - Only runs when in mobile mode - Uses `ResizeObserver` to track container size changes - Calculates scale based on available space vs. phone frame dimensions - Never scales up (only down when needed) 4. Passes `mobileScale` prop to the `PreviewBrowser` component The mobile preview will now automatically scale down to fit within the available container space, preventing the vertical cutoff issue. * Cleanup script changes for workspace abe7b640-b3ea-4716-850e-6fd096ac1a3e
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
import { useCallback, useState, useEffect, useRef } from 'react';
|
||||
import { PreviewBrowser } from '../views/PreviewBrowser';
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import {
|
||||
PreviewBrowser,
|
||||
MOBILE_WIDTH,
|
||||
MOBILE_HEIGHT,
|
||||
PHONE_FRAME_PADDING,
|
||||
} from '../views/PreviewBrowser';
|
||||
import { usePreviewDevServer } from '../hooks/usePreviewDevServer';
|
||||
import { usePreviewUrl } from '../hooks/usePreviewUrl';
|
||||
import {
|
||||
@@ -108,6 +119,44 @@ export function PreviewBrowserContainer({
|
||||
}
|
||||
}, [responsiveDimensions]);
|
||||
|
||||
// Calculate scale for mobile preview to fit container
|
||||
const [mobileScale, setMobileScale] = useState(1);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (screenSize !== 'mobile' || !containerRef.current) {
|
||||
setMobileScale(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const updateScale = () => {
|
||||
const container = containerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
// Get available space (subtract padding from p-double which is typically 32px total)
|
||||
const availableWidth = container.clientWidth - 32;
|
||||
const availableHeight = container.clientHeight - 32;
|
||||
|
||||
// Total phone frame dimensions including padding
|
||||
const totalFrameWidth = MOBILE_WIDTH + PHONE_FRAME_PADDING;
|
||||
const totalFrameHeight = MOBILE_HEIGHT + PHONE_FRAME_PADDING;
|
||||
|
||||
// Calculate scale needed to fit
|
||||
const scaleX = availableWidth / totalFrameWidth;
|
||||
const scaleY = availableHeight / totalFrameHeight;
|
||||
const scale = Math.min(scaleX, scaleY, 1); // Don't scale up, only down
|
||||
|
||||
setMobileScale(scale);
|
||||
};
|
||||
|
||||
updateScale();
|
||||
|
||||
// Observe container size changes
|
||||
const resizeObserver = new ResizeObserver(updateScale);
|
||||
resizeObserver.observe(containerRef.current);
|
||||
|
||||
return () => resizeObserver.disconnect();
|
||||
}, [screenSize]);
|
||||
|
||||
// Handle resize events - register listeners once on mount
|
||||
useEffect(() => {
|
||||
const handleMove = (clientX: number, clientY: number) => {
|
||||
@@ -297,6 +346,7 @@ export function PreviewBrowserContainer({
|
||||
attemptId && repos.length > 0 ? handleFixDevScript : undefined
|
||||
}
|
||||
hasFailedDevServer={hasFailedDevServer}
|
||||
mobileScale={mobileScale}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -25,8 +25,10 @@ import type {
|
||||
ResponsiveDimensions,
|
||||
} from '@/hooks/usePreviewSettings';
|
||||
|
||||
const MOBILE_WIDTH = 390;
|
||||
const MOBILE_HEIGHT = 844;
|
||||
export const MOBILE_WIDTH = 390;
|
||||
export const MOBILE_HEIGHT = 844;
|
||||
// Phone frame adds padding (p-3 = 12px * 2) and rounded corners
|
||||
export const PHONE_FRAME_PADDING = 24;
|
||||
|
||||
interface PreviewBrowserProps {
|
||||
url?: string;
|
||||
@@ -56,6 +58,7 @@ interface PreviewBrowserProps {
|
||||
handleEditDevScript: () => void;
|
||||
handleFixDevScript?: () => void;
|
||||
hasFailedDevServer?: boolean;
|
||||
mobileScale: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -85,6 +88,7 @@ export function PreviewBrowser({
|
||||
handleEditDevScript,
|
||||
handleFixDevScript,
|
||||
hasFailedDevServer,
|
||||
mobileScale,
|
||||
className,
|
||||
}: PreviewBrowserProps) {
|
||||
const { t } = useTranslation(['tasks', 'common']);
|
||||
@@ -260,20 +264,27 @@ export function PreviewBrowser({
|
||||
{/* Content area */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex-1 min-h-0 relative overflow-auto px-double pb-double"
|
||||
className={cn(
|
||||
'flex-1 min-h-0 relative px-double pb-double',
|
||||
screenSize === 'mobile' ? 'overflow-hidden' : 'overflow-auto'
|
||||
)}
|
||||
>
|
||||
{showIframe ? (
|
||||
<div
|
||||
className={cn(
|
||||
'h-full',
|
||||
screenSize === 'desktop'
|
||||
? ''
|
||||
: 'flex items-center justify-center p-double'
|
||||
screenSize === 'desktop' ? '' : 'flex items-center justify-center'
|
||||
)}
|
||||
>
|
||||
{screenSize === 'mobile' ? (
|
||||
// Phone frame for mobile mode
|
||||
<div className="bg-primary rounded-[2rem] p-3 shadow-xl">
|
||||
// Phone frame for mobile mode - scales down to fit container
|
||||
<div
|
||||
className="bg-primary rounded-[2rem] p-3 shadow-xl origin-center"
|
||||
style={{
|
||||
transform:
|
||||
mobileScale < 1 ? `scale(${mobileScale})` : undefined,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="rounded-[1.5rem] overflow-hidden"
|
||||
style={{ width: MOBILE_WIDTH, height: MOBILE_HEIGHT }}
|
||||
|
||||
Reference in New Issue
Block a user