feat(ui): Redesign bottom-right map control cluster

- Increase touch targets from 36px to 44px (meets accessibility guidelines)
- Wrap Locate and Layers buttons in unified .map-controls-br container
- Layer popover now opens LEFT of buttons (avoids collision with Locate)
- Add hover and active states with theme-aware styling
- Proper spacing for scale control below the cluster
- Increased icon sizes from 18px to 20px
- Mobile-responsive with proper max-height on layer popover

Layout:
  [Locate] 44x44
  [Layers] 44x44
  ──────────────
  Scale: 0.5 mi

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-05-01 23:17:13 +00:00
commit 67779dbbf7
4 changed files with 84 additions and 31 deletions

View file

@ -96,8 +96,12 @@ export default function App() {
<Panel onManeuverClick={handleManeuverClick} /> <Panel onManeuverClick={handleManeuverClick} />
<ContactModal /> <ContactModal />
<LayerControl mapRef={mapViewRef} />
{/* Bottom-right map controls */}
<div className="map-controls-br">
<LocateButton mapRef={mapViewRef} /> <LocateButton mapRef={mapViewRef} />
<LayerControl mapRef={mapViewRef} />
</div>
</div> </div>
) )
} }

View file

@ -271,7 +271,7 @@ export default function LayerControl({ mapRef }) {
title="Map layers" title="Map layers"
aria-label="Toggle map layers" aria-label="Toggle map layers"
> >
<Layers size={18} /> <Layers size={20} />
</button> </button>
{open && ( {open && (

View file

@ -48,7 +48,7 @@ export default function LocateButton({ mapRef }) {
title="My location" title="My location"
aria-label="Center map on my location" aria-label="Center map on my location"
> >
<Locate size={18} /> <Locate size={20} />
</button> </button>
) )
} }

View file

@ -236,42 +236,80 @@ body {
opacity: 1; opacity: 1;
} }
/* ═══ LAYER CONTROL ═══ */ /* ═══ BOTTOM-RIGHT MAP CONTROLS ═══ */
.layer-control { .map-controls-br {
position: absolute; position: absolute;
bottom: 32px; bottom: 40px;
right: 10px; right: 10px;
z-index: 10; z-index: 10;
display: flex;
flex-direction: column;
gap: 8px;
} }
.layer-control-btn { .map-control-btn {
width: 36px; width: 44px;
height: 36px; height: 44px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--bg-raised); background: var(--bg-raised);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 10px;
color: var(--text-secondary); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
box-shadow: var(--shadow); box-shadow: var(--shadow);
transition: color 0.1s, border-color 0.1s; transition: color 0.15s, border-color 0.15s, background 0.15s;
}
.map-control-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background: var(--bg-overlay);
}
.map-control-btn:active {
background: var(--bg-muted);
}
/* ═══ LAYER CONTROL ═══ */
.layer-control {
position: relative;
}
.layer-control-btn {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-raised);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text-secondary);
cursor: pointer;
box-shadow: var(--shadow);
transition: color 0.15s, border-color 0.15s, background 0.15s;
} }
.layer-control-btn:hover { .layer-control-btn:hover {
color: var(--text-primary); color: var(--text-primary);
border-color: var(--accent); border-color: var(--accent);
background: var(--bg-overlay);
}
.layer-control-btn:active {
background: var(--bg-muted);
} }
.layer-control-popover { .layer-control-popover {
position: absolute; position: absolute;
bottom: 44px; bottom: 0;
right: 0; right: 52px;
min-width: 160px; min-width: 180px;
background: var(--bg-raised); background: var(--bg-raised);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 10px;
padding: 8px 0; padding: 8px 0;
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
} }
@ -367,27 +405,28 @@ body {
/* ═══ LOCATE BUTTON ═══ */ /* ═══ LOCATE BUTTON ═══ */
.locate-btn { .locate-btn {
position: absolute; width: 44px;
bottom: 80px; height: 44px;
right: 10px;
z-index: 10;
width: 36px;
height: 36px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: var(--bg-raised); background: var(--bg-raised);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: 8px; border-radius: 10px;
color: var(--text-secondary); color: var(--text-secondary);
cursor: pointer; cursor: pointer;
box-shadow: var(--shadow); box-shadow: var(--shadow);
transition: color 0.1s, border-color 0.1s; transition: color 0.15s, border-color 0.15s, background 0.15s;
} }
.locate-btn:hover { .locate-btn:hover {
color: var(--text-primary); color: var(--text-primary);
border-color: var(--accent); border-color: var(--accent);
background: var(--bg-overlay);
}
.locate-btn:active {
background: var(--bg-muted);
} }
/* ═══ STOP REMOVE BUTTON (touch-friendly) ═══ */ /* ═══ STOP REMOVE BUTTON (touch-friendly) ═══ */
@ -406,16 +445,15 @@ body {
overflow-x: hidden; overflow-x: hidden;
} }
.layer-control { .map-controls-br {
bottom: auto; bottom: 24px;
top: 120px; right: 8px;
right: 10px;
} }
.locate-btn { .layer-control-popover {
bottom: auto; right: 52px;
top: 166px; max-height: 60vh;
right: 10px; overflow-y: auto;
} }
.stop-remove-btn { .stop-remove-btn {
@ -501,3 +539,14 @@ body {
line-height: 1.5; line-height: 1.5;
text-shadow: 0 0 2px rgba(0, 0, 0, 0.8); text-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
} }
/* ═══ MAPLIBRE CONTROL POSITIONING ═══ */
.maplibregl-ctrl-bottom-right {
bottom: 10px;
right: 10px;
}
.maplibregl-ctrl-bottom-right .maplibregl-ctrl-scale {
margin-right: 0;
margin-bottom: 0;
}