Make info panels collapsible with remembered state

Each main section with a data-panel key can now be folded by clicking its
heading (rotating chevron, keyboard-accessible via role/tabindex/aria-expanded
and Enter/Space). Collapsed keys are persisted in localStorage and restored on
load, so the layout stays how you left it across reloads.

State is device-global like the theme (a single non-namespaced key, not
per-user data), and only collapsed panels are stored — so any panel added
later defaults to open.
This commit is contained in:
Alexander Heldt
2026-07-10 16:57:48 +00:00
parent f4ce7dcb54
commit 561b98b64f
3 changed files with 70 additions and 7 deletions
+42
View File
@@ -1769,6 +1769,48 @@
if (ev) openEditDialog(ev);
});
// ---------- collapsible panels ----------
// Each main section carrying a data-panel key can be folded by clicking its
// heading; collapsed keys are remembered across reloads. State is device-global
// (like the theme), so a single non-namespaced key is fine — it's not per-user
// data. Only collapsed panels are stored, so newly added panels default open.
const PANELS_KEY = "puppy-tracker:panels:v1";
function loadPanelState() {
try {
const s = JSON.parse(localStorage.getItem(PANELS_KEY));
return s && typeof s === "object" ? s : {};
} catch { return {}; }
}
function savePanelState(state) {
try { localStorage.setItem(PANELS_KEY, JSON.stringify(state)); } catch { /* ignore */ }
}
function initPanels() {
const state = loadPanelState();
document.querySelectorAll("main section[data-panel]").forEach(section => {
const key = section.dataset.panel;
const h2 = section.querySelector(":scope > h2");
if (!h2) return;
section.classList.add("collapsible");
const collapsed = !!state[key];
section.classList.toggle("collapsed", collapsed);
h2.setAttribute("role", "button");
h2.setAttribute("tabindex", "0");
h2.setAttribute("aria-expanded", String(!collapsed));
const toggle = () => {
const nowCollapsed = section.classList.toggle("collapsed");
h2.setAttribute("aria-expanded", String(!nowCollapsed));
const s = loadPanelState();
if (nowCollapsed) s[key] = true; else delete s[key];
savePanelState(s);
};
h2.addEventListener("click", toggle);
h2.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggle(); }
});
});
}
initPanels();
// ---------- wiring ----------
document.querySelectorAll("button.action").forEach(btn => {
btn.addEventListener("click", () => {
+7 -7
View File
@@ -97,7 +97,7 @@
<button type="button" id="day-next" class="ghost" aria-label="Next day"></button>
</section>
<section class="overview">
<section class="overview" data-panel="overview">
<h2 id="overview-title">Today's overview</h2>
<div class="stats">
<div class="stat">
@@ -131,7 +131,7 @@
</div>
</section>
<section class="timing">
<section class="timing" data-panel="timing">
<h2>Bathroom timing <span class="muted-note">(last 7 days)</span></h2>
<div class="lasts">
<div class="last-row"><span>Typical time between pees</span><span id="gap-pee"></span></div>
@@ -142,19 +142,19 @@
<p class="muted-note timing-hint" id="timing-hint"></p>
</section>
<section class="sleep">
<section class="sleep" data-panel="sleep-windows">
<h2>Sleep windows</h2>
<ul id="sleep-list" class="wake-list"></ul>
<p id="sleep-empty" class="empty">No sleep windows yet for this day.</p>
</section>
<section class="wake">
<section class="wake" data-panel="wake-windows">
<h2>Wake windows</h2>
<ul id="wake-list" class="wake-list"></ul>
<p id="wake-empty" class="empty">No wake windows yet for this day.</p>
</section>
<section class="weekly">
<section class="weekly" data-panel="weekly">
<h2>Last 7 days</h2>
<div class="chart">
<div class="chart-title">Sleep (hours)</div>
@@ -183,7 +183,7 @@
<p class="muted-note">Darker = happens more often at that hour.</p>
</section>
<section class="weight">
<section class="weight" data-panel="weight">
<h2>Weight</h2>
<div class="weight-summary">
<div class="stat">
@@ -204,7 +204,7 @@
<p id="weight-empty" class="empty">No weigh-ins logged yet.</p>
</section>
<section class="history">
<section class="history" data-panel="history">
<h2>History</h2>
<ul id="event-list" class="event-list"></ul>
<p id="empty-state" class="empty">No events logged for this day.</p>
+21
View File
@@ -756,3 +756,24 @@ input.switch:checked::after { transform: translateX(18px); }
.chart-svg .hm-poo { fill: var(--poo); }
.chart-svg .hm-eat { fill: var(--eat); }
/* ---------- collapsible panels ---------- */
section.collapsible > h2 {
cursor: pointer;
position: relative;
padding-right: 20px;
-webkit-user-select: none;
user-select: none;
}
section.collapsible > h2::after {
content: "▾";
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
transition: transform 0.15s ease;
color: var(--muted);
font-size: 0.85em;
}
section.collapsed > h2::after { transform: translateY(-50%) rotate(-90deg); }
section.collapsed > h2 { margin-bottom: 0; }
section.collapsed > :not(h2) { display: none; }