diff --git a/internal/web/static/app.css b/internal/web/static/app.css index 00e26f3..5b6be76 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -583,6 +583,10 @@ details[open] > summary { margin-bottom: 8px; } } .actionbar .btn { min-height: 48px; } .actionbar .btn-primary { flex: 1; font-size: 16px; } +/* The extra actions that only phones hide behind "More" — see actionBar() and + extraActions() in incident.js. Hidden by default; the desktop block below + shows them and hides the now-redundant More button instead. */ +.action-extra { display: none; } .detail-placeholder { display: grid; place-items: center; height: 100%; @@ -669,7 +673,7 @@ details[open] > summary { margin-bottom: 8px; } caught by the unrelated .label > span styling meant for label chips. */ .week-nav .week-label { font-size: 14px; font-weight: 650; min-width: 11.5em; text-align: center; white-space: nowrap; } .week-label small { color: var(--faint); font-weight: 600; font-size: 11px; margin-left: 2px; } -.days { list-style: none; margin: 0; padding: 0; } +.days { list-style: none; margin: 0 auto; padding: 0; } .day { display: grid; grid-template-columns: 3.2em 4.2em 1fr; align-items: center; gap: 8px; min-height: 50px; padding: 0 14px; } .day + .day { border-top: 1px solid var(--border); } .day-name { font-weight: 650; } @@ -773,7 +777,15 @@ kbd { .pane-list .chips { flex-wrap: wrap; overflow-x: visible; } .pane-list .chip-sep { display: none; } .chips-fade { display: none; } - .view-queue:not(.has-detail) .pane-detail { display: block; } + /* With nothing selected there is no detail to show next to, so the list + takes the whole row instead of leaving the second column as dead space + around the placeholder text. Selecting an incident (.has-detail) drops + back to the base minmax(340,420) 1fr rule above. */ + .view-queue:not(.has-detail) { grid-template-columns: 1fr; } + .view-queue:not(.has-detail) .pane-list { border-right: 0; } + /* Full width reads better capped than edge-to-edge on a very wide monitor, + matching .detail's own cap below. */ + .view-queue:not(.has-detail) .list { max-width: 900px; margin: 0 auto; } /* On desktop the list stays visible next to the detail. */ .app.detail-open .nav { display: flex; } @@ -787,8 +799,12 @@ kbd { .actionbar { position: sticky; bottom: 0; padding: 12px 32px; + flex-wrap: wrap; } .actionbar .btn-primary { flex: 0 1 240px; } + /* Room enough to show every action, so More is fully redundant here. */ + .action-extra { display: inline-flex; } + .more-btn { display: none; } .sheet { width: min(440px, calc(100% - 32px)); diff --git a/internal/web/static/js/account.js b/internal/web/static/js/account.js index 80a7bb5..c8f5cba 100644 --- a/internal/web/static/js/account.js +++ b/internal/web/static/js/account.js @@ -33,7 +33,9 @@ function render() { h('div', { class: 'card' }, shortcuts())), h('div', { class: 'page-head' }), - h('button', { class: 'btn btn-block', type: 'button', onclick: signOut }, icon('logout'), 'Sign out'), + // Wrapped in a div: .btn is inline-flex, and only a block-level element + // picks up .view-page > *'s margin:auto centering (see app.css:316). + h('div', {}, h('button', { class: 'btn btn-block', type: 'button', onclick: signOut }, icon('logout'), 'Sign out')), ); } diff --git a/internal/web/static/js/adminteam.js b/internal/web/static/js/adminteam.js index 2261575..b844fdf 100644 --- a/internal/web/static/js/adminteam.js +++ b/internal/web/static/js/adminteam.js @@ -96,7 +96,9 @@ function render() { } function backLink() { - return h('a', { class: 'back-link', href: '/admin/teams' }, icon('chevronLeft'), h('span', { text: 'Teams' })); + // Wrapped in a div: .back-link is inline-flex, and only a block-level + // element picks up .view-page > *'s margin:auto centering (app.css:316). + return h('div', {}, h('a', { class: 'back-link', href: '/admin/teams' }, icon('chevronLeft'), h('span', { text: 'Teams' }))); } // --- identity -------------------------------------------------------------- diff --git a/internal/web/static/js/adminuser.js b/internal/web/static/js/adminuser.js index b04888d..626d783 100644 --- a/internal/web/static/js/adminuser.js +++ b/internal/web/static/js/adminuser.js @@ -83,7 +83,9 @@ function render() { } function backLink() { - return h('a', { class: 'back-link', href: '/admin/users' }, icon('chevronLeft'), h('span', { text: 'Users' })); + // Wrapped in a div: .back-link is inline-flex, and only a block-level + // element picks up .view-page > *'s margin:auto centering (app.css:316). + return h('div', {}, h('a', { class: 'back-link', href: '/admin/users' }, icon('chevronLeft'), h('span', { text: 'Users' }))); } // --- identity -------------------------------------------------------------- diff --git a/internal/web/static/js/incident.js b/internal/web/static/js/incident.js index 3b385cf..97134f5 100644 --- a/internal/web/static/js/incident.js +++ b/internal/web/static/js/incident.js @@ -453,9 +453,25 @@ function quickActions() { return div; } +// Desktop has room to show what a phone folds into the More sheet below — see +// the .action-extra/.more-btn rules in app.css. Resolved/archived incidents +// already say everything via primaryAction()/secondaryAction(), so there is +// nothing extra to surface for them. +function extraActions() { + if (!isOpen()) return []; + const out = [ + h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: assign }, icon('user'), 'Assign…'), + h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: addNote }, icon('note'), 'Add note…'), + ]; + out.push(inc.status === 'acknowledged' + ? h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: unacknowledge }, icon('undo'), 'Clear ack') + : h('button', { class: 'btn btn-sm action-extra', type: 'button', onclick: resolve }, icon('checkCircle'), 'Resolve…')); + return out; +} + function actionBar() { - const more = h('button', { class: 'btn', type: 'button', 'aria-label': 'More actions', onclick: moreMenu }, icon('more'), 'More'); - const bar = h('div', { class: 'actionbar' }, primaryAction(), secondaryAction(), more); + const more = h('button', { class: 'btn more-btn', type: 'button', 'aria-label': 'More actions', onclick: moreMenu }, icon('more'), 'More'); + const bar = h('div', { class: 'actionbar' }, primaryAction(), secondaryAction(), ...extraActions(), more); if (busy) for (const b of bar.querySelectorAll('button')) b.disabled = true; return bar; }