mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
feat: add language selection feature to user profile sidebar, fixed some hover color logic for both model selector and language selection
This commit is contained in:
parent
a41b75463f
commit
24049345a2
9 changed files with 87 additions and 24 deletions
|
|
@ -16,7 +16,6 @@ import {
|
|||
import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { DocumentUploadDialogProvider } from "@/components/assistant-ui/document-upload-popup";
|
||||
import { DashboardBreadcrumb } from "@/components/dashboard-breadcrumb";
|
||||
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
||||
import { LayoutDataProvider } from "@/components/layout";
|
||||
import { OnboardingTour } from "@/components/onboarding-tour";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
|
@ -200,7 +199,6 @@ export function DashboardClientLayout({
|
|||
<LayoutDataProvider
|
||||
searchSpaceId={searchSpaceId}
|
||||
breadcrumb={<DashboardBreadcrumb />}
|
||||
languageSwitcher={<LanguageSwitcher />}
|
||||
>
|
||||
{children}
|
||||
</LayoutDataProvider>
|
||||
|
|
|
|||
|
|
@ -34,14 +34,12 @@ interface LayoutDataProviderProps {
|
|||
searchSpaceId: string;
|
||||
children: React.ReactNode;
|
||||
breadcrumb?: React.ReactNode;
|
||||
languageSwitcher?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function LayoutDataProvider({
|
||||
searchSpaceId,
|
||||
children,
|
||||
breadcrumb,
|
||||
languageSwitcher,
|
||||
}: LayoutDataProviderProps) {
|
||||
const t = useTranslations("dashboard");
|
||||
const tCommon = useTranslations("common");
|
||||
|
|
@ -375,7 +373,6 @@ export function LayoutDataProvider({
|
|||
onLogout={handleLogout}
|
||||
pageUsage={pageUsage}
|
||||
breadcrumb={breadcrumb}
|
||||
languageSwitcher={languageSwitcher}
|
||||
theme={theme}
|
||||
onToggleTheme={handleToggleTheme}
|
||||
isChatPage={isChatPage}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { NotificationButton } from "@/components/notifications/NotificationButto
|
|||
|
||||
interface HeaderProps {
|
||||
breadcrumb?: React.ReactNode;
|
||||
languageSwitcher?: React.ReactNode;
|
||||
theme?: string;
|
||||
onToggleTheme?: () => void;
|
||||
mobileMenuTrigger?: React.ReactNode;
|
||||
|
|
@ -15,7 +14,6 @@ interface HeaderProps {
|
|||
|
||||
export function Header({
|
||||
breadcrumb,
|
||||
languageSwitcher,
|
||||
theme,
|
||||
onToggleTheme,
|
||||
mobileMenuTrigger,
|
||||
|
|
@ -45,8 +43,6 @@ export function Header({
|
|||
<TooltipContent>{theme === "dark" ? "Light mode" : "Dark mode"}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{languageSwitcher}
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ interface LayoutShellProps {
|
|||
onLogout?: () => void;
|
||||
pageUsage?: PageUsage;
|
||||
breadcrumb?: React.ReactNode;
|
||||
languageSwitcher?: React.ReactNode;
|
||||
theme?: string;
|
||||
onToggleTheme?: () => void;
|
||||
defaultCollapsed?: boolean;
|
||||
|
|
@ -69,7 +68,6 @@ export function LayoutShell({
|
|||
onLogout,
|
||||
pageUsage,
|
||||
breadcrumb,
|
||||
languageSwitcher,
|
||||
theme,
|
||||
onToggleTheme,
|
||||
defaultCollapsed = false,
|
||||
|
|
@ -88,7 +86,6 @@ export function LayoutShell({
|
|||
<div className={cn("flex h-screen w-full flex-col bg-background", className)}>
|
||||
<Header
|
||||
breadcrumb={breadcrumb}
|
||||
languageSwitcher={languageSwitcher}
|
||||
theme={theme}
|
||||
onToggleTheme={onToggleTheme}
|
||||
mobileMenuTrigger={<MobileSidebarTrigger onClick={() => setMobileMenuOpen(true)} />}
|
||||
|
|
@ -172,7 +169,6 @@ export function LayoutShell({
|
|||
<main className="flex-1 flex flex-col min-w-0">
|
||||
<Header
|
||||
breadcrumb={breadcrumb}
|
||||
languageSwitcher={languageSwitcher}
|
||||
theme={theme}
|
||||
onToggleTheme={onToggleTheme}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,30 @@
|
|||
"use client";
|
||||
|
||||
import { ChevronUp, LogOut, Settings } from "lucide-react";
|
||||
import { ChevronUp, Languages, LogOut, Settings } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useLocaleContext } from "@/contexts/LocaleContext";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { User } from "../../types/layout.types";
|
||||
|
||||
// Supported languages configuration
|
||||
const LANGUAGES = [
|
||||
{ code: "en" as const, name: "English", flag: "🇺🇸" },
|
||||
{ code: "zh" as const, name: "简体中文", flag: "🇨🇳" },
|
||||
];
|
||||
|
||||
interface SidebarUserProfileProps {
|
||||
user: User;
|
||||
onUserSettings?: () => void;
|
||||
|
|
@ -101,10 +112,15 @@ export function SidebarUserProfile({
|
|||
isCollapsed = false,
|
||||
}: SidebarUserProfileProps) {
|
||||
const t = useTranslations("sidebar");
|
||||
const { locale, setLocale } = useLocaleContext();
|
||||
const bgColor = stringToColor(user.email);
|
||||
const initials = getInitials(user.email);
|
||||
const displayName = user.name || user.email.split("@")[0];
|
||||
|
||||
const handleLanguageChange = (newLocale: "en" | "zh") => {
|
||||
setLocale(newLocale);
|
||||
};
|
||||
|
||||
// Collapsed view - just show avatar with dropdown
|
||||
if (isCollapsed) {
|
||||
return (
|
||||
|
|
@ -118,7 +134,8 @@ export function SidebarUserProfile({
|
|||
className={cn(
|
||||
"flex h-10 w-full items-center justify-center rounded-md",
|
||||
"hover:bg-accent transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
"focus:outline-none focus-visible:outline-none",
|
||||
"data-[state=open]:bg-transparent"
|
||||
)}
|
||||
>
|
||||
<UserAvatar avatarUrl={user.avatarUrl} initials={initials} bgColor={bgColor} />
|
||||
|
|
@ -129,7 +146,7 @@ export function SidebarUserProfile({
|
|||
<TooltipContent side="right">{displayName}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenuContent className="w-56" side="right" align="end" sideOffset={8}>
|
||||
<DropdownMenuContent className="w-56" side="right" align="center" sideOffset={8}>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserAvatar avatarUrl={user.avatarUrl} initials={initials} bgColor={bgColor} />
|
||||
|
|
@ -147,6 +164,34 @@ export function SidebarUserProfile({
|
|||
{t("user_settings")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Languages className="mr-2 h-4 w-4" />
|
||||
{t("language")}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent className="gap-1">
|
||||
{LANGUAGES.map((language) => {
|
||||
const isSelected = locale === language.code;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={language.code}
|
||||
onClick={() => handleLanguageChange(language.code)}
|
||||
className={cn(
|
||||
"mb-1 last:mb-0",
|
||||
!isSelected && "focus:bg-transparent hover:bg-transparent",
|
||||
isSelected && "bg-accent focus:!bg-accent hover:!bg-accent"
|
||||
)}
|
||||
>
|
||||
<span className="mr-2">{language.flag}</span>
|
||||
<span className="flex-1">{language.name}</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem onClick={onLogout}>
|
||||
|
|
@ -169,7 +214,8 @@ export function SidebarUserProfile({
|
|||
className={cn(
|
||||
"flex w-full items-center gap-2 px-2 py-3 text-left",
|
||||
"hover:bg-accent transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
"focus:outline-none focus-visible:outline-none",
|
||||
"data-[state=open]:bg-transparent"
|
||||
)}
|
||||
>
|
||||
<UserAvatar avatarUrl={user.avatarUrl} initials={initials} bgColor={bgColor} />
|
||||
|
|
@ -185,7 +231,7 @@ export function SidebarUserProfile({
|
|||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-56" side="top" align="start" sideOffset={4}>
|
||||
<DropdownMenuContent className="w-56" side="top" align="center" sideOffset={4}>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserAvatar avatarUrl={user.avatarUrl} initials={initials} bgColor={bgColor} />
|
||||
|
|
@ -203,6 +249,34 @@ export function SidebarUserProfile({
|
|||
{t("user_settings")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Languages className="mr-2 h-4 w-4" />
|
||||
{t("language")}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent className="gap-1">
|
||||
{LANGUAGES.map((language) => {
|
||||
const isSelected = locale === language.code;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={language.code}
|
||||
onClick={() => handleLanguageChange(language.code)}
|
||||
className={cn(
|
||||
"mb-1 last:mb-0",
|
||||
!isSelected && "focus:bg-transparent hover:bg-transparent",
|
||||
isSelected && "bg-accent focus:!bg-accent hover:!bg-accent"
|
||||
)}
|
||||
>
|
||||
<span className="mr-2">{language.flag}</span>
|
||||
<span className="flex-1">{language.name}</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuSeparator />
|
||||
|
||||
<DropdownMenuItem onClick={onLogout}>
|
||||
|
|
|
|||
|
|
@ -265,8 +265,8 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
|
|||
onSelect={() => handleSelectConfig(config)}
|
||||
className={cn(
|
||||
"mx-2 rounded-lg mb-1 cursor-pointer",
|
||||
"aria-selected:bg-accent/50",
|
||||
isSelected && "bg-accent/80"
|
||||
!isSelected && "data-[selected=true]:bg-transparent hover:bg-transparent",
|
||||
isSelected && "bg-accent/80 data-[selected=true]:!bg-accent/80 hover:!bg-accent/80"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full gap-2">
|
||||
|
|
@ -327,8 +327,8 @@ export function ModelSelector({ onEdit, onAddNew, className }: ModelSelectorProp
|
|||
onSelect={() => handleSelectConfig(config)}
|
||||
className={cn(
|
||||
"mx-2 rounded-lg mb-1 cursor-pointer",
|
||||
"aria-selected:bg-accent/50",
|
||||
isSelected && "bg-accent/80"
|
||||
!isSelected && "data-[selected=true]:bg-transparent hover:bg-transparent",
|
||||
isSelected && "bg-accent/80 data-[selected=true]:!bg-accent/80 hover:!bg-accent/80"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full gap-2">
|
||||
|
|
|
|||
|
|
@ -182,13 +182,13 @@ function DropdownMenuSubTrigger({
|
|||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
<ChevronRightIcon className="ml-auto size-4 text-muted-foreground" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -687,6 +687,7 @@
|
|||
"expand_sidebar": "Expand sidebar",
|
||||
"collapse_sidebar": "Collapse sidebar",
|
||||
"user_settings": "User settings",
|
||||
"language": "Language",
|
||||
"logout": "Logout"
|
||||
},
|
||||
"errors": {
|
||||
|
|
|
|||
|
|
@ -672,6 +672,7 @@
|
|||
"expand_sidebar": "展开侧边栏",
|
||||
"collapse_sidebar": "收起侧边栏",
|
||||
"user_settings": "用户设置",
|
||||
"language": "语言",
|
||||
"logout": "退出登录"
|
||||
},
|
||||
"errors": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue