Replace the mobile tab bar with a hamburger menu
Six tabs (Queue, On-call, Alerts, Team, Admin, Account) had already
outgrown the bottom bar once: 56b8191 let it auto-size its columns to
fit however many there were, but on a phone that only left each tab
55-65px wide. Squeezing further wasn't an option, so the bar is gone
on mobile and a hamburger button in the topbar opens a menu instead.
The menu reuses the sheet + menu-item pattern already used for the
snooze and assign-to actions in incident.js, rather than a new overlay
component. It lists the same sections the sidebar does, including
hiding Admin for non-admins, and the triggered-incident badge that
used to sit on the Queue tab icon now shows on the hamburger button.
Desktop (>=900px) is untouched: the sidebar is the same markup, CSS
alone hides it below 900px and shows it above, so nothing there
changed behaviourally.
Claude-Session: https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7
This commit is contained in:
@@ -30,6 +30,17 @@ const SECTIONS = {
|
||||
more: { title: 'Account', view: account },
|
||||
};
|
||||
|
||||
// The mobile hamburger menu's contents — the same sections the desktop
|
||||
// sidebar's .nav-link list carries in index.html, in the same order.
|
||||
const NAV_ITEMS = [
|
||||
{ path: '/', section: 'queue', label: 'Queue', icon: 'queueList' },
|
||||
{ path: '/oncall', section: 'oncall', label: 'On-call', icon: 'calendar' },
|
||||
{ path: '/alerts', section: 'alerts', label: 'Alerts', icon: 'bell' },
|
||||
{ path: '/team', section: 'team', label: 'Team', icon: 'team' },
|
||||
{ path: '/admin', section: 'admin', label: 'Admin', icon: 'shield', adminOnly: true },
|
||||
{ path: '/more', section: 'more', label: 'Account', icon: 'user' },
|
||||
];
|
||||
|
||||
function parseRoute(pathname) {
|
||||
const m = pathname.match(/^\/incidents\/(\d+)\/?$/);
|
||||
if (m) return { section: 'queue', incident: Number(m[1]) };
|
||||
@@ -132,6 +143,35 @@ function render() {
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
// ---------- nav menu ----------
|
||||
|
||||
// The mobile hamburger menu: same shape as the sheet-based action menus in
|
||||
// incident.js (openSheet + a <ul class="menu"> of menu-item buttons), one
|
||||
// item per NAV_ITEMS entry, resolving with a path for navigate() to use.
|
||||
function openNavMenu() {
|
||||
const current = SECTIONS[route.section].nav || route.section;
|
||||
const triggered = state.open.filter((i) => i.status === 'triggered').length;
|
||||
const items = NAV_ITEMS.filter((n) => !n.adminOnly || state.me?.user?.is_admin);
|
||||
ui.openSheet(() => [
|
||||
ui.h('h2', { class: 'sheet-title', text: 'Sections' }),
|
||||
ui.h('ul', { class: 'menu', role: 'menu' }, items.map((n) =>
|
||||
ui.h('li', {}, ui.h('button', {
|
||||
class: 'menu-item',
|
||||
type: 'button',
|
||||
role: 'menuitemradio',
|
||||
'aria-checked': String(n.section === current),
|
||||
onclick: () => ui.closeSheet(n.path),
|
||||
},
|
||||
ui.icon(n.icon),
|
||||
n.label,
|
||||
n.section === 'queue' && triggered > 0 && ui.badge(String(triggered), 'st-triggered menu-sub'),
|
||||
))),
|
||||
),
|
||||
]).then((path) => {
|
||||
if (path) navigate(path);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- refresh + badges ----------
|
||||
|
||||
async function refresh() {
|
||||
@@ -158,9 +198,12 @@ function updateBadges() {
|
||||
pill.classList.toggle('has-triggered', triggered > 0);
|
||||
pill.classList.toggle('all-acked', open > 0 && triggered === 0);
|
||||
|
||||
const badge = document.querySelector('[data-badge]');
|
||||
badge.hidden = triggered === 0;
|
||||
badge.textContent = String(triggered);
|
||||
// Two badges carry this count: the sidebar's Queue tab (desktop) and the
|
||||
// hamburger button (mobile) — only one of the two is ever visible at once.
|
||||
for (const badge of document.querySelectorAll('[data-badge]')) {
|
||||
badge.hidden = triggered === 0;
|
||||
badge.textContent = String(triggered);
|
||||
}
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
@@ -180,6 +223,7 @@ async function boot() {
|
||||
document.addEventListener('keydown', onKey);
|
||||
$('login-form').addEventListener('submit', onLogin);
|
||||
$('signup-form').addEventListener('submit', onSignup);
|
||||
$('menu-btn').addEventListener('click', openNavMenu);
|
||||
|
||||
// /signup is the one route that works without a session.
|
||||
if (location.pathname.replace(/\/$/, '') === '/signup') {
|
||||
|
||||
Reference in New Issue
Block a user