Files
multica/packages/views/modals/create-issue-dialog.tsx
Bohan Jiang 7528022355 fix(quick-create): bound dialog height + scroll editor when content overflows (#1847)
Pasting a screenshot into the agent-create prompt expanded the editor
unbounded, which dragged DialogContent past the viewport since the agent
mode className had no max-height. Manual mode was unaffected because
manualDialogContentClass pins `!h-96`.

- Cap agent-mode DialogContent at `!max-h-[80vh]` (width stays
  `!max-w-xl`); short prompts still render compact, tall content stops
  at 80% of the viewport.
- Switch the editor wrapper to `flex-1 min-h-[140px] overflow-y-auto`
  so it absorbs the remaining vertical space inside the now-bounded
  DialogContent and scrolls internally instead of pushing the dialog.
2026-04-29 16:49:56 +08:00

98 lines
3.5 KiB
TypeScript

"use client";
import { useState } from "react";
import { cn } from "@multica/ui/lib/utils";
import { Dialog, DialogContent } from "@multica/ui/components/ui/dialog";
import {
useCreateModeStore,
type CreateMode,
} from "@multica/core/issues/stores/create-mode-store";
import { AgentCreatePanel } from "./quick-create-issue";
import { ManualCreatePanel, manualDialogContentClass } from "./create-issue";
/**
* Shell that owns the single `<Dialog>` AND `<DialogContent>` for the
* create-issue flow. Mode switching unmounts/mounts only the inner panel
* body — the Portal, Backdrop, and Popup all stay in the DOM, so Base UI
* never replays the open animation. That's what makes the switch feel
* instant; an earlier version put `<DialogContent>` inside each panel and
* the close→open animation cycle still fired on every toggle.
*
* `initialMode` comes from the modal registry (`quick-create-issue` →
* agent, `create-issue` → manual). Subsequent switches are local state
* only and never round-trip through the modal store.
*
* Carry payload: when a panel switches mode it can hand a payload up via
* `onSwitchMode`; the shell stores it as the next panel's `data` so seeding
* works exactly like a fresh open.
*
* Manual-mode `isExpanded` / `backlogHintIssueId` are lifted up because they
* drive `DialogContent`'s className — the className lives here in the shell
* since the Popup is here, but the toggles for those states live in the
* manual panel body.
*/
export function CreateIssueDialog({
onClose,
initialMode,
data,
}: {
onClose: () => void;
initialMode: CreateMode;
data?: Record<string, unknown> | null;
}) {
const setLastMode = useCreateModeStore((s) => s.setLastMode);
const [mode, setMode] = useState<CreateMode>(initialMode);
const [panelData, setPanelData] = useState(data ?? null);
const [isExpanded, setIsExpanded] = useState(false);
const [backlogHintIssueId, setBacklogHintIssueId] = useState<string | null>(null);
const switchTo = (next: CreateMode) => (carry?: Record<string, unknown> | null) => {
setLastMode(next);
setPanelData(carry ?? null);
setMode(next);
};
const className =
mode === "agent"
? cn(
"p-0 gap-0 flex flex-col overflow-hidden",
"!top-1/2 !left-1/2 !-translate-x-1/2 !-translate-y-1/2",
// Width is capped; height is content-driven up to 80vh so a
// pasted screenshot can't push the dialog past the viewport
// (the inner editor area scrolls instead).
"!max-w-xl !w-full !max-h-[80vh]",
// Smooth size transition when switching modes — the manual mode
// uses the same easing.
"!transition-all !duration-300 !ease-out",
)
: manualDialogContentClass(isExpanded, backlogHintIssueId);
return (
<Dialog open onOpenChange={(v) => { if (!v) onClose(); }}>
<DialogContent
finalFocus={false}
showCloseButton={false}
className={className}
>
{mode === "agent" ? (
<AgentCreatePanel
onClose={onClose}
onSwitchMode={switchTo("manual")}
data={panelData}
/>
) : (
<ManualCreatePanel
onClose={onClose}
onSwitchMode={switchTo("agent")}
data={panelData}
isExpanded={isExpanded}
setIsExpanded={setIsExpanded}
backlogHintIssueId={backlogHintIssueId}
setBacklogHintIssueId={setBacklogHintIssueId}
/>
)}
</DialogContent>
</Dialog>
);
}