Open tile settings in a sheet above the mockup and remove tiles in place
App 0.2.14: clicking a card opens a settings sheet (bottom sheet on mobile) with segmented choices that apply live to the mockup behind it, so the page no longer scrolls to a list below. Each card gets a remove button with an undo toast; the old per-tile list is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+153
-193
@@ -13,11 +13,18 @@ const node = (tag, text, cls) => {
|
||||
if (cls) n.className = cls;
|
||||
return n;
|
||||
};
|
||||
function toast(message) {
|
||||
$("#toast").textContent = message;
|
||||
$("#toast").hidden = false;
|
||||
function toast(message, action) {
|
||||
const t = $("#toast");
|
||||
t.replaceChildren(node("span", message));
|
||||
if (action) {
|
||||
const b = node("button", action.label);
|
||||
b.type = "button";
|
||||
b.onclick = () => { action.run(); t.hidden = true; };
|
||||
t.append(b);
|
||||
}
|
||||
t.hidden = false;
|
||||
clearTimeout(toast.timer);
|
||||
toast.timer = setTimeout(() => ($("#toast").hidden = true), 5000);
|
||||
toast.timer = setTimeout(() => (t.hidden = true), action ? 8000 : 5000);
|
||||
}
|
||||
async function api(path, options = {}) {
|
||||
const response = await fetch(`api/${path}`, {
|
||||
@@ -53,6 +60,7 @@ function select(id) {
|
||||
return;
|
||||
selected = id;
|
||||
selectedTile = null;
|
||||
if ($("#tile-sheet").open) $("#tile-sheet").close();
|
||||
const screen = inventory.screens.find((s) => s.id === id);
|
||||
if (!screen) return;
|
||||
layout = structuredClone(screen.layout);
|
||||
@@ -284,7 +292,9 @@ function renderPreview() {
|
||||
if (place.page !== page) return;
|
||||
// Keep the grid honest: an empty right column before a wide tile is a real gap on the screen.
|
||||
for (; filled < place.slot; filled++) grid.append(node("span", "", "preview-gap"));
|
||||
const card = node("button", undefined, "preview-tile");
|
||||
const card = node("div", undefined, "preview-tile");
|
||||
card.tabIndex = 0;
|
||||
card.setAttribute("role", "button");
|
||||
const name = tile.name || entityName(tile.entity);
|
||||
const background=inventory.backgrounds?.[tile.options?.background]?.color;
|
||||
if(background)card.style.backgroundColor=background;
|
||||
@@ -294,11 +304,14 @@ function renderPreview() {
|
||||
enableDrag(card, { kind: "tile", index });
|
||||
card.classList.toggle("chosen", selectedTile === tile.entity);
|
||||
card.setAttribute("aria-label", `Tegel ${index + 1}: ${name}, instellen`);
|
||||
card.onclick = () => {
|
||||
selectedTile = tile.entity;
|
||||
renderTiles();
|
||||
document.querySelectorAll("#tiles > .tile")[index]?.scrollIntoView({behavior: "smooth", block: "center"});
|
||||
};
|
||||
card.onclick = () => openTileSheet(index);
|
||||
card.onkeydown = (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openTileSheet(index); } };
|
||||
const remove = node("button", "✕", "preview-remove");
|
||||
remove.type = "button";
|
||||
remove.title = "Tegel verwijderen";
|
||||
remove.setAttribute("aria-label", `${name} verwijderen`);
|
||||
remove.onclick = (e) => { e.stopPropagation(); removeTile(index); };
|
||||
card.append(remove);
|
||||
if (tile.options?.inline === "slider") card.append(node("span", "", "preview-slider"));
|
||||
const display = tile.options?.display;
|
||||
if (display && display !== "standard") card.append(node("small", displayNames[display] || display));
|
||||
@@ -318,193 +331,140 @@ function renderPreview() {
|
||||
}
|
||||
function renderTiles() {
|
||||
renderPreview();
|
||||
$("#tiles").replaceChildren();
|
||||
$("#count").textContent = `${layout.tiles.length} / ${tileLimit()}${tileLimit()===10?" · update firmware voor 20":""}`;
|
||||
$("#no-tiles").hidden = layout.tiles.length > 0;
|
||||
const { placement } = packTiles(layout.tiles);
|
||||
layout.tiles.forEach((tile, i) => {
|
||||
if (i > 0 && placement[i].page !== placement[i - 1].page) $("#tiles").append(node("li", `Pagina ${placement[i].page + 1}`, "page-break"));
|
||||
const li = node("li", undefined, "tile");
|
||||
li.classList.toggle("selected-tile", selectedTile === tile.entity);
|
||||
const content = node("div"),
|
||||
input = node("input");
|
||||
input.value = tile.name;
|
||||
input.placeholder = entityName(tile.entity);
|
||||
input.maxLength = 60;
|
||||
input.setAttribute("aria-label", `Naam voor tegel ${i + 1}`);
|
||||
input.oninput = () => {
|
||||
tile.name = input.value;
|
||||
if (sheetIndex >= 0) renderTileSheet();
|
||||
}
|
||||
function removeTile(index) {
|
||||
const [tile] = layout.tiles.splice(index, 1);
|
||||
if (!tile) return;
|
||||
if (sheetIndex >= 0) closeTileSheet();
|
||||
markDirty();
|
||||
renderTiles();
|
||||
renderResults();
|
||||
toast(`${tile.name || entityName(tile.entity)} verwijderd`, {
|
||||
label: "Ongedaan maken",
|
||||
run: () => { layout.tiles.splice(Math.min(index, layout.tiles.length), 0, tile); markDirty(); renderTiles(); renderResults(); },
|
||||
});
|
||||
}
|
||||
// Tile settings open in a sheet above the mockup; every change applies live,
|
||||
// so the card behind it shows the result while you pick.
|
||||
let sheetIndex = -1;
|
||||
function openTileSheet(index) {
|
||||
sheetIndex = index;
|
||||
selectedTile = layout.tiles[index].entity;
|
||||
renderTileSheet();
|
||||
renderPreview();
|
||||
const sheet = $("#tile-sheet");
|
||||
if (!sheet.open) sheet.showModal();
|
||||
}
|
||||
function closeTileSheet() {
|
||||
sheetIndex = -1;
|
||||
selectedTile = null;
|
||||
const sheet = $("#tile-sheet");
|
||||
if (sheet.open) sheet.close();
|
||||
renderPreview();
|
||||
}
|
||||
$("#tile-sheet").addEventListener("close", () => { if (sheetIndex >= 0) { sheetIndex = -1; selectedTile = null; renderPreview(); } });
|
||||
function field(title, control) {
|
||||
const label = node("label", undefined, "sheet-field");
|
||||
label.append(node("span", title), control);
|
||||
return label;
|
||||
}
|
||||
function segmented(choices, value, onChange) {
|
||||
const group = node("div", undefined, "segmented");
|
||||
for (const [key, text] of choices) {
|
||||
const b = node("button", text);
|
||||
b.type = "button";
|
||||
b.setAttribute("aria-pressed", String(String(key) === String(value)));
|
||||
b.onclick = () => { for (const other of group.children) other.setAttribute("aria-pressed", String(other === b)); onChange(key); };
|
||||
group.append(b);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
function renderTileSheet() {
|
||||
const sheet = $("#tile-sheet"), tile = layout.tiles[sheetIndex];
|
||||
if (!tile) return;
|
||||
sheet.replaceChildren();
|
||||
const domain = tile.entity.split(".")[0], name = entityName(tile.entity);
|
||||
const head = node("div", undefined, "sheet-head"), titles = node("div");
|
||||
titles.append(node("strong", name), node("small", tile.entity));
|
||||
const close = node("button", "✕", "quiet sheet-close");
|
||||
close.type = "button";
|
||||
close.setAttribute("aria-label", "Sluiten");
|
||||
close.onclick = closeTileSheet;
|
||||
head.append(domainBadge(tile.entity), titles, close);
|
||||
const body = node("div", undefined, "sheet-body");
|
||||
const nameInput = node("input");
|
||||
nameInput.value = tile.name;
|
||||
nameInput.placeholder = name;
|
||||
nameInput.maxLength = 60;
|
||||
nameInput.oninput = () => { tile.name = nameInput.value; markDirty(); renderPreview(); };
|
||||
body.append(field("Naam op het scherm", nameInput));
|
||||
const displays = domain === "screen"
|
||||
? [["digital", "Digitale klok"], ["analog", "Analoge klok"]]
|
||||
: [["standard", "Naam en status"], ["watch", "Grote waarde"]];
|
||||
if (domain === "weather") displays.push(["forecast", "Weersvoorspelling"]);
|
||||
if (domain === "sensor") displays.push(["graph", "Grafiek"]);
|
||||
if (domain === "sun") displays.push(["sunpath", "Zonnebaan"]);
|
||||
const current = (key, fallback) => tile.options?.[key] ?? fallback;
|
||||
const set = (key, value) => {
|
||||
tile.options = { ...tile.options, [key]: value };
|
||||
if (key === "display" && value === "watch") tile.options.inline = "none";
|
||||
if (key === "display" && ["forecast", "sunpath"].includes(value)) tile.options.size = "wide";
|
||||
if (key === "inline" && value === "slider") tile.options.display = "standard";
|
||||
markDirty();
|
||||
renderTiles();
|
||||
};
|
||||
body.append(field("Weergave", segmented(displays, current("display", domain === "screen" ? "digital" : "standard"), (v) => set("display", v))));
|
||||
body.append(field("Breedte", segmented([["single", "Normaal"], ["wide", "Dubbelbreed"]], current("size", "single"), (v) => set("size", v))));
|
||||
if (domain !== "screen") {
|
||||
const taps = [["auto", "Automatisch"], ["detail", "Bediening openen"], ["none", "Alleen bekijken"]];
|
||||
if (["light", "switch", "input_boolean", "fan", "media_player"].includes(domain)) taps.push(["toggle", "Aan / uit"]);
|
||||
body.append(field("Bij aantikken", segmented(taps, current("tap", "auto"), (v) => set("tap", v))));
|
||||
}
|
||||
if (["light", "fan", "cover", "number", "input_number", "media_player"].includes(domain))
|
||||
body.append(field("Kleine slider op de tegel", segmented([["none", "Nee"], ["slider", "Ja, direct bedienen"]], current("inline", "none"), (v) => set("inline", v))));
|
||||
if (domain === "sensor")
|
||||
body.append(field("Geschiedenis", segmented([[1, "1 uur"], [6, "6 uur"], [24, "24 uur"]], current("history_hours", 24), (v) => set("history_hours", Number(v)))));
|
||||
const palette = node("div", undefined, "sheet-field");
|
||||
palette.append(node("span", "Pastel achtergrond"));
|
||||
const swatches = node("div", undefined, "palette-swatches");
|
||||
for (const [key, choice] of Object.entries(inventory.backgrounds || {})) {
|
||||
const button = node("button", undefined, "palette-choice");
|
||||
button.type = "button";
|
||||
button.setAttribute("aria-label", `Achtergrond: ${choice.label}`);
|
||||
button.setAttribute("aria-pressed", String((tile.options?.background || "auto") === key));
|
||||
const sample = node("span", undefined, "palette-sample");
|
||||
if (choice.color) sample.style.backgroundColor = choice.color;
|
||||
else sample.classList.add("palette-auto");
|
||||
button.append(sample, node("span", choice.label));
|
||||
button.onclick = () => {
|
||||
tile.options = { ...tile.options, background: key };
|
||||
for (const other of swatches.children) other.setAttribute("aria-pressed", String(other === button));
|
||||
markDirty();
|
||||
renderPreview();
|
||||
};
|
||||
content.append(input, node("small", tile.entity));
|
||||
const actions = node("div", undefined, "tile-actions");
|
||||
for (const [label, title, action, disabled] of [
|
||||
["↑", "Omhoog", () => move(i, i - 1), i === 0],
|
||||
["↓", "Omlaag", () => move(i, i + 1), i === layout.tiles.length - 1],
|
||||
[
|
||||
"✕",
|
||||
"Verwijderen",
|
||||
() => {
|
||||
layout.tiles.splice(i, 1);
|
||||
markDirty();
|
||||
renderTiles();
|
||||
renderResults();
|
||||
},
|
||||
false,
|
||||
],
|
||||
]) {
|
||||
const b = node("button", label);
|
||||
b.title = title;
|
||||
b.setAttribute("aria-label", title);
|
||||
b.disabled = disabled;
|
||||
b.onclick = action;
|
||||
actions.append(b);
|
||||
}
|
||||
const options = node("details", undefined, "tile-options");
|
||||
options.append(node("summary", "Bediening & weergave instellen"));
|
||||
options.open = selectedTile === tile.entity;
|
||||
options.ontoggle = () => {
|
||||
li.classList.toggle("selected-tile", options.open);
|
||||
if (options.open) {
|
||||
selectedTile = tile.entity;
|
||||
renderPreview();
|
||||
document.querySelectorAll(".tile-options").forEach((other) => {
|
||||
if (other !== options) other.open = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
li.onclick = (e) => {
|
||||
if (!e.target.closest("input, select, button, summary, .tile-options"))
|
||||
options.open = !options.open;
|
||||
};
|
||||
const palette = node("fieldset", undefined, "tile-palette");
|
||||
palette.append(node("legend", "Pastel achtergrond"));
|
||||
const swatches = node("div", undefined, "palette-swatches");
|
||||
for(const [key, choice] of Object.entries(inventory.backgrounds || {})) {
|
||||
const button=node("button", undefined, "palette-choice");
|
||||
button.type="button";
|
||||
button.setAttribute("aria-label", `Achtergrond: ${choice.label}`);
|
||||
button.setAttribute("aria-pressed", String((tile.options?.background || "auto") === key));
|
||||
const sample=node("span", undefined, "palette-sample");
|
||||
if(choice.color)sample.style.backgroundColor=choice.color;
|
||||
else sample.classList.add("palette-auto");
|
||||
button.append(sample,node("span",choice.label));
|
||||
button.onclick=()=>{
|
||||
tile.options={...tile.options,background:key};
|
||||
for(const other of swatches.children)other.setAttribute("aria-pressed",String(other===button));
|
||||
markDirty();renderPreview();
|
||||
};
|
||||
swatches.append(button);
|
||||
}
|
||||
palette.append(swatches,node("small", "Donkere tekst blijft leesbaar. Kies Standaard voor de normale kleuren. Vereist schermfirmware 0.2.10+."));
|
||||
options.append(palette);
|
||||
const controls = {};
|
||||
const domain = tile.entity.split(".")[0];
|
||||
const displays = domain === "screen"
|
||||
? [["digital", "Digitale klok"], ["analog", "Analoge klok"]]
|
||||
: [["standard", "Naam en status"], ["watch", "Grote waarde"]];
|
||||
if (domain === "weather") displays.push(["forecast", "Weersvoorspelling (dubbelbreed)"]);
|
||||
if (domain === "sensor") displays.push(["graph", "Grafiek van de geschiedenis"]);
|
||||
if (domain === "sun") displays.push(["sunpath", "Zonnebaan (dubbelbreed)"]);
|
||||
const fields = [
|
||||
[
|
||||
"tap",
|
||||
"Bij aantikken",
|
||||
[
|
||||
["auto", "Automatisch"],
|
||||
["detail", "Bediening openen"],
|
||||
["none", "Alleen bekijken"],
|
||||
],
|
||||
],
|
||||
["display", "Weergave", displays],
|
||||
["size", "Breedte (firmware 0.2.14+)", [["single", "Normaal"], ["wide", "Dubbelbreed"]]],
|
||||
];
|
||||
if (domain === "screen") fields.shift();
|
||||
if (
|
||||
["light", "switch", "input_boolean", "fan", "media_player"].includes(
|
||||
domain,
|
||||
)
|
||||
)
|
||||
fields[0][2].push(["toggle", "Aan / uit"]);
|
||||
if (
|
||||
[
|
||||
"light",
|
||||
"fan",
|
||||
"cover",
|
||||
"number",
|
||||
"input_number",
|
||||
"media_player",
|
||||
].includes(domain)
|
||||
)
|
||||
fields.push([
|
||||
"inline",
|
||||
"Kleine slider op deze tegel?",
|
||||
[
|
||||
["none", "Nee"],
|
||||
["slider", "Ja, direct bedienen"],
|
||||
],
|
||||
]);
|
||||
if (domain === "sensor")
|
||||
fields.push([
|
||||
"history_hours",
|
||||
"Geschiedenis",
|
||||
[
|
||||
[1, "1 uur"],
|
||||
[6, "6 uur"],
|
||||
[24, "24 uur"],
|
||||
],
|
||||
]);
|
||||
for (const [key, title, choices] of fields) {
|
||||
const label = node("label", title),
|
||||
input = node("select");
|
||||
for (const [value, text] of choices) {
|
||||
const option = node("option", text);
|
||||
option.value = value;
|
||||
input.append(option);
|
||||
}
|
||||
input.value =
|
||||
tile.options?.[key] ??
|
||||
(key === "tap"
|
||||
? "auto"
|
||||
: key === "display"
|
||||
? (domain === "screen" ? "digital" : "standard")
|
||||
: key === "history_hours"
|
||||
? 24
|
||||
: key === "size"
|
||||
? "single"
|
||||
: "none");
|
||||
controls[key] = input;
|
||||
input.onchange = () => {
|
||||
tile.options = {
|
||||
...tile.options,
|
||||
[key]: key === "history_hours" ? Number(input.value) : input.value,
|
||||
};
|
||||
if (key === "display" && input.value === "watch")
|
||||
tile.options.inline = "none";
|
||||
if (key === "display" && ["forecast", "sunpath"].includes(input.value))
|
||||
tile.options.size = "wide";
|
||||
if (key === "inline" && input.value === "slider")
|
||||
tile.options.display = "standard";
|
||||
for (const [field, control] of Object.entries(controls)) {
|
||||
if (tile.options[field] !== undefined)
|
||||
control.value = tile.options[field];
|
||||
}
|
||||
markDirty();
|
||||
renderPreview();
|
||||
};
|
||||
label.append(input);
|
||||
options.append(label);
|
||||
}
|
||||
if (domain !== "screen") {
|
||||
const inspectTile = node("button", "Inspecteer deze tegel", "quiet");
|
||||
inspectTile.onclick = () => inspect(tile.entity);
|
||||
options.append(inspectTile);
|
||||
}
|
||||
content.append(options);
|
||||
li.append(domainBadge(tile.entity), content, actions);
|
||||
$("#tiles").append(li);
|
||||
});
|
||||
swatches.append(button);
|
||||
}
|
||||
palette.append(swatches);
|
||||
body.append(palette);
|
||||
const foot = node("div", undefined, "sheet-foot");
|
||||
const remove = node("button", "Verwijderen", "quiet danger");
|
||||
remove.type = "button";
|
||||
remove.onclick = () => removeTile(sheetIndex);
|
||||
foot.append(remove);
|
||||
if (domain !== "screen") {
|
||||
const inspectButton = node("button", "Inspecteer", "quiet");
|
||||
inspectButton.type = "button";
|
||||
inspectButton.onclick = () => { const entity = tile.entity; closeTileSheet(); inspect(entity); };
|
||||
foot.append(inspectButton);
|
||||
}
|
||||
const done = node("button", "Klaar");
|
||||
done.type = "button";
|
||||
done.onclick = closeTileSheet;
|
||||
foot.append(done);
|
||||
sheet.append(head, body, foot);
|
||||
}
|
||||
function addTile(id, at) {
|
||||
if (layout.tiles.some((t) => t.entity === id) || layout.tiles.length >= tileLimit()) return;
|
||||
@@ -520,7 +480,7 @@ function addTile(id, at) {
|
||||
const drag = { active: false, source: null, element: null, ghost: null, target: null, timer: 0, start: null, offset: null, pointerId: null, suppressUntil: 0, last: null, scroller: 0 };
|
||||
function enableDrag(element, source) {
|
||||
element.addEventListener("pointerdown", (e) => {
|
||||
if (e.button !== 0 || element.disabled) return;
|
||||
if (e.button !== 0 || element.disabled || e.target.closest(".preview-remove")) return;
|
||||
Object.assign(drag, { source, element, start: { x: e.clientX, y: e.clientY }, pointerId: e.pointerId, active: false });
|
||||
clearTimeout(drag.timer);
|
||||
if (e.pointerType === "touch") drag.timer = setTimeout(() => beginDrag(e), 260);
|
||||
|
||||
@@ -112,12 +112,12 @@
|
||||
<span id="count">0 / 10</span>
|
||||
</div>
|
||||
<p class="hint">
|
||||
Klik op een tegel om bediening, weergave en breedte te kiezen.
|
||||
Sleep om te ordenen. Zes vakjes per pagina; een dubbelbrede
|
||||
tegel telt voor twee en begint altijd links.
|
||||
Klik op een tegel voor naam, weergave, breedte en kleur; het
|
||||
kruisje verwijdert. Sleep om te ordenen of sleep entiteiten uit
|
||||
de lijst hierheen. Zes vakjes per pagina; een dubbelbrede tegel
|
||||
telt voor twee en begint altijd links.
|
||||
</p>
|
||||
<div id="layout-preview" aria-label="Schermindeling"></div>
|
||||
<ol id="tiles"></ol>
|
||||
<p id="no-tiles" class="hint">
|
||||
Voeg je eerste lamp, scène of apparaat toe via de kiezer.
|
||||
</p>
|
||||
@@ -351,6 +351,7 @@
|
||||
<p id="firmware-status" role="status"></p>
|
||||
<pre id="firmware-log"></pre>
|
||||
</dialog>
|
||||
<dialog id="tile-sheet" class="tile-sheet" aria-label="Tegel instellen"></dialog>
|
||||
<div id="toast" role="status" hidden></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -544,6 +544,9 @@ dialog label {
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 14px 22px;
|
||||
background: var(--ink);
|
||||
color: white;
|
||||
@@ -842,3 +845,29 @@ pre {
|
||||
.palette-sample { width: 36px; height: 28px; border-radius: 7px; border: 1px solid #0002; }
|
||||
.palette-auto { background: linear-gradient(135deg, #fff 50%, #e5e7eb 50%); }
|
||||
.tile-palette small { color: #52616e; }
|
||||
|
||||
/* Tile settings sheet and in-preview removal. */
|
||||
#toast button { background: transparent; color: #9fc4ff; padding: 0; font-weight: 650; }
|
||||
.preview-tile { position: relative; }
|
||||
.preview-remove { position: absolute; top: 6px; right: 6px; width: 24px; height: 24px; padding: 0; border-radius: 50%; background: #ffffffd9; color: var(--muted); border: 1px solid var(--line); font-size: 11px; line-height: 1; display: grid; place-items: center; opacity: 0; transition: opacity 0.15s; }
|
||||
.preview-tile:hover .preview-remove, .preview-tile:focus-within .preview-remove { opacity: 1; }
|
||||
.preview-remove:hover { color: #b3261e; border-color: #e7b4b0; background: white; }
|
||||
@media (hover: none) { .preview-remove { opacity: 1; } }
|
||||
.tile-sheet { padding: 0; width: min(560px, calc(100% - 28px)); overflow: hidden; }
|
||||
.sheet-head { display: flex; align-items: center; gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--line); }
|
||||
.sheet-head > div { flex: 1; min-width: 0; }
|
||||
.sheet-head strong { display: block; font-size: 16px; }
|
||||
.sheet-head small { color: var(--muted); overflow-wrap: anywhere; }
|
||||
.sheet-close { font-size: 15px; }
|
||||
.sheet-body { padding: 16px 20px; display: grid; gap: 14px; max-height: 58vh; overflow: auto; }
|
||||
.sheet-field { display: grid; gap: 7px; font-size: 12px; font-weight: 650; color: var(--muted); }
|
||||
.sheet-field input { font-weight: 400; font-size: 14px; }
|
||||
.segmented { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.segmented button { background: #f3f5f8; color: var(--ink); border: 1px solid transparent; border-radius: 10px; padding: 8px 12px; font-size: 13px; font-weight: 500; }
|
||||
.segmented button[aria-pressed="true"] { background: var(--soft); color: var(--accent); border-color: #c9dafa; font-weight: 650; }
|
||||
.sheet-foot { display: flex; gap: 10px; align-items: center; padding: 14px 20px; border-top: 1px solid var(--line); background: #fafbfc; }
|
||||
.sheet-foot .danger { color: #b3261e; margin-right: auto; }
|
||||
@media (max-width: 620px) {
|
||||
.tile-sheet { width: 100%; max-width: 100%; margin: auto 0 0; border-radius: 20px 20px 0 0; max-height: 90vh; }
|
||||
.sheet-body { max-height: 52vh; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user