fix: settings dialog closes on theme change (#211)

https://github.com/better-auth/better-hub/issues/201
This commit is contained in:
Maxwell
2026-02-28 06:35:32 +10:00
committed by GitHub
parent 34578acc10
commit 6d5cb59dd2
3 changed files with 61 additions and 6 deletions
@@ -27,9 +27,15 @@ interface SettingsContentProps {
initialSettings: UserSettings;
user: { name: string; email: string; image: string | null };
githubProfile: GitHubProfile;
onThemeTransition?: () => void;
}
export function SettingsContent({ initialSettings, user, githubProfile }: SettingsContentProps) {
export function SettingsContent({
initialSettings,
user,
githubProfile,
onThemeTransition,
}: SettingsContentProps) {
const [activeTab, setActiveTab] = useState<TabId>("general");
const [settings, setSettings] = useState(initialSettings);
const { emit } = useMutationEvents();
@@ -126,7 +132,11 @@ export function SettingsContent({ initialSettings, user, githubProfile }: Settin
{/* Content — only this area scrolls */}
<div className="flex-1 min-h-0 overflow-y-auto border border-t-0 border-border mx-6 mb-6">
{activeTab === "general" && (
<GeneralTab settings={settings} onUpdate={handleUpdate} />
<GeneralTab
settings={settings}
onUpdate={handleUpdate}
onThemeTransition={onThemeTransition}
/>
)}
{activeTab === "editor" && <EditorTab />}
{activeTab === "ai" && (
@@ -1,5 +1,6 @@
"use client";
import { useCallback, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
@@ -53,11 +54,35 @@ export function SettingsDialog({ open, onOpenChange, user, githubProfile }: Sett
gcTime: 15 * 60 * 1000,
});
const [isThemeTransitioning, setIsThemeTransitioning] = useState(false);
const transitionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const onThemeTransition = useCallback(() => {
if (transitionTimeoutRef.current) {
clearTimeout(transitionTimeoutRef.current);
}
setIsThemeTransitioning(true);
transitionTimeoutRef.current = setTimeout(() => {
setIsThemeTransitioning(false);
}, 1000);
}, []);
const handleInteractOutside = useCallback(
(e: Event) => {
if (isThemeTransitioning) {
e.preventDefault();
}
},
[isThemeTransitioning],
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
className="sm:max-w-2xl p-0 gap-0 overflow-hidden max-h-[85vh] outline-none"
showCloseButton={false}
onPointerDownOutside={handleInteractOutside}
onInteractOutside={handleInteractOutside}
>
<VisuallyHidden.Root>
<DialogTitle>Settings</DialogTitle>
@@ -68,6 +93,7 @@ export function SettingsDialog({ open, onOpenChange, user, githubProfile }: Sett
initialSettings={settings}
user={user}
githubProfile={githubProfile}
onThemeTransition={onThemeTransition}
/>
) : (
<div className="flex-1 flex items-center justify-center px-6">
@@ -10,6 +10,7 @@ import type { UserSettings } from "@/lib/user-settings-store";
interface GeneralTabProps {
settings: UserSettings;
onUpdate: (updates: Partial<UserSettings>) => Promise<void>;
onThemeTransition?: () => void;
}
function ThemeGrid({
@@ -84,10 +85,24 @@ const RADIUS_OPTIONS: { id: BorderRadiusPreset; label: string; description: stri
{ id: "large", label: "Large", description: "Soft & rounded" },
];
export function GeneralTab({ settings: _settings, onUpdate: _onUpdate }: GeneralTabProps) {
export function GeneralTab({
settings: _settings,
onUpdate: _onUpdate,
onThemeTransition,
}: GeneralTabProps) {
const { themeId, mode, borderRadius, setTheme, toggleMode, setBorderRadius, themes } =
useColorTheme();
const handleSetTheme = (id: string) => {
onThemeTransition?.();
setTheme(id);
};
const handleToggleMode = () => {
onThemeTransition?.();
toggleMode();
};
return (
<div className="divide-y divide-border">
{/* Mode toggle */}
@@ -105,7 +120,9 @@ export function GeneralTab({ settings: _settings, onUpdate: _onUpdate }: General
</p>
<div className="flex gap-2">
<button
onClick={() => mode === "light" && toggleMode()}
onClick={() =>
mode === "light" && handleToggleMode()
}
className={cn(
"flex items-center gap-2 px-3 py-2 border text-sm transition-colors",
mode === "dark"
@@ -120,7 +137,9 @@ export function GeneralTab({ settings: _settings, onUpdate: _onUpdate }: General
)}
</button>
<button
onClick={() => mode === "dark" && toggleMode()}
onClick={() =>
mode === "dark" && handleToggleMode()
}
className={cn(
"flex items-center gap-2 px-3 py-2 border text-sm transition-colors",
mode === "light"
@@ -150,7 +169,7 @@ export function GeneralTab({ settings: _settings, onUpdate: _onUpdate }: General
themes={themes}
activeId={themeId}
mode={mode}
onSelect={setTheme}
onSelect={handleSetTheme}
/>
</div>