63 lines
3.2 KiB
TypeScript
63 lines
3.2 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
import { useEffect, useId, useRef, useState } from "react";
|
||
|
|
import Link from "next/link";
|
||
|
|
import { ChevronDown, Moon, Settings, Sun, UserRound } from "lucide-react";
|
||
|
|
import { useTheme } from "./ThemeProvider";
|
||
|
|
import { LogoutButton } from "./auth/LogoutButton";
|
||
|
|
|
||
|
|
export function AccountMenu({ name, email }: { name: string; email: string }) {
|
||
|
|
const [open, setOpen] = useState(false);
|
||
|
|
const root = useRef<HTMLDivElement>(null);
|
||
|
|
const trigger = useRef<HTMLButtonElement>(null);
|
||
|
|
const panelId = useId();
|
||
|
|
const { theme, toggle } = useTheme();
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!open) return;
|
||
|
|
const closeOutside = (event: Event) => {
|
||
|
|
if (!root.current?.contains(event.target as Node)) setOpen(false);
|
||
|
|
};
|
||
|
|
const closeOnEscape = (event: KeyboardEvent) => {
|
||
|
|
if (event.key === "Escape") {
|
||
|
|
setOpen(false);
|
||
|
|
trigger.current?.focus();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
document.addEventListener("pointerdown", closeOutside);
|
||
|
|
document.addEventListener("focusin", closeOutside);
|
||
|
|
document.addEventListener("keydown", closeOnEscape);
|
||
|
|
return () => {
|
||
|
|
document.removeEventListener("pointerdown", closeOutside);
|
||
|
|
document.removeEventListener("focusin", closeOutside);
|
||
|
|
document.removeEventListener("keydown", closeOnEscape);
|
||
|
|
};
|
||
|
|
}, [open]);
|
||
|
|
|
||
|
|
const itemClass = "flex min-h-11 w-full items-center gap-3 rounded-lg px-3 text-sm text-text hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-[#30638E] dark:hover:bg-white/5";
|
||
|
|
|
||
|
|
return <div ref={root} className="relative">
|
||
|
|
<button ref={trigger} type="button" aria-label="Account and settings" title="Account and settings" aria-expanded={open} aria-controls={panelId}
|
||
|
|
onClick={() => setOpen(!open)}
|
||
|
|
className="flex min-h-11 min-w-11 items-center justify-center gap-2 rounded-xl border border-border px-1.5 text-text transition hover:bg-gray-100 focus-visible:outline-2 focus-visible:outline-[#30638E] dark:hover:bg-white/5 md:px-3">
|
||
|
|
<span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg bg-[#30638E]/10 text-[#30638E] dark:text-indigo-ink"><UserRound className="h-4 w-4" /></span>
|
||
|
|
<span className="hidden max-w-32 truncate text-sm font-medium md:block">{name || "Account"}</span>
|
||
|
|
<ChevronDown className="hidden h-4 w-4 text-text-muted md:block" />
|
||
|
|
</button>
|
||
|
|
{open && <div id={panelId} className="absolute right-0 top-full mt-2 w-72 max-w-[calc(100vw-2rem)] rounded-xl border border-border bg-surface p-2 shadow-xl">
|
||
|
|
<div className="border-b border-border px-3 pb-3 pt-2">
|
||
|
|
<p className="break-words text-sm font-semibold text-text">{name || "Your account"}</p>
|
||
|
|
<p className="mt-1 break-all text-xs text-text-muted">{email}</p>
|
||
|
|
</div>
|
||
|
|
<nav aria-label="Account options" className="mt-2">
|
||
|
|
<Link href="/settings" className={itemClass} onClick={() => setOpen(false)}><Settings className="h-4 w-4" />Settings</Link>
|
||
|
|
<button type="button" onClick={toggle} className={itemClass}>
|
||
|
|
{theme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
||
|
|
{theme === "dark" ? "Switch to light mode" : "Switch to dark mode"}
|
||
|
|
</button>
|
||
|
|
<div className="mt-2 border-t border-border pt-2"><LogoutButton fullWidth /></div>
|
||
|
|
</nav>
|
||
|
|
</div>}
|
||
|
|
</div>;
|
||
|
|
}
|