Shell: color field type (swatch picker) in tile settings and admin config
Renders <input type="color"> with the label beside it; the stored value is always a #rrggbb string (3-digit hex widened, anything else falls back to the schema default). Same styling in the gear popover and the admin form. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4c5d7956ab
commit
a71b14d755
@@ -64,7 +64,7 @@ shell code changes, ever.
|
||||
Both schemas map field names to specs:
|
||||
|
||||
```
|
||||
{ "type": "string" | "number" | "boolean" | "switch" | "select" | "password" | "endpoint",
|
||||
{ "type": "string" | "number" | "boolean" | "switch" | "select" | "color" | "password" | "endpoint",
|
||||
"label": "Shown next to the field",
|
||||
"default": <value>,
|
||||
"group": "Section heading", // optional; see below
|
||||
@@ -78,6 +78,8 @@ Both schemas map field names to specs:
|
||||
|
||||
`boolean` renders as a checkbox; `switch` is the same true/false value shown
|
||||
as a toggle (use it for a feature on/off that other fields depend on).
|
||||
`color` renders a swatch picker and stores a `#rrggbb` string (give it a hex
|
||||
default; the tile applies it as a CSS custom property — see `pco-plan`).
|
||||
|
||||
`group` gathers consecutive fields under a labeled subsection in the admin
|
||||
page, so a module with several distinct concerns (e.g. a ProPresenter
|
||||
|
||||
@@ -209,6 +209,17 @@ function fieldFor(key, spec, value, passwordSet, moduleId) {
|
||||
field.append(span, select);
|
||||
read = () => select.value;
|
||||
selectEl = select;
|
||||
} else if (type === 'color') {
|
||||
// A swatch picker; the value is always a #rrggbb string.
|
||||
field.classList.add('color-field');
|
||||
const span = document.createElement('span');
|
||||
span.textContent = label;
|
||||
const input = document.createElement('input');
|
||||
input.type = 'color';
|
||||
const hex = (v, fb) => (/^#[0-9a-fA-F]{6}$/.test(String(v ?? '').trim()) ? String(v).trim().toLowerCase() : fb);
|
||||
input.value = hex(value, hex(spec?.default, '#2ee59a'));
|
||||
field.append(span, input);
|
||||
read = () => input.value;
|
||||
} else {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = label;
|
||||
|
||||
@@ -919,6 +919,14 @@ function unmountModule(tile) {
|
||||
|
||||
/* ── per-tile settings popover ──────────────────────────────────────── */
|
||||
|
||||
/** `<input type="color">` only accepts #rrggbb — anything else falls back. */
|
||||
function colorHex(value, fallback) {
|
||||
const s = String(value ?? '').trim();
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(s)) return s.toLowerCase();
|
||||
if (/^#[0-9a-fA-F]{3}$/.test(s)) return ('#' + s.slice(1).split('').map((c) => c + c).join('')).toLowerCase();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function closeSettingsPopovers() {
|
||||
document.querySelectorAll('.tile-settings').forEach((p) => p.remove());
|
||||
}
|
||||
@@ -1004,6 +1012,16 @@ function toggleSettingsPopover(tile, el) {
|
||||
select.value = String(current ?? '');
|
||||
field.append(span, select);
|
||||
inputs.set(key, () => select.value);
|
||||
} else if (type === 'color') {
|
||||
// A swatch picker; the value is always a #rrggbb string.
|
||||
field.classList.add('color-field');
|
||||
const span = document.createElement('span');
|
||||
span.textContent = label;
|
||||
const input = document.createElement('input');
|
||||
input.type = 'color';
|
||||
input.value = colorHex(current, colorHex(spec?.default, '#2ee59a'));
|
||||
field.append(span, input);
|
||||
inputs.set(key, () => input.value);
|
||||
} else {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = label;
|
||||
|
||||
@@ -626,6 +626,27 @@ body.fullscreen #grid { padding-bottom: 0; }
|
||||
}
|
||||
.field.check input { width: 15px; height: 15px; accent-color: var(--accent); }
|
||||
|
||||
/* color fields: label on the left, swatch on the right */
|
||||
.field.color-field {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.field.color-field > span { flex: 1; min-width: 0; }
|
||||
.field.color-field input[type="color"] {
|
||||
width: 44px;
|
||||
height: 26px;
|
||||
padding: 2px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
.field.color-field input[type="color"]:focus { outline: none; border-color: var(--accent); }
|
||||
.field.color-field .field-help { flex-basis: 100%; }
|
||||
|
||||
/* ── modal dialogs (layout name prompt etc.) ───────────────────────── */
|
||||
|
||||
#modal-backdrop {
|
||||
|
||||
Reference in New Issue
Block a user