Admin: a module card is a status line until you open it

Every module's settings were always spread out, so the page read as a wall
of forms. A card now shows what you check at a glance — dot, name, version,
description, status readout, the enabled switch, Uninstall — with one button
at the bottom right: Configure opens the settings and becomes Apply, which
saves without closing them. Clicking the module name opens and closes the
settings too (keyboard as well: it is a focusable button). A card stays open
across a reload for the rest of the session, so re-checking something after
an Apply doesn't fold it away. A module with nothing to configure has no
button.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-09-13 14:03:10 -04:00
co-authored by Claude Fable 5.1
parent b9d9e2894a
commit e7b0ea6ebf
2 changed files with 30 additions and 5 deletions
+5 -2
View File
@@ -295,13 +295,16 @@ details.field-group > summary.field-group-title:hover { color: var(--text); }
} }
.field.switch-field .switch-label { font-size: 13px; } .field.switch-field .switch-label { font-size: 13px; }
/* status on the left, Apply on the right */ /* the card's one action row: status on the left, Configure / Apply on the right */
.module-actions { .module-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
margin-top: 4px; margin-top: 10px;
} }
.module-name.is-toggle { cursor: pointer; }
.module-name.is-toggle:hover,
.module-name.is-toggle:focus-visible { color: var(--accent); outline: none; }
.module-actions .admin-status { flex: 1; margin-top: 0; } .module-actions .admin-status { flex: 1; margin-top: 0; }
.module-actions .btn { margin-left: auto; } .module-actions .btn { margin-left: auto; }
+25 -3
View File
@@ -585,15 +585,37 @@ function buildModuleCard(mod) {
const actions = document.createElement('div'); const actions = document.createElement('div');
actions.className = 'module-actions'; actions.className = 'module-actions';
const save = document.createElement('button'); const save = document.createElement('button');
save.className = 'btn primary';
save.textContent = 'Apply';
const status = document.createElement('span'); const status = document.createElement('span');
status.className = 'admin-status'; status.className = 'admin-status';
actions.append(status, save); actions.append(status, save);
form.appendChild(actions); // A card is a status line until someone wants to change something: the
// form starts folded, and the row at the bottom holds one button that
// does both jobs — "Configure" opens the form and becomes "Apply", which
// saves without closing it. The name is the other way in and out.
card.appendChild(form); card.appendChild(form);
card.appendChild(actions);
const openKey = 'proddash:admin:open:' + mod.id;
const setOpen = (open) => {
form.hidden = !open;
status.hidden = !open;
save.textContent = open ? 'Apply' : 'Configure';
save.className = open ? 'btn primary' : 'btn';
try { sessionStorage.setItem(openKey, open ? '1' : '0'); } catch { /* fine */ }
};
let wasOpen = false;
try { wasOpen = sessionStorage.getItem(openKey) === '1'; } catch { /* fine */ }
setOpen(wasOpen);
name.classList.add('is-toggle');
name.title = 'Show or hide this module\'s settings';
name.tabIndex = 0;
name.setAttribute('role', 'button');
name.addEventListener('click', () => setOpen(form.hidden));
name.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setOpen(form.hidden); }
});
save.addEventListener('click', async () => { save.addEventListener('click', async () => {
if (form.hidden) return setOpen(true);
const config = {}; const config = {};
for (const [key, read] of readers) config[key] = read(); for (const [key, read] of readers) config[key] = read();
save.disabled = true; save.disabled = true;