v3: search engine, responsive header with compact user menu

This commit is contained in:
khannurien
2026-03-29 11:56:31 +00:00
parent f0f6472db6
commit cbb3505139
31 changed files with 1206 additions and 178 deletions

View File

@@ -0,0 +1,64 @@
import { useEffect, useRef, useState } from "react";
import { Link } from "react-router";
import { Avatar } from "./Avatar.tsx";
import type { User } from "../model.ts";
export function UserMenu({ user }: { user: User }) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
function onMouseDown(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
}
function onKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") setOpen(false);
}
document.addEventListener("mousedown", onMouseDown);
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("mousedown", onMouseDown);
document.removeEventListener("keydown", onKeyDown);
};
}, [open]);
return (
<div className="user-menu" ref={ref}>
<button
type="button"
className="user-menu-trigger"
onClick={() => setOpen((o) => !o)}
aria-expanded={open}
aria-label="User menu"
>
<Avatar
userId={user.id}
username={user.username}
hasAvatar={!!user.avatarMime}
size={28}
/>
</button>
{open && (
<div className="user-menu-dropdown" role="menu">
<Link
to={`/users/${user.username}`}
className="user-menu-item"
role="menuitem"
onClick={() => setOpen(false)}
>
@{user.username}
</Link>
<Link
to={`/users/${user.username}/playlists`}
className="user-menu-item"
role="menuitem"
onClick={() => setOpen(false)}
>
Playlists
</Link>
</div>
)}
</div>
);
}