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:
Anastasiia Solop
2026-01-15 11:14:02 +01:00
committed by GitHub
parent ea5954c8f5
commit e80af6b8e5
2 changed files with 71 additions and 10 deletions

View File

@@ -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}
/>
);

View File

@@ -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 }}