fix(dashboard): size the web-session cookie modal to fit on 1080p (#6265) (#6601)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-07 21:01:45 -03:00
committed by GitHub
parent cc5f596b5f
commit 6d649d3d3f
4 changed files with 92 additions and 3 deletions

View File

@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { useTranslations } from "next-intl";
import { Button, Badge, Input, Modal, Toggle } from "@/shared/components";
import { Button, Badge, Input, Modal, Toggle, TALL_MODAL_PROPS } from "@/shared/components";
import {
providerAllowsOptionalApiKey,
supportsBulkApiKey,
@@ -445,7 +445,7 @@ export default function AddApiKeyModal({
title={getAddCredentialModalTitle(t, providerDisplayName, webSessionCredential)}
onClose={onClose}
size="lg"
bodyClassName="p-6 max-h-[85vh] overflow-y-auto"
{...TALL_MODAL_PROPS}
>
<div className="flex flex-col gap-4">
{webProviderHostLink && (

View File

@@ -4,6 +4,14 @@ import { useEffect, useRef, useId } from "react";
import { cn } from "@/shared/utils/cn";
import Button from "./Button";
// #6265 — preset for content-heavy modals: caps height on the OUTERMOST dialog
// wrapper only (single scroll owner) and keeps the inner body plain (no
// independent max-h/overflow), avoiding a double height cap that clips content.
export const TALL_MODAL_PROPS = {
className: "max-h-[90vh] overflow-y-auto",
bodyClassName: "p-6",
};
interface ModalProps {
isOpen: boolean;
onClose: () => void;

View File

@@ -6,7 +6,7 @@ export { default as Checkbox } from "./Checkbox";
export { default as Textarea } from "./Textarea";
export { default as Card } from "./Card";
export { default as Collapsible } from "./Collapsible";
export { default as Modal, ConfirmModal } from "./Modal";
export { default as Modal, ConfirmModal, TALL_MODAL_PROPS } from "./Modal";
export { default as Loading, Spinner, PageLoading, Skeleton, CardSkeleton } from "./Loading";
export { default as Avatar } from "./Avatar";
export { default as Badge } from "./Badge";

View File

@@ -0,0 +1,81 @@
// @vitest-environment jsdom
// Issue #6265 — the "Add session cookie" modal (AddApiKeyModal, shared by every
// `-web` cookie provider) was undersized on a 1920x1080 viewport: users had to
// scroll *inside* the modal to reach Save, and the top of the cookie helper text
// was clipped. Root cause: the height cap (`max-h-*` + `overflow-y-auto`) lived on
// the INNER body div only, while the OUTERMOST dialog wrapper had no height bound
// at all — so the whole box (header + body) could grow taller than the viewport
// and get clipped by the centering flexbox, independent of the inner scrollbar.
//
// Fix: move the single height cap to the outermost dialog wrapper (`role="dialog"`)
// and stop giving the inner body container its own independent `max-h-`/`overflow`
// cap, so there is exactly one scroll owner for the whole modal.
import React, { act } from "react";
import { createRoot } from "react-dom/client";
import { describe, it, expect, vi, afterEach } from "vitest";
vi.mock("next-intl", () => ({
useTranslations: () => (key: string) => key,
}));
const { default: AddApiKeyModal } =
await import("../../../src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal");
const containers: Array<{ root: ReturnType<typeof createRoot>; el: HTMLDivElement }> = [];
function render(props: Record<string, unknown>) {
const el = document.createElement("div");
document.body.appendChild(el);
const root = createRoot(el);
act(() => {
root.render(
<AddApiKeyModal
isOpen
onSave={async () => undefined}
onClose={() => {}}
{...(props as any)}
/>
);
});
containers.push({ root, el });
return el;
}
afterEach(() => {
for (const { root, el } of containers.splice(0)) {
act(() => root.unmount());
el.remove();
}
});
describe("AddApiKeyModal — cookie modal sizing (#6265)", () => {
it("caps height on the OUTERMOST dialog wrapper, not on an inner body div", () => {
// chatgpt-web is a `kind: "cookie"` web-session provider — same shared modal
// path lmarena/claude-web/gemini-web/kimi-web/z-ai all go through.
const el = render({ provider: "chatgpt-web", providerName: "ChatGPT (Web)" });
const dialog = el.querySelector<HTMLElement>('[role="dialog"]');
expect(dialog).toBeTruthy();
// The outermost wrapper must be the single owner of the height cap + scroll.
expect(dialog!.className).toMatch(/max-h-\[90vh\]/);
expect(dialog!.className).toMatch(/overflow-y-auto/);
// The body container (last child of the dialog — header is first, no footer
// prop is used by AddApiKeyModal) must NOT carry its own independent max-h/
// overflow cap — otherwise the outer cap and the inner cap fight (double cap),
// clipping content before the outer 90vh bound ever kicks in.
const bodyDiv = dialog!.children[dialog!.children.length - 1] as HTMLElement;
expect(bodyDiv).toBeTruthy();
expect(bodyDiv.className).not.toMatch(/max-h-/);
expect(bodyDiv.className).not.toMatch(/overflow-y-auto/);
// Sanity: the cookie helper text and Save button are both present in the DOM
// (this modal renders the full guide + form Save/Cancel inline in the body).
expect(el.textContent).toContain("How to get the session credential");
const saveBtn = Array.from(el.querySelectorAll("button")).find(
(b) => b.textContent?.trim() === "save"
);
expect(saveBtn).toBeTruthy();
});
});