mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-07-09 15:05:59 +00:00
fix(collections): mobile polish — touch targets, safe-areas, overflow (#1081)
From a mobile UX audit of the collections page: - Detail sheet: the read-mode footer no longer clips "Remove from list" (it wraps, drops the growing spacer) and clears the home indicator (safe-area padding, 84dvh instead of 84vh). - Bigger touch targets on phones (≥40px): view toggle, filter dropdowns, select-bar buttons, detail close/actions, drawer rail rows, and the interactive status badge (enlarged tap area via a pseudo-element, look unchanged). - Select action bar breaks its bulk actions onto their own line instead of stranding them behind a growing spacer. - Lists drawer honours device safe-areas and gets an explicit close button. - Page honours the top safe-area and goes full-width on phones (drop the 95vw cap); filter popovers cap their width so long category names don't overflow. - Add-place: Cancel/Add pinned in the modal footer (reachable without scrolling), status pills wrap. - Drop dead hero mobile CSS left over from the hero refactor.
This commit is contained in:
@@ -106,7 +106,20 @@ export default function AddPlaceToCollectionModal({ isOpen, collectionId, collec
|
||||
const address = picked ? str(picked.address) : undefined
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={t('collections.addPlace')} size="md">
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
title={t('collections.addPlace')}
|
||||
size="md"
|
||||
footer={
|
||||
<div className="flex justify-end gap-2">
|
||||
<button type="button" onClick={onClose} className="px-3 py-1.5 rounded-lg border border-edge text-content-secondary text-[13px] hover:bg-surface-hover">{t('common.cancel')}</button>
|
||||
<button type="button" onClick={save} disabled={saving || !name.trim()} className="px-3 py-1.5 rounded-lg bg-accent text-accent-text text-[13px] font-semibold disabled:opacity-50 inline-flex items-center gap-1.5">
|
||||
{saving ? <Loader2 size={14} className="animate-spin" /> : <Check size={14} />} {t('common.add')}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Search — picking a result fills the location below */}
|
||||
<div className="relative">
|
||||
@@ -155,7 +168,7 @@ export default function AddPlaceToCollectionModal({ isOpen, collectionId, collec
|
||||
|
||||
{/* Status */}
|
||||
<div>
|
||||
<div className="flex gap-1.5">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{STATUS_ORDER.map(s => {
|
||||
const Icon = STATUS_META[s].icon
|
||||
const on = status === s
|
||||
@@ -222,13 +235,6 @@ export default function AddPlaceToCollectionModal({ isOpen, collectionId, collec
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-1">
|
||||
<button type="button" onClick={onClose} className="px-3 py-1.5 rounded-lg border border-edge text-content-secondary text-[13px] hover:bg-surface-hover">{t('common.cancel')}</button>
|
||||
<button type="button" onClick={save} disabled={saving || !name.trim()} className="px-3 py-1.5 rounded-lg bg-accent text-accent-text text-[13px] font-semibold disabled:opacity-50 inline-flex items-center gap-1.5">
|
||||
{saving ? <Loader2 size={14} className="animate-spin" /> : <Check size={14} />} {t('common.add')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -262,7 +262,12 @@ export default function CollectionsPage(): React.ReactElement {
|
||||
{c.mobileRailOpen && (
|
||||
<>
|
||||
<div className="col-drawer-backdrop" onClick={() => c.setMobileRailOpen(false)} />
|
||||
<div className="col-drawer">{rail}</div>
|
||||
<div className="col-drawer">
|
||||
<div className="col-drawer-head">
|
||||
<button type="button" onClick={() => c.setMobileRailOpen(false)} aria-label={t('common.close')}><X size={18} /></button>
|
||||
</div>
|
||||
{rail}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@
|
||||
.trek-dash .col-filter-btn .col-filter-chev { color: var(--ink-3); transition: transform .15s; }
|
||||
.trek-dash .col-filter-btn.open .col-filter-chev { transform: rotate(180deg); }
|
||||
.trek-dash .col-filter-lbl { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
|
||||
.trek-dash .col-filter-pop { position: absolute; top: calc(100% + 6px); left: 0; z-index: 40; min-width: 190px; max-height: 320px; overflow-y: auto; padding: 6px; border-radius: 12px; background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-lg); display: flex; flex-direction: column; gap: 2px; }
|
||||
.trek-dash .col-filter-pop { position: absolute; top: calc(100% + 6px); left: 0; z-index: 40; min-width: 190px; max-width: min(280px, calc(100vw - 28px)); max-height: 320px; overflow-y: auto; padding: 6px; border-radius: 12px; background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh-lg); display: flex; flex-direction: column; gap: 2px; }
|
||||
.trek-dash .col-filter-opt { display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 9px; border-radius: 8px; font-size: 13px; color: var(--ink-2); text-align: left; transition: background .12s; }
|
||||
.trek-dash .col-filter-opt:hover { background: var(--surface-2); }
|
||||
.trek-dash .col-filter-opt.on { color: var(--ink); font-weight: 600; }
|
||||
@@ -404,7 +404,11 @@
|
||||
.trek-dash .col-detail-btn.danger:hover { background: oklch(0.6 0.2 25 / .12); }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.trek-dash .col-detail { left: 0; right: 0; bottom: 0; transform: none; width: 100%; max-height: 84vh; border-radius: var(--r-xl) var(--r-xl) 0 0; }
|
||||
.trek-dash .col-detail { left: 0; right: 0; bottom: 0; transform: none; width: 100%; max-height: 84dvh; border-radius: var(--r-xl) var(--r-xl) 0 0; }
|
||||
/* the read-mode footer (Edit · Copy · Remove) would otherwise clip its last
|
||||
button — let it wrap, drop the growing spacer, and clear the home indicator */
|
||||
.trek-dash .col-detail-footer { flex-wrap: wrap; padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); }
|
||||
.trek-dash .col-detail-footer-spacer { display: none; }
|
||||
}
|
||||
|
||||
/* ----------------- empty / loading ----------------- */
|
||||
@@ -430,21 +434,35 @@
|
||||
.trek-dash .col-rail-toggle:hover { background: var(--surface-2); color: var(--ink); }
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.trek-dash.col-root { padding-top: 0; }
|
||||
.trek-dash .col-page { padding: 14px 14px 130px; }
|
||||
.trek-dash.col-root { padding-top: env(safe-area-inset-top, 0px); }
|
||||
.trek-dash .col-page { max-width: none; padding: 14px 14px 130px; }
|
||||
.trek-dash .col-hero { min-height: 128px; border-radius: var(--r-xl); margin-bottom: 16px; }
|
||||
.trek-dash .col-hero-content { padding: 14px 16px 15px; gap: 8px; }
|
||||
.trek-dash .col-hero-title { font-size: 28px; }
|
||||
.trek-dash .col-hero-actions { top: 14px; right: 14px; }
|
||||
.trek-dash .col-glass-btn span.txt { display: none; }
|
||||
.trek-dash .col-glass-btn { padding: 0; width: 38px; justify-content: center; }
|
||||
.trek-dash .col-glass-btn.has-count { width: auto; padding: 0 12px; }
|
||||
.trek-dash .col-hero-stats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; margin: 0 -18px; padding: 0 18px; }
|
||||
.trek-dash .col-hero-stats::-webkit-scrollbar { display: none; }
|
||||
.trek-dash .col-chip { flex-shrink: 0; }
|
||||
.trek-dash .col-search input { width: 100%; }
|
||||
.trek-dash .col-search { flex: 1; }
|
||||
.trek-dash .col-mapwrap { height: calc(100vh - 260px); min-height: 320px; }
|
||||
.trek-dash .col-mapwrap { height: calc(100dvh - 260px); min-height: 320px; }
|
||||
|
||||
/* bigger touch targets on phones (visuals unchanged on desktop) */
|
||||
.trek-dash .col-viewseg button { width: 44px; height: 38px; }
|
||||
.trek-dash .col-filter-btn { min-height: 40px; }
|
||||
.trek-dash .col-filter-opt { min-height: 40px; }
|
||||
.trek-dash .col-selbar-btn { min-height: 40px; }
|
||||
.trek-dash .col-detail-close { width: 40px; height: 40px; }
|
||||
.trek-dash .col-detail-btn { min-height: 40px; }
|
||||
.trek-dash .col-detail-icon-btn { min-width: 40px; min-height: 40px; }
|
||||
.trek-dash .col-drawer .col-row-btn { min-height: 44px; }
|
||||
.trek-dash .col-drawer .col-rail-new { min-height: 44px; }
|
||||
/* enlarge the interactive status badge's tap area without changing its look */
|
||||
.trek-dash .col-lrow-end > [role="button"] { position: relative; }
|
||||
.trek-dash .col-lrow-end > [role="button"]::after { content: ''; position: absolute; inset: -9px -6px; }
|
||||
|
||||
/* select action bar: break the bulk actions onto their own line instead of
|
||||
stranding them behind a growing spacer */
|
||||
.trek-dash .col-selbar .col-toolbar-spacer { flex-basis: 100%; height: 0; }
|
||||
}
|
||||
|
||||
/* ----------------- mobile rail drawer ----------------- */
|
||||
@@ -453,5 +471,9 @@
|
||||
position: fixed; left: 0; top: 0; bottom: 0; z-index: 151; width: 288px; max-width: 82vw;
|
||||
background: var(--surface); border-right: 1px solid var(--line); padding: 12px; overflow-y: auto;
|
||||
padding-top: calc(var(--nav-h) + 12px);
|
||||
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
@media (max-width: 720px) { .trek-dash .col-drawer { padding-top: 16px; } }
|
||||
.trek-dash .col-drawer-head { display: flex; justify-content: flex-end; margin-bottom: 6px; }
|
||||
.trek-dash .col-drawer-head button { width: 40px; height: 40px; display: grid; place-items: center; border-radius: var(--r-sm); color: var(--ink-2); }
|
||||
.trek-dash .col-drawer-head button:hover { background: var(--surface-2); color: var(--ink); }
|
||||
@media (max-width: 720px) { .trek-dash .col-drawer { padding-top: calc(16px + env(safe-area-inset-top, 0px)); } }
|
||||
|
||||
Reference in New Issue
Block a user