mirror of
https://github.com/kbenestad/ClubLedger.git
synced 2026-06-18 09:44:33 +00:00
feat: hamburger menu for mobile nav
On screens ≤640px the tab row collapses; a hamburger button (Material Symbols menu/close icon) appears in the nav-right area. Tapping it opens a fixed full-width dropdown below the header with full-height tap targets and a left accent border on the active item. Tapping a tab or anywhere outside closes the menu. Desktop layout is unchanged — nav-tabs uses display:contents so the buttons remain direct flex children of nav. https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
This commit is contained in:
parent
7a1a1a88d9
commit
8d1c2e4eb5
3 changed files with 107 additions and 12 deletions
|
|
@ -93,6 +93,23 @@ async function startApp() {
|
||||||
document.querySelector('[data-view="bar"]').classList.add('hidden');
|
document.querySelector('[data-view="bar"]').classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hamburger menu toggle
|
||||||
|
const hamburger = document.getElementById('hamburger');
|
||||||
|
const navTabs = document.getElementById('navTabs');
|
||||||
|
hamburger.addEventListener('click', e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const open = navTabs.classList.toggle('open');
|
||||||
|
hamburger.setAttribute('aria-expanded', open);
|
||||||
|
hamburger.querySelector('.material-symbols-outlined').textContent = open ? 'close' : 'menu';
|
||||||
|
});
|
||||||
|
document.addEventListener('click', e => {
|
||||||
|
if (navTabs.classList.contains('open') && !navTabs.contains(e.target)) {
|
||||||
|
navTabs.classList.remove('open');
|
||||||
|
hamburger.setAttribute('aria-expanded', 'false');
|
||||||
|
hamburger.querySelector('.material-symbols-outlined').textContent = 'menu';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Nav tab switching
|
// Nav tab switching
|
||||||
document.querySelectorAll('.nav-btn').forEach(btn => {
|
document.querySelectorAll('.nav-btn').forEach(btn => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
|
|
@ -101,6 +118,10 @@ async function startApp() {
|
||||||
document.querySelectorAll('.view').forEach(v => v.classList.add('hidden'));
|
document.querySelectorAll('.view').forEach(v => v.classList.add('hidden'));
|
||||||
document.getElementById('view-' + btn.dataset.view).classList.remove('hidden');
|
document.getElementById('view-' + btn.dataset.view).classList.remove('hidden');
|
||||||
if (btn.dataset.view === 'admin') loadAdminView();
|
if (btn.dataset.view === 'admin') loadAdminView();
|
||||||
|
// Close mobile menu after selection
|
||||||
|
navTabs.classList.remove('open');
|
||||||
|
hamburger.setAttribute('aria-expanded', 'false');
|
||||||
|
hamburger.querySelector('.material-symbols-outlined').textContent = 'menu';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,25 @@
|
||||||
<!-- ===================== NAV ===================== -->
|
<!-- ===================== NAV ===================== -->
|
||||||
<nav>
|
<nav>
|
||||||
<span class="brand" id="navBrand">ClubLedger</span>
|
<span class="brand" id="navBrand">ClubLedger</span>
|
||||||
<button class="nav-btn active" data-view="members">
|
<div class="nav-tabs" id="navTabs">
|
||||||
<span class="material-symbols-outlined nav-icon">group</span>Members
|
<button class="nav-btn active" data-view="members">
|
||||||
</button>
|
<span class="material-symbols-outlined nav-icon">group</span>Members
|
||||||
<button class="nav-btn" data-view="cashier">
|
</button>
|
||||||
<span class="material-symbols-outlined nav-icon">universal_currency_alt</span>Cashier
|
<button class="nav-btn" data-view="cashier">
|
||||||
</button>
|
<span class="material-symbols-outlined nav-icon">universal_currency_alt</span>Cashier
|
||||||
<button class="nav-btn" data-view="bar">
|
</button>
|
||||||
<span class="material-symbols-outlined nav-icon">point_of_sale</span>Bar
|
<button class="nav-btn" data-view="bar">
|
||||||
</button>
|
<span class="material-symbols-outlined nav-icon">point_of_sale</span>Bar
|
||||||
<button class="nav-btn hidden" data-view="admin" id="adminTabBtn">
|
</button>
|
||||||
<span class="material-symbols-outlined nav-icon">settings_applications</span>Admin
|
<button class="nav-btn hidden" data-view="admin" id="adminTabBtn">
|
||||||
</button>
|
<span class="material-symbols-outlined nav-icon">settings_applications</span>Admin
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="nav-right">
|
<div class="nav-right">
|
||||||
<span class="nav-user" id="navUser"></span>
|
<span class="nav-user" id="navUser"></span>
|
||||||
|
<button class="hamburger" id="hamburger" aria-label="Open menu" aria-expanded="false">
|
||||||
|
<span class="material-symbols-outlined">menu</span>
|
||||||
|
</button>
|
||||||
<button class="nav-logout" onclick="doLogout()">Sign out</button>
|
<button class="nav-logout" onclick="doLogout()">Sign out</button>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ nav {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* On desktop the wrapper is invisible — buttons behave as direct nav children */
|
||||||
|
.nav-tabs { display: contents; }
|
||||||
|
|
||||||
.brand {
|
.brand {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
@ -504,3 +507,69 @@ select {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Hamburger button (hidden on desktop) ---- */
|
||||||
|
.hamburger {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(240,246,252,.3);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
color: var(--nav-text);
|
||||||
|
width: 34px;
|
||||||
|
height: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
transition: background .1s;
|
||||||
|
}
|
||||||
|
.hamburger:hover { background: rgba(177,186,196,.15); }
|
||||||
|
.hamburger .material-symbols-outlined { font-size: 20px; line-height: 1; }
|
||||||
|
|
||||||
|
/* ---- Mobile nav ---- */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
/* Hide desktop tabs, show hamburger */
|
||||||
|
.nav-tabs {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 48px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: var(--nav-bg);
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 6px 0 10px;
|
||||||
|
border-bottom: 1px solid rgba(240,246,252,.12);
|
||||||
|
box-shadow: 0 8px 16px rgba(0,0,0,.35);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
.nav-tabs.open { display: flex; }
|
||||||
|
|
||||||
|
.hamburger { display: inline-flex; }
|
||||||
|
|
||||||
|
/* Hide username text to save space */
|
||||||
|
.nav-user { display: none; }
|
||||||
|
|
||||||
|
/* Mobile tab items: full-width rows with left accent border */
|
||||||
|
.nav-btn, .nav-link {
|
||||||
|
align-self: auto;
|
||||||
|
width: 100%;
|
||||||
|
padding: 13px 20px;
|
||||||
|
font-size: .9375rem;
|
||||||
|
border-bottom: none;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.nav-btn:hover, .nav-link:hover {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-left-color: rgba(240,246,252,.35);
|
||||||
|
background: rgba(177,186,196,.12);
|
||||||
|
}
|
||||||
|
.nav-btn.active, .nav-link.active {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-left-color: #fff;
|
||||||
|
background: rgba(177,186,196,.12);
|
||||||
|
}
|
||||||
|
.nav-icon { font-size: 20px; }
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue