Files
sencho/frontend/src/components/ComposeDiffPreviewDialog.tsx
T
Anso 46b3d53274 refactor(frontend): migrate diff dialogs to Modal chrome (D-5) (#909)
* refactor(frontend): extend Modal size system with wide variant

Adds 'wide' (max-w-5xl w-[95vw]) to ModalSize for dialogs that render
Monaco DiffEditor side-by-side, which need ~1024px to display both panels
clearly. The existing 'xl' cap at max-w-xl (576px) is too narrow for that
use case.

* refactor(frontend): migrate ComposeDiffPreviewDialog to Modal chrome (D-5)

Replaces raw Dialog/DialogHeader/DialogFooter with Modal size="wide",
ModalHeader, and ModalFooter. Kicker carries the stack name and file type
context; title is the file name (accessible dialog name for tests). Footer
hint reuses the existing "ON DISK → UNSAVED" label via the hint prop.

* refactor(frontend): migrate GitSourceDiffDialog to Modal chrome (D-5)

Replaces Dialog + nested AlertDialog with Modal size="wide", ModalHeader,
ModalFooter, and ConfirmModal. Kicker is "GIT · PULL PREVIEW"; title is
the stack name. Short SHA moves to the sr-only description. The "Deploy
after apply" checkbox in the footer hint slot uses normal-case and
tracking-normal on the Label to prevent KICKER_CLASS uppercase/tracking
from being inherited. The overwrite confirmation uses ConfirmModal with
variant="destructive" (rose rail) since it replaces local file content.
2026-05-04 11:53:35 -04:00

92 lines
2.5 KiB
TypeScript

import { Suspense } from 'react';
import { DiffEditor } from '@/lib/monacoLoader';
import { Loader2 } from 'lucide-react';
import { Modal, ModalHeader, ModalFooter } from '@/components/ui/modal';
import { Button } from '@/components/ui/button';
export interface ComposeDiffPreviewDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
stackName: string;
fileName: string;
language: 'yaml' | 'ini';
original: string;
modified: string;
actionLabel: 'Save' | 'Save & deploy';
confirming: boolean;
isDarkMode: boolean;
onConfirm: () => void | Promise<void>;
}
export function ComposeDiffPreviewDialog({
open,
onOpenChange,
stackName,
fileName,
language,
original,
modified,
actionLabel,
confirming,
isDarkMode,
onConfirm,
}: ComposeDiffPreviewDialogProps) {
return (
<Modal size="wide" open={open} onOpenChange={onOpenChange}>
<ModalHeader
kicker={`${stackName} · COMPOSE DIFF`}
title={fileName}
description="Review the diff between on-disk content and unsaved editor changes before saving."
/>
<div className="px-6 pb-4 pt-3">
<div className="h-[55vh] border border-glass-border rounded-md overflow-hidden">
<Suspense fallback={<div className="w-full h-full" aria-busy="true" />}>
<DiffEditor
height="100%"
language={language}
theme={isDarkMode ? 'vs-dark' : 'vs'}
original={original}
modified={modified}
options={{
readOnly: true,
renderSideBySide: true,
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontFamily: "'Geist Mono', monospace",
fontSize: 12,
}}
/>
</Suspense>
</div>
</div>
<ModalFooter
hint="ON DISK → UNSAVED"
secondary={
<Button
variant="outline"
size="sm"
onClick={() => onOpenChange(false)}
disabled={confirming}
>
Cancel
</Button>
}
primary={
<Button
size="sm"
onClick={() => onConfirm()}
disabled={confirming}
>
{confirming && (
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" strokeWidth={1.5} />
)}
{actionLabel}
</Button>
}
/>
</Modal>
);
}