RJBULK-APP/components/shell/top-bar.tsx

98 lines
3.6 KiB
TypeScript

"use client";
import { useRouter } from "next/navigation";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { LogOut, User as UserIcon, Moon, Sun } from "lucide-react";
import { logoutAction } from "@/actions/auth";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { MobileNav } from "./mobile-nav";
interface TopBarProps {
user: { name?: string; email: string };
}
export function TopBar({ user }: TopBarProps) {
const router = useRouter();
function toggleDark() {
const html = document.documentElement;
html.classList.toggle("dark");
localStorage.setItem("theme", html.classList.contains("dark") ? "dark" : "light");
}
// Render the trigger as a plain button (no asChild + Slot). The asChild +
// Slot + Radix Trigger chain is fragile under React 19 — the click handler
// doesn't always toggle the menu even though data-state is set. A direct
// button is reliable and gets the same shadcn styling via buttonVariants().
return (
<header className="sticky top-0 z-20 flex h-16 items-center justify-between gap-2 border-b bg-background/95 px-4 md:px-6 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="flex items-center gap-2">
<MobileNav />
</div>
<div className="hidden md:block text-sm text-muted-foreground">
{user.name || user.email}
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={toggleDark}
aria-label="Toggle theme"
className={cn(buttonVariants({ variant: "ghost", size: "icon" }), "relative")}
>
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</button>
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
buttonVariants({ variant: "ghost", size: "sm" }),
"gap-2 px-2"
)}
>
<span
aria-hidden
className="flex h-7 w-7 items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-semibold"
>
{(user.name || user.email).charAt(0).toUpperCase()}
</span>
<span className="hidden sm:inline text-sm font-medium">
{user.name || user.email}
</span>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>
<div className="flex flex-col">
<span className="text-sm font-medium">{user.name || "User"}</span>
<span className="text-xs text-muted-foreground">{user.email}</span>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => router.push("/profile")}>
<UserIcon className="mr-2 h-4 w-4" />
Profile
</DropdownMenuItem>
<DropdownMenuSeparator />
<form action={logoutAction}>
<DropdownMenuItem asChild>
<button type="submit" className="w-full text-left">
<LogOut className="mr-2 h-4 w-4" />
Log out
</button>
</DropdownMenuItem>
</form>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
);
}