- Compare: move the "Judge each day by its" basis toggle out of the filter sheet into the inline comfort card above the results (a scoring control, like the comfort slider). Handler is id-based, so no JS change for the move. - Metric selector is duplicated, not relocated: revert the sheetOnly relocation in filtersheet.js; keep the inline selector visible on all viewports and add a synced copy inside each page's filter sheet (mobile-only via CSS). The sheet copy clones the inline buttons; a single setter (setDistMetric / setColorMetric) drives the shared state and reflects the active metric on both, so picking any of the 8 metrics in either selector syncs the other and re-renders. Applies to compare's "Distribution by" and calendar's "Color the calendar by". Frontend-only; no backend or payload change.
72 lines
3.2 KiB
JavaScript
72 lines
3.2 KiB
JavaScript
// Mobile filter access: a small floating "Filters" pill (fixed, reachable from
|
|
// anywhere on the page) that slides the page's existing filter panel up as a bottom
|
|
// sheet. Desktop is untouched — the pill, scrim and grip are display:none above the
|
|
// phone breakpoint, so the panel stays inline exactly as before (all styling lives in
|
|
// the @media(max-width:640px) block in style.css). Shared by compare + calendar.
|
|
//
|
|
// initFilterSheet({ panel, label })
|
|
// panel — the filter panel element (becomes the sheet on mobile)
|
|
// label — text on the pill and the sheet header (e.g. "Filters")
|
|
// returns { setActive(bool), close() }
|
|
|
|
const FUNNEL = `<svg class="ff-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 5h18M6 12h12M10 19h4"/></svg>`;
|
|
|
|
export function initFilterSheet({ panel, label = "Filters" }) {
|
|
if (!panel) return { setActive() {}, close() {} };
|
|
|
|
// The pill lives in <body> so it floats over the whole page regardless of scroll.
|
|
const fab = document.createElement("button");
|
|
fab.type = "button";
|
|
fab.className = "filter-fab";
|
|
fab.setAttribute("aria-expanded", "false");
|
|
fab.innerHTML = `${FUNNEL}<span class="ff-label">${label}</span><span class="ff-dot" hidden aria-hidden="true"></span>`;
|
|
|
|
const scrim = document.createElement("div");
|
|
scrim.className = "filter-scrim";
|
|
scrim.hidden = true;
|
|
|
|
// A grab handle + title + close, prepended to the panel; only shown in sheet mode.
|
|
const grip = document.createElement("div");
|
|
grip.className = "filter-sheet-grip";
|
|
grip.innerHTML = `<span class="fsg-bar" aria-hidden="true"></span>` +
|
|
`<span class="fsg-title">${label}</span>` +
|
|
`<button type="button" class="fsg-close" aria-label="Close filters">✕</button>`;
|
|
panel.prepend(grip);
|
|
|
|
document.body.append(scrim, fab);
|
|
document.body.classList.add("has-filter-sheet"); // scopes the extra bottom padding
|
|
|
|
const isOpen = () => panel.classList.contains("open");
|
|
function open() {
|
|
panel.classList.add("open");
|
|
scrim.hidden = false;
|
|
fab.setAttribute("aria-expanded", "true");
|
|
}
|
|
function close() {
|
|
panel.classList.remove("open");
|
|
scrim.hidden = true;
|
|
fab.setAttribute("aria-expanded", "false");
|
|
}
|
|
|
|
fab.addEventListener("click", () => (isOpen() ? close() : open()));
|
|
scrim.addEventListener("click", close);
|
|
grip.querySelector(".fsg-close").addEventListener("click", close);
|
|
document.addEventListener("keydown", (e) => { if (e.key === "Escape" && isOpen()) close(); });
|
|
|
|
// The pill only makes sense once the page has data to filter — the panel starts
|
|
// hidden and the page reveals it. Mirror that onto the pill, and drop the sheet if
|
|
// the panel goes away.
|
|
const reflect = () => {
|
|
fab.classList.toggle("ready", !panel.hidden);
|
|
if (panel.hidden && isOpen()) close();
|
|
};
|
|
new MutationObserver(reflect).observe(panel, { attributes: true, attributeFilter: ["hidden"] });
|
|
reflect();
|
|
|
|
return {
|
|
// Show a dot on the pill when a filter subset is active (so the applied filter is
|
|
// visible even with the sheet closed).
|
|
setActive(active) { fab.querySelector(".ff-dot").hidden = !active; },
|
|
close,
|
|
};
|
|
}
|