fix(ui): mobile pop-out for the advice, and tables that fit a phone
The brewing advice is several hundred characters. In a table cell it pushed the table past the width of a phone, so the column was clipped off the right edge and the advice could not be read without sideways scrolling. The advice is now a compact trigger in the cell and the text is in a floating panel that opens on hover (desktop) or on tap (phone). The panel is position:fixed and anchored by the script. Absolutely positioned inside the cell it was clipped by the table's own scroll container, and the advice was sliced in half by the card's edge. It flips above the trigger when there is no room below. The tables now reflow into stacked cards on a narrow screen, with each value labelled by its column name. A seven column table does not fit a phone, and it used to scroll sideways inside its wrapper, so the columns on the right were off the screen. Also: the header wraps onto two rows so the nav tabs are not clipped, the point scale says 'points' rather than 'pts', and the reading count on the beer card does not orphan its word.
This commit is contained in:
@@ -56,11 +56,187 @@
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The brewing advice pop-out.
|
||||
*
|
||||
* On a desktop the panel opens on hover, which the CSS does. A
|
||||
* phone has no hover, so the trigger is a button that toggles it -
|
||||
* and it is a real button, so it works with a keyboard and with a
|
||||
* screen reader.
|
||||
*/
|
||||
/**
|
||||
* The brewing advice pop-out.
|
||||
*
|
||||
* The advice runs to several hundred characters. In a table cell it
|
||||
* pushed the table past the width of a phone, so it is a compact
|
||||
* trigger instead and the text is shown in a floating panel.
|
||||
*
|
||||
* The panel is position:fixed so it escapes the table's scroll
|
||||
* container - absolutely positioned inside the cell it was clipped
|
||||
* by the container and sliced in half by the card's edge.
|
||||
*
|
||||
* It opens on hover on a desktop, and on tap on a phone, which has
|
||||
* no hover. The trigger is a real button, so it also works with a
|
||||
* keyboard.
|
||||
*/
|
||||
function wireAdvicePopouts() {
|
||||
var triggers = document.querySelectorAll(".advice-trigger");
|
||||
for (var i = 0; i < triggers.length; i++) {
|
||||
(function (trigger) {
|
||||
var panel = trigger.parentNode.querySelector(".advice-pop");
|
||||
if (!panel) { return; }
|
||||
|
||||
var closeTimer = null;
|
||||
|
||||
function isPhone() {
|
||||
return window.matchMedia("(max-width: 720px)").matches;
|
||||
}
|
||||
|
||||
function place() {
|
||||
if (isPhone()) {
|
||||
/* The CSS centres it on a phone - clear any
|
||||
inline position left over from a desktop
|
||||
resize. */
|
||||
panel.style.top = "";
|
||||
panel.style.left = "";
|
||||
return;
|
||||
}
|
||||
|
||||
var box = trigger.getBoundingClientRect();
|
||||
var gap = 10;
|
||||
var height = panel.offsetHeight;
|
||||
var width = panel.offsetWidth;
|
||||
var spaceBelow = window.innerHeight - box.bottom - gap;
|
||||
var spaceAbove = box.top - gap;
|
||||
|
||||
/* Prefer below; flip above when there is not enough
|
||||
room and there is more room above. */
|
||||
var top;
|
||||
if (height <= spaceBelow || spaceBelow >= spaceAbove) {
|
||||
top = box.bottom + gap;
|
||||
} else {
|
||||
top = Math.max(gap, box.top - height - gap);
|
||||
}
|
||||
|
||||
/* Keep it on screen horizontally. */
|
||||
var left = Math.min(box.left, window.innerWidth - width - gap);
|
||||
left = Math.max(gap, left);
|
||||
|
||||
panel.style.top = Math.round(top) + "px";
|
||||
panel.style.left = Math.round(left) + "px";
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = null;
|
||||
}
|
||||
panel.classList.add("is-open");
|
||||
trigger.setAttribute("aria-expanded", "true");
|
||||
place();
|
||||
}
|
||||
|
||||
function close() {
|
||||
panel.classList.remove("is-open");
|
||||
trigger.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
function closeSoon() {
|
||||
/* A short delay, so the mouse can travel from the
|
||||
trigger into the panel without it closing. */
|
||||
if (closeTimer) { clearTimeout(closeTimer); }
|
||||
closeTimer = setTimeout(function () {
|
||||
closeTimer = null;
|
||||
if (!panel.matches(":hover") && !trigger.matches(":hover")) {
|
||||
close();
|
||||
}
|
||||
}, 180);
|
||||
}
|
||||
|
||||
trigger.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (panel.classList.contains("is-open")) {
|
||||
close();
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
});
|
||||
|
||||
trigger.addEventListener("mouseenter", function () {
|
||||
if (!isPhone()) { open(); }
|
||||
});
|
||||
trigger.addEventListener("mouseleave", closeSoon);
|
||||
panel.addEventListener("mouseenter", function () {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer);
|
||||
closeTimer = null;
|
||||
}
|
||||
});
|
||||
panel.addEventListener("mouseleave", closeSoon);
|
||||
|
||||
var closeButton = panel.querySelector(".advice-pop__close");
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
close();
|
||||
trigger.focus();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === "Escape" && panel.classList.contains("is-open")) {
|
||||
close();
|
||||
trigger.focus();
|
||||
}
|
||||
});
|
||||
document.addEventListener("click", function (event) {
|
||||
if (!panel.contains(event.target) && event.target !== trigger) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("resize", function () {
|
||||
if (panel.classList.contains("is-open")) { place(); }
|
||||
});
|
||||
window.addEventListener("scroll", function () {
|
||||
if (panel.classList.contains("is-open")) { close(); }
|
||||
}, true);
|
||||
})(triggers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function labelTableCells() {
|
||||
var tables = document.querySelectorAll("table.data");
|
||||
for (var t = 0; t < tables.length; t++) {
|
||||
var headerRow = tables[t].querySelector("thead tr");
|
||||
if (!headerRow) { continue; }
|
||||
var labels = [];
|
||||
var headers = headerRow.querySelectorAll("th");
|
||||
for (var h = 0; h < headers.length; h++) {
|
||||
labels.push(headers[h].textContent.trim());
|
||||
}
|
||||
|
||||
var rows = tables[t].querySelectorAll("tbody tr");
|
||||
for (var r = 0; r < rows.length; r++) {
|
||||
var cells = rows[r].querySelectorAll("td");
|
||||
for (var c = 0; c < cells.length && c < labels.length; c++) {
|
||||
if (labels[c] && !cells[c].getAttribute("data-label")) {
|
||||
cells[c].setAttribute("data-label", labels[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
focusFirstEmpty();
|
||||
defaultDateFields();
|
||||
wireToggles();
|
||||
wireConfirms();
|
||||
wireAdvicePopouts();
|
||||
labelTableCells();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
|
||||
Reference in New Issue
Block a user