refactor: unify modal overlay system with shadcn/ui Dialog

Fix inconsistent modal overlays by migrating all custom implementations
to the unified shadcn/ui Dialog component with proper z-index layering.

Changes:
- Update Dialog component to support three z-index levels:
  - base (z-40): First-level dialogs
  - nested (z-50): Nested dialogs
  - alert (z-[60]): Alert/confirmation dialogs (using Tailwind arbitrary value)
- Refactor all custom modal implementations to use Dialog:
  - EndpointSpeedTest: API endpoint speed testing panel
  - ClaudeConfigEditor: Claude common config editor
  - CodexQuickWizardModal: Codex quick setup wizard
  - CodexCommonConfigModal: Codex common config editor
  - SettingsDialog: Restart confirmation prompt
- Remove custom backdrop implementations and manual z-index
- Leverage Radix UI Portal for automatic DOM order management
- Ensure consistent overlay behavior and keyboard interactions

This eliminates the "background residue" issue where overlays from
different layers would conflict, providing a unified and professional
user experience across all modal interactions.
This commit is contained in:
Jason
2025-10-17 21:32:28 +08:00
parent bcaebc1bcb
commit 2d3d717826
6 changed files with 333 additions and 468 deletions

View File

@@ -1,7 +1,14 @@
import React, { useEffect } from "react";
import { X, Save } from "lucide-react";
import React from "react";
import { Save } from "lucide-react";
import { useTranslation } from "react-i18next";
import { isLinux } from "@/lib/platform";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
interface CodexCommonConfigModalProps {
isOpen: boolean;
@@ -24,56 +31,14 @@ export const CodexCommonConfigModal: React.FC<CodexCommonConfigModalProps> = ({
}) => {
const { t } = useTranslation();
// Support ESC key to close modal
useEffect(() => {
if (!isOpen) return;
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault();
onClose();
}
};
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
onMouseDown={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
{/* Backdrop */}
<div
className={`absolute inset-0 bg-black/50 dark:bg-black/70${
isLinux() ? "" : " backdrop-blur-sm"
}`}
/>
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent zIndex="nested" className="max-w-2xl max-h-[90vh] flex flex-col p-0">
<DialogHeader className="px-6 pt-6 pb-0">
<DialogTitle>{t("codexConfig.editCommonConfigTitle")}</DialogTitle>
</DialogHeader>
{/* Modal */}
<div className="relative bg-white dark:bg-gray-900 rounded-xl shadow-lg max-w-2xl w-full mx-4 max-h-[90vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-800">
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
{t("codexConfig.editCommonConfigTitle")}
</h2>
<button
type="button"
onClick={onClose}
className="p-1 text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors"
aria-label={t("common.close")}
>
<X size={18} />
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-auto p-6 space-y-4">
<div className="flex-1 overflow-auto px-6 py-4 space-y-4">
<p className="text-sm text-gray-500 dark:text-gray-400">
{t("codexConfig.commonConfigHint")}
</p>
@@ -102,25 +67,16 @@ export const CodexCommonConfigModal: React.FC<CodexCommonConfigModalProps> = ({
)}
</div>
{/* Footer */}
<div className="flex items-center justify-end gap-3 p-6 border-t border-gray-200 dark:border-gray-800 bg-gray-100 dark:bg-gray-800">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 hover:bg-white dark:hover:bg-gray-700 rounded-lg transition-colors"
>
<DialogFooter className="px-6 pb-6 pt-4 border-t border-gray-200 dark:border-gray-800 bg-gray-100 dark:bg-gray-800 m-0">
<Button type="button" variant="outline" onClick={onClose}>
{t("common.cancel")}
</button>
<button
type="button"
onClick={onClose}
className="px-4 py-2 bg-blue-500 dark:bg-blue-600 text-white rounded-lg hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors text-sm font-medium flex items-center gap-2"
>
</Button>
<Button type="button" onClick={onClose} className="gap-2">
<Save className="w-4 h-4" />
{t("common.save")}
</button>
</div>
</div>
</div>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};