feat: Created AdminDropdown component with click-outside/Escape close,…

- "frontend/src/components/AdminDropdown.tsx"
- "frontend/src/App.tsx"
- "frontend/src/App.css"

GSD-Task: S01/T01
This commit is contained in:
jlightner 2026-03-30 11:02:23 +00:00
parent 93a643f1b8
commit cd9dd6d8f9
3 changed files with 122 additions and 5 deletions

View file

@ -776,6 +776,53 @@ body {
color: var(--color-text-on-header-hover); color: var(--color-text-on-header-hover);
} }
/* ── Admin dropdown ───────────────────────────────────────────────────────── */
.admin-dropdown {
position: relative;
}
.admin-dropdown__trigger {
font-family: inherit;
font-size: 0.875rem;
color: var(--color-text-on-header);
background: none;
border: none;
cursor: pointer;
padding: 0;
transition: color 0.15s;
}
.admin-dropdown__trigger:hover {
color: var(--color-text-on-header-hover);
}
.admin-dropdown__menu {
position: absolute;
top: calc(100% + 0.5rem);
right: 0;
min-width: 10rem;
background: var(--color-bg-surface);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
box-shadow: 0 4px 16px var(--color-shadow-heavy);
z-index: 100;
padding: 0.375rem 0;
}
.admin-dropdown__item {
display: block;
padding: 0.5rem 1rem;
color: var(--color-text-primary);
text-decoration: none;
font-size: 0.875rem;
transition: background 0.12s;
}
.admin-dropdown__item:hover {
background: var(--color-bg-surface-hover);
}
/* ── Home / Hero ──────────────────────────────────────────────────────────── */ /* ── Home / Hero ──────────────────────────────────────────────────────────── */
.home-hero { .home-hero {

View file

@ -9,7 +9,7 @@ import ReviewQueue from "./pages/ReviewQueue";
import MomentDetail from "./pages/MomentDetail"; import MomentDetail from "./pages/MomentDetail";
import AdminReports from "./pages/AdminReports"; import AdminReports from "./pages/AdminReports";
import AdminPipeline from "./pages/AdminPipeline"; import AdminPipeline from "./pages/AdminPipeline";
import ModeToggle from "./components/ModeToggle"; import AdminDropdown from "./components/AdminDropdown";
export default function App() { export default function App() {
return ( return (
@ -23,11 +23,8 @@ export default function App() {
<Link to="/">Home</Link> <Link to="/">Home</Link>
<Link to="/topics">Topics</Link> <Link to="/topics">Topics</Link>
<Link to="/creators">Creators</Link> <Link to="/creators">Creators</Link>
<Link to="/admin/review">Review</Link> <AdminDropdown />
<Link to="/admin/reports">Reports</Link>
<Link to="/admin/pipeline">Pipeline</Link>
</nav> </nav>
<ModeToggle />
</div> </div>
</header> </header>

View file

@ -0,0 +1,73 @@
import { useEffect, useRef, useState } from "react";
import { Link } from "react-router-dom";
export default function AdminDropdown() {
const [open, setOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
// Close on outside click
useEffect(() => {
function handler(e: MouseEvent) {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
) {
setOpen(false);
}
}
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
// Close on Escape
useEffect(() => {
function handler(e: KeyboardEvent) {
if (e.key === "Escape") setOpen(false);
}
if (open) {
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
}
}, [open]);
return (
<div className="admin-dropdown" ref={dropdownRef}>
<button
className="admin-dropdown__trigger"
onClick={() => setOpen((prev) => !prev)}
aria-expanded={open}
aria-haspopup="true"
>
Admin
</button>
{open && (
<div className="admin-dropdown__menu" role="menu">
<Link
to="/admin/review"
className="admin-dropdown__item"
role="menuitem"
onClick={() => setOpen(false)}
>
Review
</Link>
<Link
to="/admin/reports"
className="admin-dropdown__item"
role="menuitem"
onClick={() => setOpen(false)}
>
Reports
</Link>
<Link
to="/admin/pipeline"
className="admin-dropdown__item"
role="menuitem"
onClick={() => setOpen(false)}
>
Pipeline
</Link>
</div>
)}
</div>
);
}