feat(panel): single-panel architecture with UX refinements

Major refactor consolidating two-panel layout (Routes/Contacts + floating
PlaceDetail) into one 400px left column with state-driven content.

Architecture:
- New PlaceCard component for preview and stop cards (collapsible)
- Panel states: IDLE, PREVIEW, ROUTING, PREVIEW_ROUTING, ROUTE_CALCULATED
- usePanelState selector in store.js derives state from selectedPlace/stops/route
- StopList now renders stops as PlaceCard with variant=stop
- PlaceDetail.jsx removed from App.jsx (content moved to PlaceCard)

UX refinements:
- Panel width 400px (was 360px) to fit buttons on one line
- Map zoom padding updated to 420px for wider panel
- Body text bumped to text-sm (14px) for readability
- Get Directions button hidden when 2+ stops (route auto-calculates)
- PlaceCard title prefers feature name (raw.name) over formatted address
- Preview card shows above route during PREVIEW_ROUTING state
- Directions flow no longer shows toast when GPS denied
This commit is contained in:
Matt 2026-04-26 21:14:39 +00:00
commit 5eb83e9b4b
6 changed files with 626 additions and 100 deletions

View file

@ -1352,8 +1352,8 @@ const MapView = forwardRef(function MapView(_, ref) {
(b, c) => b.extend(c),
new maplibregl.LngLatBounds(allCoords[0], allCoords[0])
)
const hasDetail = useStore.getState().selectedPlace != null
const leftPad = hasDetail ? 700 : 340
// Single-panel: no floating detail
const leftPad = 420 // 360px panel + margin
map.fitBounds(bounds, { padding: { top: 60, bottom: 60, left: leftPad, right: 60 } })
}
}
@ -1426,7 +1426,7 @@ const MapView = forwardRef(function MapView(_, ref) {
(b, s) => b.extend([s.lon, s.lat]),
new maplibregl.LngLatBounds([stops[0].lon, stops[0].lat], [stops[0].lon, stops[0].lat])
)
map.fitBounds(bounds, { padding: { top: 60, bottom: 60, left: 340, right: 60 } })
map.fitBounds(bounds, { padding: { top: 60, bottom: 60, left: 420, right: 60 } })
}
}
}, [stops, route, gpsOrigin, geoPermission])