mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 09:24:09 +00:00
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.
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { useState, Suspense } from 'react';
|
||||
import { DiffEditor } from '@/lib/monacoLoader';
|
||||
import { AlertTriangle, GitBranch, Loader2 } from 'lucide-react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
||||
import { AlertTriangle, Loader2 } from 'lucide-react';
|
||||
import { Modal, ModalHeader, ModalFooter, ConfirmModal } from '@/components/ui/modal';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsHighlight, TabsHighlightItem } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -72,79 +71,73 @@ export function GitSourceDiffDialog({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-5xl w-[95vw] p-0 gap-0">
|
||||
<DialogHeader className="px-6 pt-6 pb-4 border-b border-glass-border">
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<GitBranch className="w-4 h-4" strokeWidth={1.5} />
|
||||
<span>Review update for</span>
|
||||
<span className="font-mono tabular-nums">{stackName}</span>
|
||||
<span className="font-mono tabular-nums text-xs text-stat-subtitle">@{shortSha}</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription className="sr-only">
|
||||
Review the diff between the current on-disk stack files and the incoming Git commit.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Modal size="wide" open={open} onOpenChange={onOpenChange}>
|
||||
<ModalHeader
|
||||
kicker="GIT · PULL PREVIEW"
|
||||
title={stackName}
|
||||
description={`Incoming commit ${shortSha}. Review the diff between the current on-disk stack files and the incoming Git commit.`}
|
||||
/>
|
||||
|
||||
<div className="px-6 pt-4 space-y-3">
|
||||
{!pull.validation.ok && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
|
||||
<div>
|
||||
<p className="font-medium">Incoming compose failed validation</p>
|
||||
<pre className="font-mono text-[11px] whitespace-pre-wrap mt-1">{pull.validation.error}</pre>
|
||||
</div>
|
||||
<div className="px-6 pt-4 space-y-3">
|
||||
{!pull.validation.ok && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
|
||||
<div>
|
||||
<p className="font-medium">Incoming compose failed validation</p>
|
||||
<pre className="font-mono text-[11px] whitespace-pre-wrap mt-1">{pull.validation.error}</pre>
|
||||
</div>
|
||||
)}
|
||||
{pull.hasLocalChanges && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
|
||||
<div>
|
||||
<p className="font-medium">Local edits detected on disk</p>
|
||||
<p className="mt-0.5">Applying will overwrite changes that differ from the last applied commit.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{envAvailable && (
|
||||
<Tabs value={diffTab} onValueChange={(v) => setDiffTab(v as 'compose' | 'env')}>
|
||||
<TabsList>
|
||||
<TabsHighlight className="rounded-md bg-glass-highlight" transition={springs.snappy}>
|
||||
<TabsHighlightItem value="compose">
|
||||
<TabsTrigger value="compose">compose.yaml</TabsTrigger>
|
||||
</TabsHighlightItem>
|
||||
<TabsHighlightItem value="env">
|
||||
<TabsTrigger value="env">.env</TabsTrigger>
|
||||
</TabsHighlightItem>
|
||||
</TabsHighlight>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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={effectiveTab === 'compose' ? 'yaml' : 'ini'}
|
||||
theme={isDarkMode ? 'vs-dark' : 'vs'}
|
||||
original={currentValue}
|
||||
modified={incomingValue}
|
||||
options={{
|
||||
readOnly: true,
|
||||
renderSideBySide: true,
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
fontFamily: "'Geist Mono', monospace",
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{pull.hasLocalChanges && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" strokeWidth={1.5} />
|
||||
<div>
|
||||
<p className="font-medium">Local edits detected on disk</p>
|
||||
<p className="mt-0.5">Applying will overwrite changes that differ from the last applied commit.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DialogFooter className="px-6 py-4 border-t border-glass-border flex flex-row items-center justify-between sm:justify-between gap-4">
|
||||
{envAvailable && (
|
||||
<Tabs value={diffTab} onValueChange={(v) => setDiffTab(v as 'compose' | 'env')}>
|
||||
<TabsList>
|
||||
<TabsHighlight className="rounded-md bg-glass-highlight" transition={springs.snappy}>
|
||||
<TabsHighlightItem value="compose">
|
||||
<TabsTrigger value="compose">compose.yaml</TabsTrigger>
|
||||
</TabsHighlightItem>
|
||||
<TabsHighlightItem value="env">
|
||||
<TabsTrigger value="env">.env</TabsTrigger>
|
||||
</TabsHighlightItem>
|
||||
</TabsHighlight>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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={effectiveTab === 'compose' ? 'yaml' : 'ini'}
|
||||
theme={isDarkMode ? 'vs-dark' : 'vs'}
|
||||
original={currentValue}
|
||||
modified={incomingValue}
|
||||
options={{
|
||||
readOnly: true,
|
||||
renderSideBySide: true,
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
fontFamily: "'Geist Mono', monospace",
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalFooter
|
||||
hint={
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="git-source-deploy-after"
|
||||
@@ -152,60 +145,57 @@ export function GitSourceDiffDialog({
|
||||
onCheckedChange={(checked) => setDeployAfter(checked === true)}
|
||||
disabled={applying || !pull.validation.ok}
|
||||
/>
|
||||
<Label htmlFor="git-source-deploy-after" className="text-xs cursor-pointer">
|
||||
<Label
|
||||
htmlFor="git-source-deploy-after"
|
||||
className="text-xs normal-case tracking-normal cursor-pointer"
|
||||
>
|
||||
Deploy after apply
|
||||
</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onDismiss()}
|
||||
disabled={applying}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleApplyClick}
|
||||
disabled={applying || !pull.validation.ok}
|
||||
>
|
||||
{applying ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" strokeWidth={1.5} />
|
||||
Applying...
|
||||
</>
|
||||
) : (
|
||||
'Apply'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Overwrite local edits?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
The on-disk stack files differ from the last applied commit. Applying this pull will replace them with the incoming content.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={applying}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={async () => {
|
||||
setConfirmOpen(false);
|
||||
await apply();
|
||||
}}
|
||||
}
|
||||
secondary={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onDismiss()}
|
||||
disabled={applying}
|
||||
>
|
||||
Overwrite and apply
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
Dismiss
|
||||
</Button>
|
||||
}
|
||||
primary={
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleApplyClick}
|
||||
disabled={applying || !pull.validation.ok}
|
||||
>
|
||||
{applying ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-1.5 animate-spin" strokeWidth={1.5} />
|
||||
Applying...
|
||||
</>
|
||||
) : (
|
||||
'Apply'
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<ConfirmModal
|
||||
open={confirmOpen}
|
||||
onOpenChange={setConfirmOpen}
|
||||
variant="destructive"
|
||||
kicker="GIT · LOCAL CHANGES"
|
||||
title="Overwrite local edits?"
|
||||
description="The on-disk stack files differ from the last applied commit. Applying this pull will replace them with the incoming content."
|
||||
confirmLabel="Overwrite and apply"
|
||||
confirming={applying}
|
||||
onConfirm={async () => {
|
||||
setConfirmOpen(false);
|
||||
await apply();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user