177 lines
5.8 KiB
TypeScript
177 lines
5.8 KiB
TypeScript
import { Suspense, type MutableRefObject, type ReactNode } from 'react';
|
|
|
|
import type { FeatureFilters, FeatureMeta, POI, PostcodeGeometry, ViewState } from '../../../types';
|
|
import type { useMapData } from '../../../hooks/useMapData';
|
|
import type { TravelTimeEntry } from '../../../hooks/useTravelTime';
|
|
import type { SearchedLocation } from '../LocationSearch';
|
|
import MobileBottomSheet from '../MobileBottomSheet';
|
|
import { MapPinIcon } from '../../ui/icons/MapPinIcon';
|
|
import type { MapFlyTo } from './types';
|
|
import { MapFallback, PaneFallback } from './Fallbacks';
|
|
import { LoadingOverlay } from './LoadingOverlay';
|
|
import { Map, MobileDrawer } from './lazyComponents';
|
|
|
|
type MapData = ReturnType<typeof useMapData>;
|
|
type RightPaneTab = 'properties' | 'area';
|
|
|
|
interface MobileMapPageProps {
|
|
initialLoading: boolean;
|
|
mapData: MapData;
|
|
pois: POI[];
|
|
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<MapFlyTo | null>;
|
|
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;
|
|
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;
|
|
filtersPane: ReactNode;
|
|
mobileLegend: ReactNode;
|
|
renderAreaPane: () => ReactNode;
|
|
renderPropertiesPane: () => ReactNode;
|
|
toasts: ReactNode;
|
|
upgradeModal: ReactNode;
|
|
}
|
|
|
|
export function MobileMapPage({
|
|
initialLoading,
|
|
mapData,
|
|
pois,
|
|
mapViewFeature,
|
|
filterRange,
|
|
viewSource,
|
|
onCancelPin,
|
|
features,
|
|
selectedHexagonId,
|
|
hoveredHexagonId,
|
|
onHexagonClick,
|
|
onHexagonHover,
|
|
initialViewState,
|
|
flyToRef,
|
|
theme,
|
|
filters,
|
|
selectedPostcodeGeometry,
|
|
onLocationSearched,
|
|
onCurrentLocationFound,
|
|
currentLocation,
|
|
travelTimeEntries,
|
|
bottomScreenInset,
|
|
onBottomSheetCoveredHeightChange,
|
|
mobileDrawerOpen,
|
|
onMobileDrawerClose,
|
|
onMobileDrawerPanelRectChange,
|
|
rightPaneTab,
|
|
onMobileDrawerTabChange,
|
|
poiPaneOpen,
|
|
onTogglePoiPane,
|
|
poiButtonLabel,
|
|
poiPane,
|
|
filtersPane,
|
|
mobileLegend,
|
|
renderAreaPane,
|
|
renderPropertiesPane,
|
|
toasts,
|
|
upgradeModal,
|
|
}: MobileMapPageProps) {
|
|
return (
|
|
<div className="flex-1 overflow-hidden relative">
|
|
<LoadingOverlay show={initialLoading} />
|
|
|
|
<div className="absolute inset-0">
|
|
<Suspense fallback={<MapFallback />}>
|
|
<Map
|
|
data={mapData.data}
|
|
postcodeData={mapData.postcodeData}
|
|
usePostcodeView={mapData.usePostcodeView}
|
|
pois={pois}
|
|
onViewChange={mapData.handleViewChange}
|
|
viewFeature={mapViewFeature}
|
|
colorRange={mapData.colorRange}
|
|
filterRange={filterRange}
|
|
viewSource={viewSource}
|
|
onCancelPin={onCancelPin}
|
|
onResetPreviewScale={mapData.handleResetPreviewScale}
|
|
canResetPreviewScale={mapData.canResetPreviewScale}
|
|
features={features}
|
|
selectedHexagonId={selectedHexagonId}
|
|
hoveredHexagonId={hoveredHexagonId}
|
|
onHexagonClick={onHexagonClick}
|
|
onHexagonHover={onHexagonHover}
|
|
initialViewState={initialViewState}
|
|
flyToRef={flyToRef}
|
|
theme={theme}
|
|
filters={filters}
|
|
selectedPostcodeGeometry={selectedPostcodeGeometry}
|
|
onLocationSearched={onLocationSearched}
|
|
onCurrentLocationFound={onCurrentLocationFound}
|
|
currentLocation={currentLocation}
|
|
bounds={mapData.bounds}
|
|
hideLegend
|
|
hideLocationSearch={mobileDrawerOpen && !!selectedHexagonId}
|
|
travelTimeEntries={travelTimeEntries}
|
|
bottomScreenInset={bottomScreenInset}
|
|
/>
|
|
</Suspense>
|
|
</div>
|
|
|
|
<button
|
|
onClick={onTogglePoiPane}
|
|
className={`absolute top-3 right-3 z-20 p-2 rounded-lg shadow-lg bg-white dark:bg-warm-800 ${poiPaneOpen ? 'text-teal-600 dark:text-teal-400' : 'text-warm-500 dark:text-warm-400 hover:text-teal-600 dark:hover:text-teal-400'}`}
|
|
aria-label={poiButtonLabel}
|
|
>
|
|
<MapPinIcon className="w-5 h-5" />
|
|
</button>
|
|
|
|
{poiPaneOpen && (
|
|
<div className="absolute top-14 right-3 left-3 z-20 flex h-[45dvh] min-h-0 flex-col overflow-hidden rounded-lg border border-warm-200 bg-white shadow-xl dark:border-warm-700 dark:bg-warm-900">
|
|
{poiPane}
|
|
</div>
|
|
)}
|
|
|
|
<MobileBottomSheet
|
|
legend={mobileLegend}
|
|
onCoveredHeightChange={onBottomSheetCoveredHeightChange}
|
|
>
|
|
{filtersPane}
|
|
</MobileBottomSheet>
|
|
|
|
{mobileDrawerOpen && selectedHexagonId && (
|
|
<Suspense fallback={<PaneFallback />}>
|
|
<MobileDrawer
|
|
onClose={onMobileDrawerClose}
|
|
renderArea={renderAreaPane}
|
|
renderProperties={renderPropertiesPane}
|
|
tab={rightPaneTab}
|
|
onPanelRectChange={onMobileDrawerPanelRectChange}
|
|
onTabChange={onMobileDrawerTabChange}
|
|
/>
|
|
</Suspense>
|
|
)}
|
|
|
|
{toasts}
|
|
{upgradeModal}
|
|
</div>
|
|
);
|
|
}
|