Add shared UI shell and list component
Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function compareRows(left, right, key, direction) {
|
||||
const leftValue = left.dataset[key] || "";
|
||||
const rightValue = right.dataset[key] || "";
|
||||
const numericLeft = Number(leftValue);
|
||||
const numericRight = Number(rightValue);
|
||||
const bothNumeric = leftValue !== "" && rightValue !== ""
|
||||
&& Number.isFinite(numericLeft) && Number.isFinite(numericRight);
|
||||
const comparison = bothNumeric
|
||||
? numericLeft - numericRight
|
||||
: leftValue.localeCompare(rightValue, undefined, {
|
||||
numeric: true,
|
||||
sensitivity: "base",
|
||||
});
|
||||
return comparison * direction;
|
||||
}
|
||||
|
||||
function refreshList(list) {
|
||||
const body = list.querySelector("[data-data-list-body]");
|
||||
const table = list.querySelector("[data-data-list-table]");
|
||||
const empty = list.querySelector("[data-data-list-empty]");
|
||||
const hasRows = body && body.children.length > 0;
|
||||
table.hidden = !hasRows;
|
||||
empty.hidden = hasRows;
|
||||
}
|
||||
|
||||
function initializeList(list) {
|
||||
refreshList(list);
|
||||
|
||||
list.addEventListener("click", function (event) {
|
||||
const sortButton = event.target.closest("[data-sort-key]");
|
||||
if (sortButton) {
|
||||
const body = list.querySelector("[data-data-list-body]");
|
||||
const key = sortButton.dataset.sortKey;
|
||||
const ascending = sortButton.dataset.sortDirection !== "asc";
|
||||
const rows = Array.from(body.children);
|
||||
rows.sort(function (left, right) {
|
||||
return compareRows(left, right, key, ascending ? 1 : -1);
|
||||
});
|
||||
rows.forEach(function (row) {
|
||||
body.appendChild(row);
|
||||
});
|
||||
list.querySelectorAll("[data-sort-key]").forEach(function (button) {
|
||||
button.removeAttribute("data-sort-direction");
|
||||
button.closest("th").setAttribute("aria-sort", "none");
|
||||
});
|
||||
sortButton.dataset.sortDirection = ascending ? "asc" : "desc";
|
||||
sortButton.closest("th").setAttribute(
|
||||
"aria-sort",
|
||||
ascending ? "ascending" : "descending"
|
||||
);
|
||||
}
|
||||
|
||||
const confirmButton = event.target.closest("[data-confirm]");
|
||||
if (confirmButton && !window.confirm(confirmButton.dataset.confirm)) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}, true);
|
||||
|
||||
new MutationObserver(function () {
|
||||
refreshList(list);
|
||||
}).observe(list.querySelector("[data-data-list-body]"), { childList: true });
|
||||
}
|
||||
|
||||
document.querySelectorAll("[data-data-list]").forEach(initializeList);
|
||||
}());
|
||||
+40
-1
@@ -1 +1,40 @@
|
||||
#map { height: 20rem; }
|
||||
#map { height: 20rem; }
|
||||
|
||||
.app-sidebar {
|
||||
color: var(--bs-accordion-btn-focus-border-color);
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.data-list {
|
||||
min-height: 10rem;
|
||||
}
|
||||
|
||||
.data-list-empty {
|
||||
padding: 2.5rem 1rem;
|
||||
}
|
||||
|
||||
.data-list-sort {
|
||||
display: inline-flex;
|
||||
gap: .5rem;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-weight: 700;
|
||||
background: none;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.data-list-sort[data-sort-direction="asc"] .fa-sort::before {
|
||||
content: "\f0de";
|
||||
}
|
||||
|
||||
.data-list-sort[data-sort-direction="desc"] .fa-sort::before {
|
||||
content: "\f0dd";
|
||||
}
|
||||
|
||||
.data-list-actions {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
Reference in New Issue
Block a user