Mobile nav: replace bottom icon bar with burger menu for consistency
Matches the slide-in sidebar + top-bar burger pattern used by other apps (maintenance, cashup) instead of the bottom tab bar.
This commit is contained in:
parent
69b4c50e14
commit
baba818693
2 changed files with 61 additions and 45 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { DollarSign, CalendarDays, TrendingUp, BarChart3, Wallet, Settings } from 'lucide-react'
|
import { DollarSign, CalendarDays, TrendingUp, BarChart3, Wallet, Settings, Menu } from 'lucide-react'
|
||||||
import AuthGate, { useAuth } from './components/AuthGate'
|
import AuthGate, { useAuth } from './components/AuthGate'
|
||||||
import { UpdateBanner } from './components/UpdateBanner'
|
import { UpdateBanner } from './components/UpdateBanner'
|
||||||
import { useVersionCheck } from './hooks/useVersionCheck'
|
import { useVersionCheck } from './hooks/useVersionCheck'
|
||||||
|
|
@ -25,13 +25,19 @@ const NAV: { id: Page; label: string; icon: React.ElementType; cap?: string }[]
|
||||||
function Shell() {
|
function Shell() {
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
const [page, setPage] = useState<Page>('weekly')
|
const [page, setPage] = useState<Page>('weekly')
|
||||||
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
|
|
||||||
const hotelName = import.meta.env.VITE_HOTEL_NAME || 'Hotel'
|
const hotelName = import.meta.env.VITE_HOTEL_NAME || 'Hotel'
|
||||||
|
|
||||||
const visibleNav = NAV.filter(n => !n.cap || can(user, n.cap))
|
const visibleNav = NAV.filter(n => !n.cap || can(user, n.cap))
|
||||||
|
|
||||||
|
function selectPage(id: Page) {
|
||||||
|
setPage(id)
|
||||||
|
setMenuOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className={`app-shell${menuOpen ? ' menu-open' : ''}`}>
|
||||||
<nav className="sidebar">
|
<nav className="sidebar">
|
||||||
<div className="sidebar-logo">
|
<div className="sidebar-logo">
|
||||||
<DollarSign size={16} strokeWidth={1.75} />
|
<DollarSign size={16} strokeWidth={1.75} />
|
||||||
|
|
@ -42,7 +48,7 @@ function Shell() {
|
||||||
<div
|
<div
|
||||||
key={n.id}
|
key={n.id}
|
||||||
className={`nav-item${page === n.id ? ' active' : ''}`}
|
className={`nav-item${page === n.id ? ' active' : ''}`}
|
||||||
onClick={() => setPage(n.id)}
|
onClick={() => selectPage(n.id)}
|
||||||
>
|
>
|
||||||
<n.icon size={16} strokeWidth={1.75} />
|
<n.icon size={16} strokeWidth={1.75} />
|
||||||
{n.label}
|
{n.label}
|
||||||
|
|
@ -54,6 +60,16 @@ function Shell() {
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{menuOpen && <div className="menu-backdrop" onClick={() => setMenuOpen(false)} />}
|
||||||
|
|
||||||
|
<header className="top-bar">
|
||||||
|
<button className="top-bar-burger" onClick={() => setMenuOpen(o => !o)}>
|
||||||
|
<Menu size={20} strokeWidth={1.75} />
|
||||||
|
</button>
|
||||||
|
<DollarSign size={18} strokeWidth={1.75} color="var(--gold)" />
|
||||||
|
<span className="top-bar-title">Wage Costs</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
<main className="content">
|
<main className="content">
|
||||||
{page === 'weekly' && <Weekly />}
|
{page === 'weekly' && <Weekly />}
|
||||||
{page === 'monthly' && <Monthly />}
|
{page === 'monthly' && <Monthly />}
|
||||||
|
|
@ -62,19 +78,6 @@ function Shell() {
|
||||||
{page === 'budgets' && <Budgets />}
|
{page === 'budgets' && <Budgets />}
|
||||||
{page === 'settings' && <SettingsPage />}
|
{page === 'settings' && <SettingsPage />}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<nav className="bottom-nav">
|
|
||||||
{visibleNav.map(n => (
|
|
||||||
<div
|
|
||||||
key={n.id}
|
|
||||||
className={`bottom-nav-item${page === n.id ? ' active' : ''}`}
|
|
||||||
onClick={() => setPage(n.id)}
|
|
||||||
>
|
|
||||||
<n.icon size={18} strokeWidth={1.75} />
|
|
||||||
<span>{n.label}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -402,46 +402,59 @@ input[type="text"]:focus {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Bottom nav (mobile only) ───────────────────────────────────── */
|
/* ── Top bar + burger (mobile only) ────────────────────────────── */
|
||||||
.bottom-nav {
|
.top-bar {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
height: var(--topbar-h);
|
||||||
bottom: 0; left: 0; right: 0;
|
|
||||||
background: var(--navy);
|
background: var(--navy);
|
||||||
border-top: 1px solid rgba(255,255,255,0.1);
|
color: #fff;
|
||||||
z-index: 200;
|
|
||||||
height: 58px;
|
|
||||||
justify-content: space-around;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-nav-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
padding: 0 12px;
|
||||||
gap: 3px;
|
gap: 10px;
|
||||||
color: rgba(255,255,255,0.45);
|
flex-shrink: 0;
|
||||||
font-size: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
padding: 4px 2px;
|
|
||||||
transition: color 0.15s;
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-nav-item.active { color: var(--gold); }
|
.top-bar-title {
|
||||||
.bottom-nav-item:hover { color: rgba(255,255,255,0.85); }
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar-burger {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.5);
|
||||||
|
z-index: 199;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Mobile breakpoint ──────────────────────────────────────────── */
|
/* ── Mobile breakpoint ──────────────────────────────────────────── */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.sidebar { display: none; }
|
.app-shell { flex-direction: column; }
|
||||||
.bottom-nav { display: flex; }
|
.top-bar { display: flex; }
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0; bottom: 0;
|
||||||
|
z-index: 200;
|
||||||
|
transform: translateX(calc(-1 * var(--sidebar-w)));
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
.app-shell.menu-open .sidebar { transform: translateX(0); }
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
padding-bottom: 68px; /* clear bottom nav */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue