Shell: grouped, collapsible, multi-column per-tile settings popover
The gear popover learns the admin form's conventions: an instanceSchema
field's `group` starts a labeled subsection, `help` puts a line of guidance
under a control, and a manifest `instanceGroups[name]` adds
{ collapsed, columns, help } for that group (rendered as <details> with a
grid body; container queries drop to two or one column in narrow tiles).
/api/modules now exposes instanceGroups. Lets a module keep a long
show/hide checklist per tile — where display choices belong — without
the popover turning into a scroll of thirty loose checkboxes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
20dc2deb2b
commit
01eac1bdc4
+50
-1
@@ -934,11 +934,54 @@ function toggleSettingsPopover(tile, el) {
|
||||
pop.className = 'tile-settings';
|
||||
const inputs = new Map();
|
||||
|
||||
// Fields render in schema order; a field's `group` starts a labeled
|
||||
// subsection that consecutive same-group fields share, and the manifest's
|
||||
// optional `instanceGroups[name]` adds { collapsed, columns, help } —
|
||||
// the same conventions as the admin page's config form.
|
||||
const groupMeta = man.instanceGroups && typeof man.instanceGroups === 'object' ? man.instanceGroups : {};
|
||||
let groupName = null;
|
||||
let container = pop;
|
||||
|
||||
for (const [key, spec] of Object.entries(man.instanceSchema)) {
|
||||
const type = spec?.type || 'string';
|
||||
const label = spec?.label || key;
|
||||
const current = key in (tile.settings || {}) ? tile.settings[key] : spec?.default;
|
||||
|
||||
const g = spec?.group || '';
|
||||
if (g !== groupName) {
|
||||
groupName = g;
|
||||
if (g) {
|
||||
const meta = groupMeta[g] && typeof groupMeta[g] === 'object' ? groupMeta[g] : {};
|
||||
const folds = meta.collapsed !== undefined;
|
||||
const group = document.createElement(folds ? 'details' : 'div');
|
||||
group.className = 'field-group';
|
||||
if (folds) group.open = !meta.collapsed;
|
||||
const title = document.createElement(folds ? 'summary' : 'div');
|
||||
title.className = 'field-group-title';
|
||||
title.textContent = g;
|
||||
group.appendChild(title);
|
||||
const body = document.createElement('div');
|
||||
body.className = 'field-group-body';
|
||||
const cols = Math.min(4, Math.trunc(Number(meta.columns)) || 0);
|
||||
if (cols > 1) {
|
||||
body.classList.add('is-columns');
|
||||
body.style.setProperty('--cols', String(cols));
|
||||
}
|
||||
if (meta.help) {
|
||||
const help = document.createElement('div');
|
||||
help.className = 'field-group-help';
|
||||
help.textContent = meta.help;
|
||||
body.appendChild(help);
|
||||
}
|
||||
group.appendChild(body);
|
||||
pop.appendChild(group);
|
||||
pop.classList.add('has-groups');
|
||||
container = body;
|
||||
} else {
|
||||
container = pop;
|
||||
}
|
||||
}
|
||||
|
||||
const field = document.createElement('label');
|
||||
field.className = 'field' + (type === 'boolean' ? ' check' : '');
|
||||
|
||||
@@ -970,7 +1013,13 @@ function toggleSettingsPopover(tile, el) {
|
||||
field.append(span, input);
|
||||
inputs.set(key, () => (type === 'number' ? Number(input.value) : input.value));
|
||||
}
|
||||
pop.appendChild(field);
|
||||
if (spec?.help) {
|
||||
const help = document.createElement('small');
|
||||
help.className = 'field-help';
|
||||
help.textContent = spec.help;
|
||||
field.appendChild(help);
|
||||
}
|
||||
container.appendChild(field);
|
||||
}
|
||||
|
||||
const actions = document.createElement('div');
|
||||
|
||||
@@ -529,6 +529,70 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
/* A popover with grouped settings needs room for its columns; it still
|
||||
never grows past the tile (max-width above). */
|
||||
.tile-settings.has-groups {
|
||||
min-width: min(540px, calc(100% - 12px));
|
||||
container-type: inline-size;
|
||||
}
|
||||
.tile-settings .field-group {
|
||||
margin: 0 0 10px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.tile-settings .field-group-title {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.tile-settings details.field-group > summary.field-group-title {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
margin-bottom: 0;
|
||||
user-select: none;
|
||||
}
|
||||
.tile-settings details.field-group > summary::-webkit-details-marker { display: none; }
|
||||
.tile-settings details.field-group > summary.field-group-title::before {
|
||||
content: "";
|
||||
width: 0; height: 0;
|
||||
border-left: 5px solid var(--muted);
|
||||
border-top: 4px solid transparent;
|
||||
border-bottom: 4px solid transparent;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.tile-settings details.field-group[open] > summary.field-group-title::before { transform: rotate(90deg); }
|
||||
.tile-settings details.field-group[open] > summary.field-group-title { margin-bottom: 8px; }
|
||||
.tile-settings .field-group .field:last-child { margin-bottom: 0; }
|
||||
.tile-settings .field-group-body.is-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
|
||||
gap: 3px 14px;
|
||||
}
|
||||
.tile-settings .field-group-body.is-columns .field { margin-bottom: 3px; }
|
||||
.tile-settings .field-group-body.is-columns .field-group-help { grid-column: 1 / -1; }
|
||||
@container (max-width: 420px) {
|
||||
.tile-settings .field-group-body.is-columns { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
@container (max-width: 260px) {
|
||||
.tile-settings .field-group-body.is-columns { grid-template-columns: 1fr; }
|
||||
}
|
||||
.tile-settings .field-group-help,
|
||||
.tile-settings .field-help {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
color: var(--muted);
|
||||
}
|
||||
.tile-settings .field-group-help { margin: -2px 0 6px; }
|
||||
.tile-settings .field-help { margin-top: 1px; }
|
||||
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user