Sticky status headers; fix floating details-view button on phones
- Status group headers ("Not Started (37)") stick under the top nav while
scrolling through their group, on every screen size
- On desktop the column header and filter rows stick beneath the group
header (table wrappers no longer clip them; these fixed-layout tables
never scroll sideways at desktop widths)
- layout.js measures the nav, group header and header row heights so the
sticky rows stack without gaps, re-measuring on re-render/resize/scroll
- Phones: the Advanced/Simple View button sits on its own line under the
device name instead of floating over the dialog content
- Rename mobile.css/js to layout.css/js now that they hold desktop rules
This commit is contained in:
1 parent
2cbb03894b
commit
a85c22b2b9
3 files changed
+77
-11
No files matched your search
+2
-2
@@ -351,8 +351,8 @@
|
|||||||
.status-btn:hover { color: white; background: rgba(255,255,255,0.1); }
|
.status-btn:hover { color: white; background: rgba(255,255,255,0.1); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="/mobile.css">
|
<link rel="stylesheet" href="/layout.css">
|
||||||
<script src="/mobile.js"></script>
|
<script src="/layout.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,36 @@
|
|||||||
/*
|
/*
|
||||||
* Small-screen layout for the dashboard (phones, tablets, narrow windows).
|
* Layout additions for the dashboard, loaded after the page's own <style> block:
|
||||||
* Loaded after the page's own <style> block. Desktop layouts are untouched: every rule
|
* - sticky status-group headers (all sizes) and sticky column headers (desktop)
|
||||||
* here is inside a max-width media query.
|
* - card view for tables on phones and tablets
|
||||||
|
* - small-screen fixes for forms, docs and dialogs
|
||||||
|
* layout.js measures the sticky offsets and labels card cells.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------------------
|
||||||
|
* Sticky headers
|
||||||
|
*
|
||||||
|
* A status group's header ("Not Started (37)") sticks under the top nav while you scroll
|
||||||
|
* through that group, then hands over to the next group's header. On desktop the column
|
||||||
|
* header and filter rows stick beneath it. layout.js sets --nav-h (top nav height) and,
|
||||||
|
* per table, --sticky-top (nav + group header) and --thead-r1 (header row height).
|
||||||
|
* ------------------------------------------------------------------------------------- */
|
||||||
|
.group-header { position: sticky; top: var(--nav-h, 60px); z-index: 30; }
|
||||||
|
|
||||||
|
@media (min-width: 1101px) {
|
||||||
|
/* An overflowing wrapper would capture the sticky rows; these tables never need to
|
||||||
|
scroll sideways at desktop widths (they're fixed-layout, 100% wide) */
|
||||||
|
#masterList .table-wrapper, #software .table-wrapper, .stack-box.table-wrapper { overflow: visible !important; }
|
||||||
|
|
||||||
|
.master-table:not(#table-custom) thead th {
|
||||||
|
position: sticky; top: var(--sticky-top, 112px); z-index: 20;
|
||||||
|
}
|
||||||
|
.master-table:not(#table-custom) thead tr.filter-row th {
|
||||||
|
top: calc(var(--sticky-top, 112px) + var(--thead-r1, 38px) - 2px); z-index: 19; /* -2px: no seam from collapsed borders */
|
||||||
|
/* the original header tint, but opaque so rows don't show through */
|
||||||
|
background: linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), var(--bg-card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------------------
|
||||||
* Card view for data tables (phones and tablets)
|
* Card view for data tables (phones and tablets)
|
||||||
*
|
*
|
||||||
@@ -149,6 +176,10 @@
|
|||||||
.modal-header { padding: 14px 14px 8px; }
|
.modal-header { padding: 14px 14px 8px; }
|
||||||
.modal-header h2 { font-size: 1.2em; word-break: break-all; }
|
.modal-header h2 { font-size: 1.2em; word-break: break-all; }
|
||||||
|
|
||||||
|
/* Details title: the Advanced/Simple View button gets its own line under the device
|
||||||
|
name instead of floating over the content */
|
||||||
|
#detailsTitle button { float: none !important; display: block; margin: 10px 0 0 !important; font-size: 0.65em !important; }
|
||||||
|
|
||||||
/* Device details: Advanced View's source / overview / destination columns stack,
|
/* Device details: Advanced View's source / overview / destination columns stack,
|
||||||
and the whole dialog scrolls instead of each column scrolling separately */
|
and the whole dialog scrolls instead of each column scrolling separately */
|
||||||
#detailsModal .modal-content { height: auto !important; overflow-y: auto !important; display: block !important; }
|
#detailsModal .modal-content { height: auto !important; overflow-y: auto !important; display: block !important; }
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
/*
|
/*
|
||||||
* Card layout support for small screens.
|
* Layout helpers (see layout.css).
|
||||||
*
|
*
|
||||||
* On phones and tablets (see mobile.css) each table row is shown as a card, with every
|
* Sticky headers: measures the top nav and each table's group header so the column
|
||||||
* cell rendered as "Column name: value". CSS can't look up a cell's column header, so
|
* header rows can stick directly beneath them (--nav-h, --sticky-top, --thead-r1).
|
||||||
* this copies each header's text onto its cells as data-label. The dashboard re-renders
|
*
|
||||||
* its tables constantly, so labels are reapplied (once per frame at most) whenever the
|
* Card view: on phones and tablets each table row is shown as a card, with every cell
|
||||||
* DOM changes. On wide screens it does nothing.
|
* rendered as "Column name: value". CSS can't look up a cell's column header, so this
|
||||||
|
* copies each header's text onto its cells as data-label.
|
||||||
|
*
|
||||||
|
* The dashboard re-renders its tables constantly, so both are reapplied (once per frame
|
||||||
|
* at most) whenever the DOM changes.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -70,9 +74,36 @@
|
|||||||
if (btn.innerHTML !== html) btn.innerHTML = html;
|
if (btn.innerHTML !== html) btn.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Sticky offsets ---------------------------------------------------------------
|
||||||
|
function setVar(el, name, value) {
|
||||||
|
if (el.style.getPropertyValue(name) !== value) el.style.setProperty(name, value);
|
||||||
|
}
|
||||||
|
function groupHeaderFor(table) {
|
||||||
|
// The group header is a sibling of the table or of its wrapper
|
||||||
|
for (let el = table.parentElement, depth = 0; el && depth < 3; el = el.parentElement, depth++) {
|
||||||
|
const gh = el.querySelector(':scope > .group-header');
|
||||||
|
if (gh) return gh;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function updateSticky() {
|
||||||
|
const nav = document.querySelector('.top-nav');
|
||||||
|
const navH = nav ? nav.offsetHeight : 60;
|
||||||
|
setVar(document.documentElement, '--nav-h', navH + 'px');
|
||||||
|
document.querySelectorAll('table.master-table').forEach(table => {
|
||||||
|
const gh = groupHeaderFor(table);
|
||||||
|
const ghH = gh ? gh.offsetHeight : 0;
|
||||||
|
const firstRow = table.tHead && table.tHead.rows[0];
|
||||||
|
if (!firstRow || !firstRow.offsetHeight) return; // hidden page/group: keep old values
|
||||||
|
setVar(table, '--sticky-top', (navH + ghH) + 'px');
|
||||||
|
setVar(table, '--thead-r1', firstRow.offsetHeight + 'px');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let queued = false;
|
let queued = false;
|
||||||
function run() {
|
function run() {
|
||||||
queued = false;
|
queued = false;
|
||||||
|
updateSticky();
|
||||||
if (!cardsQuery.matches) return;
|
if (!cardsQuery.matches) return;
|
||||||
document.querySelectorAll('table.master-table').forEach(labelTable);
|
document.querySelectorAll('table.master-table').forEach(labelTable);
|
||||||
}
|
}
|
||||||
@@ -86,6 +117,10 @@
|
|||||||
new MutationObserver(schedule).observe(document.body, { childList: true, subtree: true });
|
new MutationObserver(schedule).observe(document.body, { childList: true, subtree: true });
|
||||||
if (cardsQuery.addEventListener) cardsQuery.addEventListener('change', schedule);
|
if (cardsQuery.addEventListener) cardsQuery.addEventListener('change', schedule);
|
||||||
else cardsQuery.addListener(schedule);
|
else cardsQuery.addListener(schedule);
|
||||||
|
window.addEventListener('resize', schedule);
|
||||||
|
// Groups and pages are shown by toggling display (not a DOM change), so also
|
||||||
|
// re-measure as the user scrolls; writes only happen when a value changes.
|
||||||
|
window.addEventListener('scroll', schedule, { passive: true });
|
||||||
schedule();
|
schedule();
|
||||||
}
|
}
|
||||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
|
||||||
Reference in new issue
Block a user