Skip to content

Commit 55c3dcf

Browse files
committed
Add final additions to mobile-friendly UI
1 parent afb5bff commit 55c3dcf

2 files changed

Lines changed: 98 additions & 8 deletions

File tree

src/js/ui.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,8 @@ export function toggleDataTable() {
518518

519519
if (panel.style.display === 'none' || panel.style.display === '') {
520520
panel.style.display = 'flex';
521+
// On mobile the FAB must be raised above the table panel
522+
document.body.classList.add('data-table-open');
521523
showTableLoading();
522524

523525
// Use setTimeout to allow loading indicator to display
@@ -537,6 +539,7 @@ export function toggleDataTable() {
537539
}, 50);
538540
} else {
539541
panel.style.display = 'none';
542+
document.body.classList.remove('data-table-open');
540543
}
541544
}
542545

@@ -1752,6 +1755,28 @@ export function initUI() {
17521755
columnPicker.style.display = 'none';
17531756
}
17541757
});
1758+
1759+
// Swipe-down to dismiss the mobile bottom sheet.
1760+
// Only triggers when the panel is open, the swipe starts while the panel's
1761+
// own scroll position is at the top (so normal upward scrolling is not
1762+
// blocked), and the downward distance exceeds 80px.
1763+
const controlsPanel = document.getElementById('controlsPanel');
1764+
if (controlsPanel) {
1765+
let swipeStartY = 0;
1766+
let swipeStartScrollTop = 0;
1767+
1768+
controlsPanel.addEventListener('touchstart', function(e) {
1769+
swipeStartY = e.touches[0].clientY;
1770+
swipeStartScrollTop = controlsPanel.scrollTop;
1771+
}, { passive: true });
1772+
1773+
controlsPanel.addEventListener('touchend', function(e) {
1774+
const deltaY = e.changedTouches[0].clientY - swipeStartY;
1775+
if (deltaY > 80 && swipeStartScrollTop === 0 && controlsPanel.classList.contains('visible')) {
1776+
togglePanel();
1777+
}
1778+
}, { passive: true });
1779+
}
17551780
}
17561781

17571782
// Export constants for use in HTML onclick handlers

styles.css

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@
6767
box-sizing: border-box;
6868
}
6969

70+
/* Eliminate the ~300ms tap delay on interactive controls and prevent
71+
double-tap zoom on buttons. Does NOT affect the map (div, not button). */
72+
button,
73+
[role="button"],
74+
input,
75+
select,
76+
label,
77+
a {
78+
touch-action: manipulation;
79+
}
80+
7081
body {
7182
font-family: -apple-system, BlinkMacSystemFont, "Inter", "SF Pro Display",
7283
"Segoe UI", "Roboto", system-ui, sans-serif;
@@ -716,6 +727,10 @@
716727
background: var(--border);
717728
}
718729

730+
.location-search-header:active {
731+
background: var(--border);
732+
}
733+
719734
.location-search-header .header-text {
720735
font-size: 11px;
721736
font-weight: 600;
@@ -4244,13 +4259,23 @@
42444259
}
42454260

42464261
.dt-pagination-label {
4247-
font-size: 11px;
4262+
font-size: 12px;
42484263
}
42494264

42504265
.column-picker {
42514266
right: 5px;
42524267
width: 180px;
42534268
}
4269+
4270+
/* The × close button inside the column-picker header has padding:0 by
4271+
default, making it a tiny tap target. Expand to 44×44px on touch. */
4272+
.column-picker-header button {
4273+
min-width: 44px;
4274+
min-height: 44px;
4275+
display: flex;
4276+
align-items: center;
4277+
justify-content: center;
4278+
}
42544279
}
42554280

