import { Suspense, type MutableRefObject, type ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import type { ActualListing, FeatureFilters, FeatureMeta, POI, PostcodeGeometry, ViewState, } from '../../../types'; import type { useMapData } from '../../../hooks/useMapData'; import type { TravelTimeEntry } from '../../../hooks/useTravelTime'; import type { OverlayId } from '../../../lib/overlays'; import type { BasemapId } from '../../../lib/basemaps'; import type { SearchedLocation } from '../LocationSearch'; import MobileBottomSheet from '../MobileBottomSheet'; import { MapPinIcon } from '../../ui/icons/MapPinIcon'; import { EyeIcon } from '../../ui/icons/EyeIcon'; import { HouseIcon } from '../../ui/icons/HouseIcon'; import { SpinnerIcon } from '../../ui/icons/SpinnerIcon'; import { IndeterminateProgressBar } from '../../ui/IndeterminateProgressBar'; import type { MapFlyTo } from './types'; import { MapControlButton, type MapControlState } from './MapControlButton'; import { MapFallback, PaneFallback } from './Fallbacks'; import { MapErrorBoundary } from '../MapErrorBoundary'; import { LoadingOverlay } from './LoadingOverlay'; import { Map, MobileDrawer } from './lazyComponents'; type MapData = ReturnType; type RightPaneTab = 'properties' | 'area'; interface MobileMapPageProps { initialLoading: boolean; mapData: MapData; pois: POI[]; activeOverlays: Set; activeCrimeTypes: Set; basemap: BasemapId; colorOpacity: number; mapViewFeature: string | null; filterRange: [number, number] | null; viewSource: 'drag' | 'eye' | null; onCancelPin: () => void; features: FeatureMeta[]; selectedHexagonId: string | null; hoveredHexagonId: string | null; onHexagonClick: (id: string, isPostcode?: boolean, geometry?: PostcodeGeometry) => void; onHexagonHover: (h3: string | null, x?: number, y?: number) => void; initialViewState: ViewState; flyToRef: MutableRefObject; theme: 'light' | 'dark'; filters: FeatureFilters; selectedPostcodeGeometry: PostcodeGeometry | null; onLocationSearched: (location: SearchedLocation | null) => void; onCurrentLocationFound: (lat: number, lng: number) => void; currentLocation: { lat: number; lng: number } | null; actualListings: ActualListing[]; actualListingsEnabled: boolean; actualListingsLoading: boolean; onToggleListingsPane?: () => void; listingsPaneOpen: boolean; listingsPane: ReactNode; travelTimeEntries: TravelTimeEntry[]; bottomScreenInset: number; onBottomSheetCoveredHeightChange: (height: number) => void; mobileDrawerOpen: boolean; onMobileDrawerClose: () => void; onMobileDrawerPanelRectChange: (rect: DOMRectReadOnly) => void; rightPaneTab: RightPaneTab; onMobileDrawerTabChange: (tab: RightPaneTab) => void; poiPaneOpen: boolean; onTogglePoiPane: () => void; poiButtonLabel: string; poiPane: ReactNode; poiControl: MapControlState; overlayPaneOpen: boolean; onToggleOverlayPane: () => void; overlayPane: ReactNode; overlayControl: MapControlState; filtersPane: ReactNode; mobileLegend: ReactNode; renderAreaPane: () => ReactNode; renderPropertiesPane: () => ReactNode; toasts: ReactNode; upgradeModal: ReactNode; editingBar?: ReactNode; } export function MobileMapPage({ initialLoading, mapData, pois, activeOverlays, activeCrimeTypes, basemap, colorOpacity, mapViewFeature, filterRange, viewSource, onCancelPin, features, selectedHexagonId, hoveredHexagonId, onHexagonClick, onHexagonHover, initialViewState, flyToRef, theme, filters, selectedPostcodeGeometry, onLocationSearched, onCurrentLocationFound, currentLocation, actualListings, actualListingsEnabled, actualListingsLoading, onToggleListingsPane, listingsPaneOpen, listingsPane, travelTimeEntries, bottomScreenInset, onBottomSheetCoveredHeightChange, mobileDrawerOpen, onMobileDrawerClose, onMobileDrawerPanelRectChange, rightPaneTab, onMobileDrawerTabChange, poiPaneOpen, onTogglePoiPane, poiButtonLabel, poiPane, poiControl, overlayPaneOpen, onToggleOverlayPane, overlayPane, overlayControl, filtersPane, mobileLegend, renderAreaPane, renderPropertiesPane, toasts, upgradeModal, editingBar, }: MobileMapPageProps) { const { t } = useTranslation(); const floatingPaneAvailableHeight = `max(12rem, calc(100dvh - ${Math.ceil( bottomScreenInset )}px - 7rem))`; // The action buttons (listing/overlays/POI) are pinned to the top-right over // the map. Reserve their width on the right of the top-card overlay so the // expanded search bar shrinks to fit instead of sliding underneath them. // Each button is p-2 + a 1.25rem icon (36px); they sit in a `gap-2` (8px) row. const ACTION_BUTTON_WIDTH = 36; const ACTION_BUTTON_GAP = 8; const actionButtonCount = (onToggleListingsPane ? 1 : 0) + 2; const topCardsRightInset = actionButtonCount * ACTION_BUTTON_WIDTH + actionButtonCount * ACTION_BUTTON_GAP; // inter-button gaps + breathing room before the search return (
}>
{onToggleListingsPane && ( )} } /> } />
{listingsPaneOpen && (
{listingsPane}
)} {overlayPaneOpen && (
{overlayPane}
)} {poiPaneOpen && (
{poiPane}
)} {filtersPane} {mobileDrawerOpen && selectedHexagonId && ( }> )} {toasts} {upgradeModal}
); }