UPG Neighbor: on a phone the list can be lowered to a bar and raised again
rega at 390 px: the Mobilize list, a bottom sheet over 54% of the map, had no way to put it down. A handle at the top of the sheet (both views, one shared choice) lowers it to a 56 px bar and raises it again, and the map frames the selection above whichever height it has.
This commit is contained in:
parent
d9e9aa448c
commit
a00c9af009
3 changed files with 28 additions and 8 deletions
|
|
@ -8,7 +8,7 @@
|
|||
* by the unreached population a campaign could reach; selecting one shows where to run it, whom it
|
||||
* reaches, and what content already exists in the language. Data: nearData.js.
|
||||
*/
|
||||
import { inject, ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { inject, ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { setProjectionOn } from '@map/composables/useMapProvider.js'
|
||||
|
|
@ -125,7 +125,7 @@ function frame() {
|
|||
// The panel covers the left 440px on wide screens, the bottom 55% on a phone: frame what you can see.
|
||||
// In a right-to-left locale the panel sits on the right, so the wide padding moves with it.
|
||||
const rtl = !!mapContainer.value && getComputedStyle(mapContainer.value).direction === 'rtl'
|
||||
const padding = w > 760 ? { top: 60, bottom: 60, left: rtl ? 60 : 440, right: rtl ? 440 : 60 } : { top: 40, bottom: Math.round(h * 0.55) + 20, left: 30, right: 30 }
|
||||
const padding = w > 760 ? { top: 60, bottom: 60, left: rtl ? 60 : 440, right: rtl ? 440 : 60 } : { top: 40, bottom: (nbState.sheetDown ? 56 : Math.round(h * 0.55)) + 20, left: 30, right: 30 }
|
||||
try {
|
||||
m.stop()
|
||||
m.fitBounds([[Math.min(...lngs), Math.min(...lats)], [Math.max(...lngs), Math.max(...lats)]], { padding, maxZoom: 5.5, duration: 900 })
|
||||
|
|
@ -133,6 +133,8 @@ function frame() {
|
|||
}
|
||||
watch(() => [nbState.lang, data.value], () => { repaint(); frame() })
|
||||
watch(list, () => repaint()) // the bubbles follow the search and the believers filter
|
||||
// PHONE: the same handle as Mobilize; the list can be lowered to a bar and raised again.
|
||||
function toggleSheet() { nbState.sheetDown = !nbState.sheetDown; nextTick(() => frame()) }
|
||||
|
||||
onMounted(async () => {
|
||||
loadNearData(profileConfig).then((d) => { data.value = d; repaint(); frame() })
|
||||
|
|
@ -217,7 +219,12 @@ useShadowStyles(`
|
|||
.cp-note { padding:14px; color:#6b7280; font-size:13px; line-height:1.45; }
|
||||
.cp-hover { position:absolute; z-index:25; pointer-events:none; background:rgba(0,27,68,.92); color:#fff; font-size:12px; line-height:1.3; padding:5px 8px; border-radius:6px; max-width:260px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.cp-maperr { position:absolute; inset-inline-end:12px; top:12px; z-index:15; background:#fff; border:1px solid #d9dee6; border-radius:8px; padding:8px 12px; font-size:13px; color:#6b7280; }
|
||||
@media (max-width:760px) { .cp-panel { top:auto; inset-inline:6px; bottom:6px; width:auto; height:55%; } }
|
||||
.cp-handle { display:none; align-items:center; justify-content:center; gap:8px; width:100%; min-height:40px; border:0; border-bottom:1px solid #eef0f4;
|
||||
background:#fff; color:${NAVY}; font:600 12px/1 inherit; cursor:pointer; flex:0 0 auto; }
|
||||
.cp-handle::before { content:''; width:36px; height:4px; border-radius:2px; background:#c9d1dd; }
|
||||
.cp-handle:focus-visible { outline:2px solid ${NAVY}; outline-offset:-2px; }
|
||||
@media (max-width:760px) { .cp-panel { top:auto; inset-inline:6px; bottom:6px; width:auto; height:55%; }
|
||||
.cp-panel.down { height:56px; } .cp-panel.down > :not(.cp-handle) { display:none; } .cp-handle { display:flex; } }
|
||||
`, 'upgn-campaigns')
|
||||
</script>
|
||||
|
||||
|
|
@ -227,7 +234,8 @@ useShadowStyles(`
|
|||
<div v-if="mapError" class="cp-maperr" role="status">{{ mapError }}</div>
|
||||
<div v-if="hover" class="cp-hover" :style="{ left: hover.x + 12 + 'px', top: hover.y + 12 + 'px' }">{{ hover.text }}</div>
|
||||
|
||||
<aside class="cp-panel">
|
||||
<aside class="cp-panel" :class="{ down: nbState.sheetDown }">
|
||||
<button type="button" class="cp-handle" :aria-expanded="nbState.sheetDown ? 'false' : 'true'" @click="toggleSheet">{{ nbState.sheetDown ? $t('neighbor.mob.sheetShow') : $t('neighbor.mob.sheetHide') }}</button>
|
||||
<template v-if="!active">
|
||||
<div class="cp-head">
|
||||
<p>{{ $t('neighbor.camp.intro') }}</p>
|
||||
|
|
|
|||
|
|
@ -339,12 +339,16 @@ function frame() {
|
|||
}
|
||||
// The panel covers part of the map: the left 400px on wide screens, the bottom 55% on a phone.
|
||||
// Frame the selection in the part you can see.
|
||||
// In a right-to-left locale the panel sits on the right, so the wide padding moves with it.
|
||||
// In a right-to-left locale the panel sits on the right, so the wide padding moves with it. On a phone
|
||||
// the list is a bottom sheet (55% of the height, or a 56 px bar when the reader has put it down).
|
||||
function framePadding() {
|
||||
const el = mapContainer.value, w = el?.clientWidth || 0, h = el?.clientHeight || 0
|
||||
const rtl = !!el && getComputedStyle(el).direction === 'rtl'
|
||||
return w > 760 ? { top: 60, bottom: 60, left: rtl ? 60 : 400, right: rtl ? 400 : 60 } : { top: 40, bottom: Math.round(h * 0.55) + 20, left: 30, right: 30 }
|
||||
return w > 760 ? { top: 60, bottom: 60, left: rtl ? 60 : 400, right: rtl ? 400 : 60 } : { top: 40, bottom: (nbState.sheetDown ? 56 : Math.round(h * 0.55)) + 20, left: 30, right: 30 }
|
||||
}
|
||||
// PHONE: the list covered 54% of the map with no way to put it down (rega, 390). A handle on the sheet
|
||||
// lowers it to a bar and raises it again; the choice is shared with Campaigns.
|
||||
function toggleSheet() { nbState.sheetDown = !nbState.sheetDown; nextTick(() => frame()) }
|
||||
|
||||
watch(() => [nbState.pgid, pFilter.value, data.value, eLevel.value], () => { repaint(); frame() })
|
||||
|
||||
|
|
@ -446,10 +450,17 @@ useShadowStyles(`
|
|||
.mb-note { padding:14px; color:#6b7280; font-size:13px; line-height:1.45; }
|
||||
.mb-maperr { position:absolute; inset-inline-end:12px; top:12px; z-index:15; background:#fff; border:1px solid #d9dee6; border-radius:8px; padding:8px 12px; font-size:13px; color:#6b7280; }
|
||||
.mb-hover { position:absolute; z-index:25; pointer-events:none; background:rgba(0,27,68,.92); color:#fff; font-size:12px; line-height:1.3; padding:5px 8px; border-radius:6px; max-width:260px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||||
.mb-handle { display:none; align-items:center; justify-content:center; gap:8px; width:100%; min-height:40px; border:0; border-bottom:1px solid #eef0f4;
|
||||
background:#fff; color:${NAVY}; font:600 12px/1 inherit; cursor:pointer; flex:0 0 auto; }
|
||||
.mb-handle::before { content:''; width:36px; height:4px; border-radius:2px; background:#c9d1dd; }
|
||||
.mb-handle:focus-visible { outline:2px solid ${NAVY}; outline-offset:-2px; }
|
||||
.mb-legend { position:absolute; inset-inline-end:12px; bottom:28px; z-index:15; background:rgba(255,255,255,.95); border:1px solid #d9dee6; border-radius:10px; padding:8px 10px; font-size:12px; }
|
||||
.mb-legend div { display:flex; align-items:center; gap:7px; padding:2px 0; }
|
||||
@media (max-width:760px) {
|
||||
.mb-panel { top:auto; inset-inline:6px; bottom:6px; width:auto; height:55%; }
|
||||
.mb-panel.down { height:56px; }
|
||||
.mb-panel.down > :not(.mb-handle) { display:none; }
|
||||
.mb-handle { display:flex; }
|
||||
.mb-legend { top:8px; bottom:auto; inset-inline-end:8px; }
|
||||
}
|
||||
`, 'upgn-mobilize')
|
||||
|
|
@ -467,7 +478,8 @@ useShadowStyles(`
|
|||
<div v-for="e in [1, 2, 3]" :key="e"><span class="mb-dot" :style="{ background: E_COLORS[e] }"></span>{{ $t(E_SHORT[e]) }}</div>
|
||||
</div>
|
||||
|
||||
<aside class="mb-panel">
|
||||
<aside class="mb-panel" :class="{ down: nbState.sheetDown }">
|
||||
<button type="button" class="mb-handle" :aria-expanded="nbState.sheetDown ? 'false' : 'true'" @click="toggleSheet">{{ nbState.sheetDown ? $t('neighbor.mob.sheetShow') : $t('neighbor.mob.sheetHide') }}</button>
|
||||
<template v-if="!selected">
|
||||
<div class="mb-head">
|
||||
<div class="mb-kind" role="group" :aria-label="$t('neighbor.mob.kind')">
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const asked = cfg.value.view || cfg.value.legacyElement
|
|||
const initial = VIEWS.includes(asked) ? asked : (LEGACY[asked] || (cfg.value.lang ? 'campaigns' : 'mobilize'))
|
||||
|
||||
// Shared by both views, so a choice made in one is still there in the other.
|
||||
const nbState = reactive({ view: initial, pgid: cfg.value.pgid || null, lang: cfg.value.lang || null, about: false, list: 'unreached' })
|
||||
const nbState = reactive({ view: initial, pgid: cfg.value.pgid || null, lang: cfg.value.lang || null, about: false, list: 'unreached', sheetDown: false })
|
||||
provide('nbState', nbState)
|
||||
|
||||
// "How it works" is a real dialog for the keyboard: focus moves to its close button when it opens, Tab
|
||||
|
|
|
|||
Loading…
Reference in a new issue