42564281
/* ── Draw Area Filter ─────────────────────────────────────────────────────── */
@@ -5083,6 +5108,14 @@ body:has(.map-positioning-active) .analytics-panel {
50835108
opacity: 0;
50845109
pointer-events: none;
50855110
}
5111+
5112+
/* When the data table panel is open the FAB sits behind it at bottom:24px.
5113+
Raise it above the panel so it remains reachable. The panel height on
5114+
mobile is clamp(180px,45vh,320px); add 16px breathing room on top. */
5115+
body.data-table-open .toggle-panel-btn {
5116+
bottom: calc(clamp(180px, 45vh, 320px) + 16px);
5117+
transition: bottom 0.2s ease, transform 0.2s, opacity 0.2s;
5118+
}
50865119
}
50875120

50885121
/* ============================================================
@@ -5151,11 +5184,15 @@ body:has(.map-positioning-active) .analytics-panel {
51515184

51525185
/* ── Checkbox dropdown items ─────────────────────────────────
51535186
4px 6px padding gives ~29px row height — too small for touch.
5154-
Increase to 8px vertical giving ~40px (within 4px of target). */
5187+
Increase to meet the 44px minimum touch target. */
51555188
@media (max-width: 768px) {
51565189
.checkbox-dropdown-item {
51575190
padding: 8px 8px;
5158-
min-height: 40px;
5191+
min-height: 44px;
5192+
}
5193+
5194+
.checkbox-dropdown-item:active {
5195+
background: var(--accent-bg);
51595196
}
51605197

51615198
/* Larger checkbox to match the bigger row */
@@ -5174,11 +5211,12 @@ body:has(.map-positioning-active) .analytics-panel {
51745211
visibility: visible;
51755212
}
51765213

5177-
/* Slightly larger hit area on touch screens */
5214+
/* Expand to full 44×44 tap target on touch screens. The icon glyph stays
5215+
small and centred; the element itself provides the tappable area. */
51785216
@media (max-width: 768px) {
51795217
.layer-info-icon {
5180-
width: 24px;
5181-
height: 24px;
5218+
width: 44px;
5219+
height: 44px;
51825220
font-size: 14px;
51835221
}
51845222
}
@@ -5317,7 +5355,7 @@ body:has(.map-positioning-active) .analytics-panel {
53175355
.choropleth-mode-btn {
53185356
font-size: 12px;
53195357
padding: 6px 10px;
5320-
min-height: 36px;
5358+
min-height: 44px;
53215359
}
53225360
}
53235361

@@ -5345,7 +5383,34 @@ body:has(.map-positioning-active) .analytics-panel {
53455383
.multi-select-count,
53465384
.select-all-btn,
53475385
.time-range-label,
5348-
.help-text-small {
5386+
.help-text-small,
5387+
.location-search-header .header-text {
53495388
font-size: 12px;
53505389
}
53515390
}
5391+
5392+
/* ── 8. :focus-visible — restore keyboard/switch-access focus rings ──────────
5393+
Nearly every element has outline:none on :focus to remove the browser's
5394+
default ring for mouse users. :focus-visible fires only when the browser
5395+
determines a focus indicator is needed (keyboard, switch, sequential nav),
5396+
so reinstating an outline here doesn't affect mouse/touch workflows but
5397+
gives keyboard and assistive-technology users a clear indicator.
5398+
!important is required because the per-element outline:none rules have
5399+
higher specificity than this bare selector. */
5400+
:focus-visible {
5401+
outline: 2px solid var(--accent) !important;
5402+
outline-offset: 2px !important;
5403+
}
5404+
5405+
/* ── 9. prefers-reduced-motion ───────────────────────────────────────────────
5406+
The app has panel slides, marker animations, notification slide-ins and
5407+
many CSS transitions. Honour the OS-level preference so users who are
5408+
sensitive to motion (vestibular disorders, etc.) get an instant UI. */
5409+
@media (prefers-reduced-motion: reduce) {
5410+
*, *::before, *::after {
5411+
animation-duration: 0.01ms !important;
5412+
animation-iteration-count: 1 !important;
5413+
transition-duration: 0.01ms !important;
5414+
scroll-behavior: auto !important;
5415+
}
5416+
}

0 commit comments

Comments
 (0)