Compare commits
2 Commits
media-quer
...
media-quer
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e32aa4b6aa | ||
|
|
682c168250 |
@@ -8,6 +8,19 @@
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
/* ── Left Group: Brand + Links ───────────────────────────── */
|
||||
|
||||
.navbar__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.navbar__brand-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar__brand {
|
||||
font-weight: 700;
|
||||
font-size: 1.4rem;
|
||||
@@ -38,6 +51,14 @@
|
||||
background-color: var(--color-bg-card);
|
||||
}
|
||||
|
||||
/* ── Right Group: Email + Logout + Hamburger ─────────────── */
|
||||
|
||||
.navbar__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.navbar__user {
|
||||
font-size: 1.275rem;
|
||||
color: #777;
|
||||
@@ -53,6 +74,10 @@
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.navbar__user-link--mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar__logout {
|
||||
padding: 0.4rem 1rem;
|
||||
font-size: 1.275rem;
|
||||
@@ -65,59 +90,78 @@
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
/* ── Navbar Responsive ───────────────────────────────────── */
|
||||
/* ── Hamburger Icon (hidden on desktop) ──────────────────── */
|
||||
|
||||
.navbar__hamburger {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.navbar__hamburger-bar {
|
||||
display: block;
|
||||
width: 28px;
|
||||
height: 3px;
|
||||
background-color: var(--color-brand);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* ── Responsive: Tablet & below (≤ 768px) ────────────────── */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.navbar__brand {
|
||||
margin-right: 1rem;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.navbar__links {
|
||||
flex-wrap: wrap;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.navbar__link {
|
||||
padding: 0.35rem 0.75rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.navbar__user {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.navbar__logout {
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.navbar {
|
||||
.navbar__brand-group {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 0.75rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.navbar__links {
|
||||
width: 100%;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.navbar__link {
|
||||
padding: 0.3rem 0.6rem;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.navbar__brand {
|
||||
margin-right: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.navbar__links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar__user-link--mobile {
|
||||
display: block;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.navbar__user-link--desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar__logout {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar__hamburger {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Responsive: Mobile (≤ 480px) ────────────────────────── */
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.navbar {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.navbar__brand {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.navbar__user-link--mobile {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useAuth } from "../contexts/AuthContext";
|
||||
import NavPanel from "./navPanel/NavPanel";
|
||||
import "./Navbar.css";
|
||||
|
||||
const navLinks = [
|
||||
@@ -11,13 +13,20 @@ const navLinks = [
|
||||
export default function Navbar() {
|
||||
const { currentUser, logout } = useAuth();
|
||||
const location = useLocation();
|
||||
const [isNavPanelOpen, setIsNavPanelOpen] = useState(false);
|
||||
|
||||
if (!currentUser) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav className="navbar">
|
||||
<div className="flex flex--center gap-sm">
|
||||
<div className="navbar__left">
|
||||
<div className="navbar__brand-group">
|
||||
<span className="navbar__brand">Koin Ping</span>
|
||||
<Link to="/account" className="navbar__user-link navbar__user-link--mobile">
|
||||
{currentUser.email}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="navbar__links">
|
||||
{navLinks.map(({ to, label }) => {
|
||||
const isActive = location.pathname === to;
|
||||
@@ -34,12 +43,29 @@ export default function Navbar() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex--center gap-lg">
|
||||
<Link to="/account" className="navbar__user navbar__user-link">{currentUser.email}</Link>
|
||||
<div className="navbar__right">
|
||||
<Link to="/account" className="navbar__user navbar__user-link navbar__user-link--desktop">
|
||||
{currentUser.email}
|
||||
</Link>
|
||||
<button onClick={logout} className="navbar__logout">
|
||||
Logout
|
||||
</button>
|
||||
<button
|
||||
className="navbar__hamburger"
|
||||
onClick={() => setIsNavPanelOpen(true)}
|
||||
aria-label="Open navigation menu"
|
||||
>
|
||||
<span className="navbar__hamburger-bar" />
|
||||
<span className="navbar__hamburger-bar" />
|
||||
<span className="navbar__hamburger-bar" />
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<NavPanel
|
||||
isOpen={isNavPanelOpen}
|
||||
onClose={() => setIsNavPanelOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
78
frontend/src/components/navPanel/NavPanel.css
Normal file
78
frontend/src/components/navPanel/NavPanel.css
Normal file
@@ -0,0 +1,78 @@
|
||||
/* ── Overlay Backdrop ─────────────────────────────────────── */
|
||||
|
||||
.nav-panel__overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-color: rgba(0, 0, 0, 0.45);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.nav-panel__overlay--visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* ── Slide-out Panel ─────────────────────────────────────── */
|
||||
|
||||
.nav-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 260px;
|
||||
height: 100%;
|
||||
background-color: var(--color-bg-elevated);
|
||||
border-right: 1px solid var(--color-border);
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
z-index: 950;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.nav-panel--open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* ── Nav Links ───────────────────────────────────────────── */
|
||||
|
||||
.nav-panel__nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav-panel__link {
|
||||
padding: 0.9rem 1.5rem;
|
||||
text-decoration: none;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 200;
|
||||
color: var(--color-text-subtle);
|
||||
border-left: 3px solid transparent;
|
||||
transition: background-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.nav-panel__link--active {
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg-card);
|
||||
border-left-color: var(--color-brand);
|
||||
}
|
||||
|
||||
/* ── Logout Button ───────────────────────────────────────── */
|
||||
|
||||
.nav-panel__logout {
|
||||
margin: 1rem 1.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 400;
|
||||
background-color: transparent;
|
||||
color: var(--color-text-subtle);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
52
frontend/src/components/navPanel/NavPanel.jsx
Normal file
52
frontend/src/components/navPanel/NavPanel.jsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useAuth } from "../../contexts/AuthContext";
|
||||
import "./NavPanel.css";
|
||||
|
||||
const navLinks = [
|
||||
{ to: "/addresses", label: "Addresses" },
|
||||
{ to: "/alerts", label: "Configure Alerts" },
|
||||
{ to: "/alertevents", label: "Alert Event History" },
|
||||
];
|
||||
|
||||
export default function NavPanel({ isOpen, onClose }) {
|
||||
const { logout } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
const handleNavClick = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
onClose();
|
||||
logout();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`nav-panel__overlay${isOpen ? " nav-panel__overlay--visible" : ""}`}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<aside className={`nav-panel${isOpen ? " nav-panel--open" : ""}`}>
|
||||
<nav className="nav-panel__nav">
|
||||
{navLinks.map(({ to, label }) => {
|
||||
const isActive = location.pathname === to;
|
||||
return (
|
||||
<Link
|
||||
key={to}
|
||||
to={to}
|
||||
className={`nav-panel__link${isActive ? " nav-panel__link--active" : ""}`}
|
||||
onClick={handleNavClick}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
<button onClick={handleLogout} className="nav-panel__logout">
|
||||
Logout
|
||||
</button>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user