diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx index a2cf4f3e3..09fb3f6d0 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx @@ -2,6 +2,7 @@ import { Check, + ChevronRight, ChevronUp, Download, ExternalLink, @@ -17,8 +18,9 @@ import { import Image from "next/image"; import { useTranslations } from "next-intl"; import type React from "react"; -import { useState } from "react"; +import { Fragment, useState } from "react"; import { Button } from "@/components/ui/button"; +import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer"; import { DropdownMenu, DropdownMenuContent, @@ -64,6 +66,8 @@ const LEARN_MORE_LINKS = [ { key: "github" as const, href: "https://github.com/MODSetter/SurfSense" }, ]; +type MobileProfileSubmenu = "theme" | "language" | "learn_more"; + interface SidebarUserProfileProps { user: User; onUserSettings?: () => void; @@ -144,12 +148,14 @@ export function SidebarUserProfile({ const isDesktopViewport = useMediaQuery("(min-width: 768px)"); const { os, primary, isMobileOS } = usePrimaryDownload(); const [isLoggingOut, setIsLoggingOut] = useState(false); + const [mobileSubmenu, setMobileSubmenu] = useState(null); const bgColor = getUserAvatarColor(user.email); const initials = getUserInitials(user.email); const displayName = user.name || user.email.split("@")[0]; const downloadUrl = primary?.url ?? GITHUB_RELEASES_URL; const downloadLabel = t("download_for_os", { os }); const showDownloadCta = !isDesktop && !isMobileOS && isDesktopViewport; + const useMobileSubmenus = !isDesktopViewport; const handleLanguageChange = (newLocale: "en" | "es" | "pt" | "hi" | "zh") => { setLocale(newLocale); @@ -169,6 +175,242 @@ export function SidebarUserProfile({ } }; + const submenuTriggerClassName = "cursor-default"; + const drawerItemClassName = cn( + "flex h-12 w-full items-center gap-3 rounded-lg px-3 text-left text-sm transition-colors", + "hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" + ); + const drawerSeparatorClassName = "mx-3 h-px bg-popover-border"; + + const renderThemeSubmenu = () => { + if (!setTheme) return null; + + if (useMobileSubmenus) { + return ( + setMobileSubmenu("theme")} + > + + {t("theme")} + + + ); + } + + return ( + + + + {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 transition-all", + "hover:bg-accent hover:text-accent-foreground", + isSelected && "text-primary" + )} + > + + {t(themeOption.value)} + {isSelected && } + + ); + })} + + + + ); + }; + + const renderLanguageSubmenu = () => { + if (useMobileSubmenus) { + return ( + setMobileSubmenu("language")} + > + + {t("language")} + + + ); + } + + return ( + + + + {t("language")} + + + + {LANGUAGES.map((language) => { + const isSelected = locale === language.code; + return ( + handleLanguageChange(language.code)} + className={cn( + "mb-1 last:mb-0 transition-all", + "hover:bg-accent hover:text-accent-foreground", + isSelected && "text-primary" + )} + > + {language.flag} + {language.name} + {isSelected && } + + ); + })} + + + + ); + }; + + const renderLearnMoreSubmenu = () => { + if (useMobileSubmenus) { + return ( + setMobileSubmenu("learn_more")} + > + + {t("learn_more")} + + + ); + } + + return ( + + + + {t("learn_more")} + + + + {LEARN_MORE_LINKS.map((link) => ( + + + {t(link.key)} + + + + ))} + +

+ v{APP_VERSION} +

+
+
+
+ ); + }; + + const mobileSubmenuDrawer = ( + { + if (!open) setMobileSubmenu(null); + }} + shouldScaleBackground={false} + > + + + + {mobileSubmenu === "theme" + ? t("theme") + : mobileSubmenu === "language" + ? t("language") + : t("learn_more")} + +
+ {mobileSubmenu === "theme" && + setTheme && + THEMES.map((themeOption, index) => { + const Icon = themeOption.icon; + const isSelected = theme === themeOption.value; + return ( + + {index > 0 &&
} + + + ); + })} + + {mobileSubmenu === "language" && + LANGUAGES.map((language, index) => { + const isSelected = locale === language.code; + return ( + + {index > 0 &&
} + + + ); + })} + + {mobileSubmenu === "learn_more" && ( + <> + {LEARN_MORE_LINKS.map((link, index) => ( + + {index > 0 &&
} + setMobileSubmenu(null)} + > + {t(link.key)} + + + + ))} +

+ v{APP_VERSION} +

+ + )} +
+ + + ); + // Collapsed view - just show avatar with dropdown if (isCollapsed) { return ( @@ -251,89 +493,11 @@ export function SidebarUserProfile({ )} - {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 transition-all", - "hover:bg-accent hover:text-accent-foreground", - isSelected && "text-primary" - )} - > - - {t(themeOption.value)} - {isSelected && } - - ); - })} - - - - )} + {renderThemeSubmenu()} - - - - {t("language")} - - - - {LANGUAGES.map((language) => { - const isSelected = locale === language.code; - return ( - handleLanguageChange(language.code)} - className={cn( - "mb-1 last:mb-0 transition-all", - "hover:bg-accent hover:text-accent-foreground", - isSelected && "text-primary" - )} - > - {language.flag} - {language.name} - {isSelected && } - - ); - })} - - - + {renderLanguageSubmenu()} - - - - {t("learn_more")} - - - - {LEARN_MORE_LINKS.map((link) => ( - - - {t(link.key)} - - - - ))} - -

- v{APP_VERSION} -

-
-
-
+ {renderLearnMoreSubmenu()} {!isDesktop && !isMobileOS && ( @@ -356,6 +520,7 @@ export function SidebarUserProfile({ + {mobileSubmenuDrawer}
); @@ -433,89 +598,11 @@ export function SidebarUserProfile({ )} - {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 transition-all", - "hover:bg-accent hover:text-accent-foreground", - isSelected && "text-primary" - )} - > - - {t(themeOption.value)} - {isSelected && } - - ); - })} - - - - )} + {renderThemeSubmenu()} - - - - {t("language")} - - - - {LANGUAGES.map((language) => { - const isSelected = locale === language.code; - return ( - handleLanguageChange(language.code)} - className={cn( - "mb-1 last:mb-0 transition-all", - "hover:bg-accent hover:text-accent-foreground", - isSelected && "text-primary" - )} - > - {language.flag} - {language.name} - {isSelected && } - - ); - })} - - - + {renderLanguageSubmenu()} - - - - {t("learn_more")} - - - - {LEARN_MORE_LINKS.map((link) => ( - - - {t(link.key)} - - - - ))} - -

- v{APP_VERSION} -

-
-
-
+ {renderLearnMoreSubmenu()} {!isDesktop && ( @@ -534,6 +621,7 @@ export function SidebarUserProfile({ + {mobileSubmenuDrawer}
); }