fix: redesign settings dialog layout to prevent content overflow and visual jitter
Fixed multiple layout issues in the settings dialog: 1. Dialog structure: Changed from grid to flexbox layout - Removed global padding from DialogContent - Added individual padding to DialogHeader (px-6 pt-6) and DialogFooter (px-6 pb-6 pt-4) - Added max-h-[90vh] constraint to prevent dialog from exceeding viewport 2. Content area improvements: - Replaced max-h-[70vh] with flex-1 for better space utilization - Set min-h-[480px] on content wrapper to maintain consistent dialog height - Applied min-h-[400px] to all TabsContent components to prevent height jumps 3. Scrollbar optimization: - Changed overflow-y-auto to overflow-y-scroll to force scrollbar gutter - Eliminates horizontal shift when switching between tabs with different content heights - Consistent with main app layout approach (App.tsx) 4. Footer enhancement: - Added border-t and bg-muted/20 for visual separation - Fixed footer overlapping content in advanced tab Result: Settings dialog now displays all content properly without requiring fullscreen, maintains consistent height across tabs, and eliminates layout shift when switching tabs.
This commit is contained in:
@@ -171,21 +171,21 @@ export function SettingsDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={handleDialogChange}>
|
<Dialog open={open} onOpenChange={handleDialogChange}>
|
||||||
<DialogContent className="max-w-3xl gap-6">
|
<DialogContent className="max-w-3xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("settings.title")}</DialogTitle>
|
<DialogTitle>{t("settings.title")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{isBusy ? (
|
{isBusy ? (
|
||||||
<div className="flex min-h-[320px] items-center justify-center">
|
<div className="flex min-h-[320px] items-center justify-center px-6">
|
||||||
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex max-h-[70vh] flex-col gap-6 overflow-hidden">
|
<div className="flex-1 overflow-y-scroll px-6 min-h-[480px]">
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
onValueChange={setActiveTab}
|
onValueChange={setActiveTab}
|
||||||
className="flex flex-1 flex-col"
|
className="flex flex-col h-full"
|
||||||
>
|
>
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="general">
|
<TabsTrigger value="general">
|
||||||
@@ -197,65 +197,63 @@ export function SettingsDialog({
|
|||||||
<TabsTrigger value="about">{t("common.about")}</TabsTrigger>
|
<TabsTrigger value="about">{t("common.about")}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto pr-1">
|
<TabsContent value="general" className="space-y-6 mt-6 min-h-[400px]">
|
||||||
<TabsContent value="general" className="space-y-6 pt-4">
|
{settings ? (
|
||||||
{settings ? (
|
<>
|
||||||
<>
|
<LanguageSettings
|
||||||
<LanguageSettings
|
value={settings.language}
|
||||||
value={settings.language}
|
onChange={(lang) => updateSettings({ language: lang })}
|
||||||
onChange={(lang) => updateSettings({ language: lang })}
|
/>
|
||||||
/>
|
<WindowSettings
|
||||||
<WindowSettings
|
settings={settings}
|
||||||
settings={settings}
|
onChange={updateSettings}
|
||||||
onChange={updateSettings}
|
/>
|
||||||
/>
|
<ConfigPathDisplay
|
||||||
<ConfigPathDisplay
|
path={configPath}
|
||||||
path={configPath}
|
onOpen={openConfigFolder}
|
||||||
onOpen={openConfigFolder}
|
/>
|
||||||
/>
|
</>
|
||||||
</>
|
) : null}
|
||||||
) : null}
|
</TabsContent>
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="advanced" className="space-y-6 pt-4">
|
<TabsContent value="advanced" className="space-y-6 mt-6 min-h-[400px]">
|
||||||
{settings ? (
|
{settings ? (
|
||||||
<>
|
<>
|
||||||
<DirectorySettings
|
<DirectorySettings
|
||||||
appConfigDir={appConfigDir}
|
appConfigDir={appConfigDir}
|
||||||
resolvedDirs={resolvedDirs}
|
resolvedDirs={resolvedDirs}
|
||||||
onAppConfigChange={updateAppConfigDir}
|
onAppConfigChange={updateAppConfigDir}
|
||||||
onBrowseAppConfig={browseAppConfigDir}
|
onBrowseAppConfig={browseAppConfigDir}
|
||||||
onResetAppConfig={resetAppConfigDir}
|
onResetAppConfig={resetAppConfigDir}
|
||||||
claudeDir={settings.claudeConfigDir}
|
claudeDir={settings.claudeConfigDir}
|
||||||
codexDir={settings.codexConfigDir}
|
codexDir={settings.codexConfigDir}
|
||||||
onDirectoryChange={updateDirectory}
|
onDirectoryChange={updateDirectory}
|
||||||
onBrowseDirectory={browseDirectory}
|
onBrowseDirectory={browseDirectory}
|
||||||
onResetDirectory={resetDirectory}
|
onResetDirectory={resetDirectory}
|
||||||
/>
|
/>
|
||||||
<ImportExportSection
|
<ImportExportSection
|
||||||
status={importStatus}
|
status={importStatus}
|
||||||
selectedFile={selectedFile}
|
selectedFile={selectedFile}
|
||||||
errorMessage={errorMessage}
|
errorMessage={errorMessage}
|
||||||
backupId={backupId}
|
backupId={backupId}
|
||||||
isImporting={isImporting}
|
isImporting={isImporting}
|
||||||
onSelectFile={selectImportFile}
|
onSelectFile={selectImportFile}
|
||||||
onImport={importConfig}
|
onImport={importConfig}
|
||||||
onExport={exportConfig}
|
onExport={exportConfig}
|
||||||
onClear={clearSelection}
|
onClear={clearSelection}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="about" className="pt-4">
|
<TabsContent value="about" className="mt-6 min-h-[400px]">
|
||||||
<AboutSection isPortable={isPortable} />
|
<AboutSection isPortable={isPortable} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</div>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DialogFooter className="gap-2">
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={handleCancel}>
|
<Button variant="outline" onClick={handleCancel}>
|
||||||
{t("common.cancel")}
|
{t("common.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -280,12 +278,14 @@ export function SettingsDialog({
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{t("settings.restartRequired")}</DialogTitle>
|
<DialogTitle>{t("settings.restartRequired")}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<p className="text-sm text-muted-foreground">
|
<div className="px-6">
|
||||||
{t("settings.restartRequiredMessage", {
|
<p className="text-sm text-muted-foreground">
|
||||||
defaultValue: "配置目录已变更,需要重启应用生效。",
|
{t("settings.restartRequiredMessage", {
|
||||||
})}
|
defaultValue: "配置目录已变更,需要重启应用生效。",
|
||||||
</p>
|
})}
|
||||||
<DialogFooter className="gap-2">
|
</p>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={handleRestartLater}>
|
<Button variant="outline" onClick={handleRestartLater}>
|
||||||
{t("settings.restartLater")}
|
{t("settings.restartLater")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed left-1/2 top-1/2 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-white dark:bg-gray-900 p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
"fixed left-1/2 top-1/2 flex flex-col w-full max-w-lg max-h-[90vh] translate-x-[-50%] translate-y-[-50%] border bg-white dark:bg-gray-900 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
zIndexMap[zIndex],
|
zIndexMap[zIndex],
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -78,7 +78,7 @@ const DialogHeader = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
"flex flex-col space-y-1.5 text-center sm:text-left px-6 pt-6",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -92,7 +92,7 @@ const DialogFooter = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 px-6 pb-6 pt-4 border-t border-border bg-muted/20",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
Reference in New Issue
Block a user