fix: crop tool overflows viewport with tall portrait images

The crop area flex container lacked min-h-0, preventing it from shrinking
below its content's intrinsic height. For tall images, react-image-crop's
CSS (max-height: inherit at specificity 0-1-1) overrides the Tailwind
max-h constraint, and without min-h-0 the flex item refuses to shrink,
causing viewport overflow.

Closes #122
This commit is contained in:
SnapOtter
2026-05-05 17:51:01 +08:00
parent e358634f8b
commit 1807a43f2d
2 changed files with 31 additions and 1 deletions
@@ -98,7 +98,7 @@ export function CropCanvas({
return (
<div ref={containerRef} className="flex flex-col w-full h-full max-w-4xl mx-auto outline-none">
{/* Crop area */}
<div className="flex-1 flex items-center justify-center overflow-hidden bg-muted/20 p-4">
<div className="flex-1 flex items-center justify-center overflow-hidden bg-muted/20 p-4 min-h-0">
<ReactCrop
crop={crop}
onChange={(_pixelCrop, percentCrop) => onCropChange(percentCrop)}
+30
View File
@@ -1,3 +1,5 @@
import { execFileSync } from "node:child_process";
import path from "node:path";
import { expect, test, uploadTestImage, waitForProcessing } from "./helpers";
// ---------------------------------------------------------------------------
@@ -195,6 +197,34 @@ test.describe("GUI Essential Tools", () => {
await expect(page.getByTestId("crop-download")).toBeVisible({ timeout: 15_000 });
});
test("tall portrait image fits within viewport without overflow", async ({
loggedInPage: page,
}) => {
await page.goto("/crop");
// Create a tall portrait image (200x2000) to trigger overflow
const portraitPath = path.join(process.cwd(), "test-results", "test-portrait-tall.png");
const script = [
"const sharp = require('sharp');",
`sharp({create:{width:200,height:2000,channels:4,background:{r:0,g:128,b:255,alpha:1}}}).png().toFile('${portraitPath.replace(/'/g, "\\'")}')`,
].join(" ");
execFileSync("node", ["-e", script], { cwd: process.cwd(), timeout: 5000 });
const fileChooserPromise = page.waitForEvent("filechooser");
await page.locator("[class*='border-dashed']").first().click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(portraitPath);
await page.waitForTimeout(1000);
const img = page.locator(".ReactCrop img");
await expect(img).toBeVisible();
const viewport = page.viewportSize()!;
const box = await img.boundingBox();
expect(box).not.toBeNull();
expect(box!.y + box!.height).toBeLessThanOrEqual(viewport.height);
});
});
// ========================================================================