diff --git a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx index 1238cf28e..c7ffa8aba 100644 --- a/surfsense_web/components/layout/providers/LayoutDataProvider.tsx +++ b/surfsense_web/components/layout/providers/LayoutDataProvider.tsx @@ -300,9 +300,6 @@ export function LayoutDataProvider({ } }, [router]); - const handleToggleTheme = useCallback(() => { - setTheme(theme === "dark" ? "light" : "dark"); - }, [theme, setTheme]); const handleViewAllSharedChats = useCallback(() => { setIsAllSharedChatsSidebarOpen(true); @@ -374,7 +371,7 @@ export function LayoutDataProvider({ pageUsage={pageUsage} breadcrumb={breadcrumb} theme={theme} - onToggleTheme={handleToggleTheme} + setTheme={setTheme} isChatPage={isChatPage} > {children} diff --git a/surfsense_web/components/layout/ui/header/Header.tsx b/surfsense_web/components/layout/ui/header/Header.tsx index 4244bba0d..0a1bd225e 100644 --- a/surfsense_web/components/layout/ui/header/Header.tsx +++ b/surfsense_web/components/layout/ui/header/Header.tsx @@ -1,21 +1,14 @@ "use client"; -import { Moon, Sun } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { NotificationButton } from "@/components/notifications/NotificationButton"; interface HeaderProps { breadcrumb?: React.ReactNode; - theme?: string; - onToggleTheme?: () => void; mobileMenuTrigger?: React.ReactNode; } export function Header({ breadcrumb, - theme, - onToggleTheme, mobileMenuTrigger, }: HeaderProps) { return ( @@ -30,19 +23,6 @@ export function Header({
{/* Notifications */} - - {/* Theme toggle */} - {onToggleTheme && ( - - - - - {theme === "dark" ? "Light mode" : "Dark mode"} - - )}
); diff --git a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx index 3047f84b5..0225b94f9 100644 --- a/surfsense_web/components/layout/ui/shell/LayoutShell.tsx +++ b/surfsense_web/components/layout/ui/shell/LayoutShell.tsx @@ -36,7 +36,7 @@ interface LayoutShellProps { pageUsage?: PageUsage; breadcrumb?: React.ReactNode; theme?: string; - onToggleTheme?: () => void; + setTheme?: (theme: "light" | "dark" | "system") => void; defaultCollapsed?: boolean; isChatPage?: boolean; children: React.ReactNode; @@ -69,7 +69,7 @@ export function LayoutShell({ pageUsage, breadcrumb, theme, - onToggleTheme, + setTheme, defaultCollapsed = false, isChatPage = false, children, @@ -86,8 +86,6 @@ export function LayoutShell({
setMobileMenuOpen(true)} />} /> @@ -117,6 +115,8 @@ export function LayoutShell({ onUserSettings={onUserSettings} onLogout={onLogout} pageUsage={pageUsage} + theme={theme} + setTheme={setTheme} />
@@ -163,14 +163,14 @@ export function LayoutShell({ onUserSettings={onUserSettings} onLogout={onLogout} pageUsage={pageUsage} + theme={theme} + setTheme={setTheme} className="hidden md:flex border-r shrink-0" />
diff --git a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx index 57fba60c9..7dd01d75a 100644 --- a/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/MobileSidebar.tsx @@ -33,6 +33,8 @@ interface MobileSidebarProps { onUserSettings?: () => void; onLogout?: () => void; pageUsage?: PageUsage; + theme?: string; + setTheme?: (theme: "light" | "dark" | "system") => void; } export function MobileSidebarTrigger({ onClick }: { onClick: () => void }) { @@ -70,6 +72,8 @@ export function MobileSidebar({ onUserSettings, onLogout, pageUsage, + theme, + setTheme, }: MobileSidebarProps) { const handleSearchSpaceSelect = (id: number) => { onSearchSpaceSelect(id); @@ -145,6 +149,8 @@ export function MobileSidebar({ onUserSettings={onUserSettings} onLogout={onLogout} pageUsage={pageUsage} + theme={theme} + setTheme={setTheme} className="w-full border-none" />
diff --git a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx index 9a632506c..336b695ea 100644 --- a/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx +++ b/surfsense_web/components/layout/ui/sidebar/Sidebar.tsx @@ -35,6 +35,8 @@ interface SidebarProps { onUserSettings?: () => void; onLogout?: () => void; pageUsage?: PageUsage; + theme?: string; + setTheme?: (theme: "light" | "dark" | "system") => void; className?: string; } @@ -58,6 +60,8 @@ export function Sidebar({ onUserSettings, onLogout, pageUsage, + theme, + setTheme, className, }: SidebarProps) { const t = useTranslations("sidebar"); @@ -241,6 +245,8 @@ export function Sidebar({ onUserSettings={onUserSettings} onLogout={onLogout} isCollapsed={isCollapsed} + theme={theme} + setTheme={setTheme} />
diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx index 376f27d06..d468c7bbe 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx @@ -1,6 +1,6 @@ "use client"; -import { ChevronUp, Languages, LogOut, Settings } from "lucide-react"; +import { Check, ChevronUp, Laptop, Languages, LogOut, Moon, Settings, Sun } from "lucide-react"; import { useTranslations } from "next-intl"; import { DropdownMenu, @@ -25,11 +25,20 @@ const LANGUAGES = [ { code: "zh" as const, name: "简体中文", flag: "🇨🇳" }, ]; +// Supported themes configuration +const THEMES = [ + { value: "light" as const, name: "Light", icon: Sun }, + { value: "dark" as const, name: "Dark", icon: Moon }, + { value: "system" as const, name: "System", icon: Laptop }, +]; + interface SidebarUserProfileProps { user: User; onUserSettings?: () => void; onLogout?: () => void; isCollapsed?: boolean; + theme?: string; + setTheme?: (theme: "light" | "dark" | "system") => void; } /** @@ -110,6 +119,8 @@ export function SidebarUserProfile({ onUserSettings, onLogout, isCollapsed = false, + theme, + setTheme, }: SidebarUserProfileProps) { const t = useTranslations("sidebar"); const { locale, setLocale } = useLocaleContext(); @@ -121,6 +132,10 @@ export function SidebarUserProfile({ setLocale(newLocale); }; + const handleThemeChange = (newTheme: "light" | "dark" | "system") => { + setTheme?.(newTheme); + }; + // Collapsed view - just show avatar with dropdown if (isCollapsed) { return ( @@ -164,6 +179,38 @@ export function SidebarUserProfile({ {t("user_settings")} + {setTheme && ( + + + + {t("theme")} + + + + {THEMES.map((themeOption) => { + const Icon = themeOption.icon; + const isSelected = theme === themeOption.value; + return ( + handleThemeChange(themeOption.value)} + className={cn( + "mb-1 last:mb-0", + !isSelected && "focus:bg-transparent hover:bg-transparent", + isSelected && "bg-accent focus:!bg-accent hover:!bg-accent" + )} + > + + {t(themeOption.value)} + {isSelected && } + + ); + })} + + + + )} + @@ -249,6 +296,38 @@ export function SidebarUserProfile({ {t("user_settings")} + {setTheme && ( + + + + {t("theme")} + + + + {THEMES.map((themeOption) => { + const Icon = themeOption.icon; + const isSelected = theme === themeOption.value; + return ( + handleThemeChange(themeOption.value)} + className={cn( + "mb-1 last:mb-0", + !isSelected && "focus:bg-transparent hover:bg-transparent", + isSelected && "bg-accent focus:!bg-accent hover:!bg-accent" + )} + > + + {t(themeOption.value)} + {isSelected && } + + ); + })} + + + + )} + diff --git a/surfsense_web/components/new-chat/model-selector.tsx b/surfsense_web/components/new-chat/model-selector.tsx index f9ebc0077..48141ed64 100644 --- a/surfsense_web/components/new-chat/model-selector.tsx +++ b/surfsense_web/components/new-chat/model-selector.tsx @@ -232,7 +232,7 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp