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:
parent
93a643f1b8
commit
cd9dd6d8f9
3 changed files with 122 additions and 5 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
73
frontend/src/components/AdminDropdown.tsx
Normal file
73
frontend/src/components/AdminDropdown.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue