Files
rehearshalhub/web/src/components/TopBar.tsx
2026-04-08 15:10:52 +02:00

156 lines
4.9 KiB
TypeScript
Executable File

import { useState, useRef, useEffect } from "react";
import { useNavigate, useLocation, matchPath } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { listBands } from "../api/bands";
import { getInitials } from "../utils";
export function TopBar() {
const navigate = useNavigate();
const location = useLocation();
const [dropdownOpen, setDropdownOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const { data: bands } = useQuery({ queryKey: ["bands"], queryFn: listBands });
// Derive active band from URL
const bandMatch = matchPath("/bands/:bandId/*", location.pathname) ?? matchPath("/bands/:bandId", location.pathname);
const activeBandId = bandMatch?.params?.bandId ?? null;
const activeBand = bands?.find((b) => b.id === activeBandId) ?? null;
// Close dropdown on outside click
useEffect(() => {
if (!dropdownOpen) return;
function handleClick(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
setDropdownOpen(false);
}
}
document.addEventListener("mousedown", handleClick);
return () => document.removeEventListener("mousedown", handleClick);
}, [dropdownOpen]);
return (
<header
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
height: 50,
background: "#0b0b0e",
borderBottom: "1px solid rgba(255,255,255,0.06)",
zIndex: 1000,
display: "flex",
justifyContent: "flex-end",
padding: "0 16px",
alignItems: "center",
}}
>
<div ref={dropdownRef} style={{ position: "relative" }}>
<button
onClick={() => setDropdownOpen((o) => !o)}
style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "6px 10px",
background: "rgba(255,255,255,0.05)",
border: "1px solid rgba(255,255,255,0.07)",
borderRadius: 8,
cursor: "pointer",
color: "#eeeef2",
textAlign: "left",
fontFamily: "inherit",
fontSize: 13,
}}
>
<div
style={{
width: 32,
height: 32,
background: "rgba(232,162,42,0.15)",
border: "1px solid rgba(232,162,42,0.3)",
borderRadius: "50%",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 12,
fontWeight: 700,
color: "#e8a22a",
flexShrink: 0,
}}
>
{activeBand ? getInitials(activeBand.name) : "?"}
</div>
</button>
{dropdownOpen && bands && (
<div
style={{
position: "absolute",
top: "calc(100% + 4px)",
right: 0,
width: 200,
background: "#18181e",
border: "1px solid rgba(255,255,255,0.1)",
borderRadius: 10,
padding: 6,
zIndex: 1001,
boxShadow: "0 8px 24px rgba(0,0,0,0.5)",
}}
>
{bands.map((band) => (
<button
key={band.id}
onClick={() => {
navigate(`/bands/${band.id}`);
setDropdownOpen(false);
}}
style={{
width: "100%",
display: "flex",
alignItems: "center",
gap: 8,
padding: "8px 10px",
marginBottom: 2,
background: band.id === activeBandId ? "rgba(232,162,42,0.08)" : "transparent",
border: "none",
borderRadius: 6,
cursor: "pointer",
color: "#eeeef2",
textAlign: "left",
fontFamily: "inherit",
fontSize: 13,
}}
>
<div
style={{
width: 24,
height: 24,
borderRadius: "50%",
background: "rgba(232,162,42,0.15)",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 10,
fontWeight: 700,
color: "#e8a22a",
flexShrink: 0,
}}
>
{getInitials(band.name)}
</div>
<span style={{ flex: 1, fontSize: 13 }}>
{band.name}
</span>
{band.id === activeBandId && (
<span style={{ fontSize: 12, color: "#e8a22a", flexShrink: 0 }}></span>
)}
</button>
))}
</div>
)}
</div>
</header>
);
}