lgtm
This commit is contained in:
parent
084117cea8
commit
a8de0a614d
36 changed files with 1329 additions and 522 deletions
|
|
@ -69,6 +69,7 @@ interface MapProps {
|
|||
bounds?: Bounds | null;
|
||||
hideLegend?: boolean;
|
||||
hideLocationSearch?: boolean;
|
||||
hideTopCardsWhenNarrow?: boolean;
|
||||
travelTimeEntries?: TravelTimeEntry[];
|
||||
densityLabel?: string;
|
||||
totalCount?: number;
|
||||
|
|
@ -82,6 +83,17 @@ interface Dimensions {
|
|||
height: number;
|
||||
}
|
||||
|
||||
const DESKTOP_TOP_CARD_WIDTH = 300;
|
||||
const DESKTOP_TOP_CARD_GAP = 8;
|
||||
const DESKTOP_TOP_CARD_HORIZONTAL_INSET = 24;
|
||||
const DESKTOP_TOP_CARDS_STACKED_MIN_MAP_WIDTH =
|
||||
DESKTOP_TOP_CARD_WIDTH + DESKTOP_TOP_CARD_HORIZONTAL_INSET;
|
||||
const DESKTOP_TOP_CARDS_ROW_MIN_MAP_WIDTH =
|
||||
DESKTOP_TOP_CARD_WIDTH * 2 + DESKTOP_TOP_CARD_GAP + DESKTOP_TOP_CARD_HORIZONTAL_INSET;
|
||||
const DESKTOP_TOP_CARD_CLASS = 'w-[300px]';
|
||||
const DESKTOP_LOCATION_SEARCH_INPUT_CLASS =
|
||||
'px-2 py-2 text-sm w-full border-none outline-none bg-transparent text-warm-700 dark:text-warm-200 placeholder-warm-400 dark:placeholder-warm-500';
|
||||
|
||||
type MapContainerStyle = CSSProperties & {
|
||||
'--map-mobile-bottom-inset'?: string;
|
||||
};
|
||||
|
|
@ -208,6 +220,23 @@ function getRenderedViewState(map: MapRef | null): ViewState | null {
|
|||
};
|
||||
}
|
||||
|
||||
function getRenderedVisibleCenter(
|
||||
map: MapRef | null,
|
||||
dimensions: Dimensions,
|
||||
bottomScreenInset: number
|
||||
): Pick<ViewState, 'latitude' | 'longitude'> | null {
|
||||
if (!map || dimensions.width <= 0 || dimensions.height <= 0) return null;
|
||||
|
||||
const visibleBottomInset = clamp(bottomScreenInset, 0, dimensions.height);
|
||||
const visibleCenterY = (dimensions.height - visibleBottomInset) / 2;
|
||||
const center = map.unproject([dimensions.width / 2, visibleCenterY]);
|
||||
|
||||
return {
|
||||
longitude: center.lng,
|
||||
latitude: center.lat,
|
||||
};
|
||||
}
|
||||
|
||||
function DeckOverlay({
|
||||
layers,
|
||||
getTooltip,
|
||||
|
|
@ -260,6 +289,7 @@ export default memo(function Map({
|
|||
bounds: viewportBounds,
|
||||
hideLegend = false,
|
||||
hideLocationSearch = false,
|
||||
hideTopCardsWhenNarrow = false,
|
||||
travelTimeEntries = EMPTY_TRAVEL_ENTRIES,
|
||||
densityLabel: densityLabelProp,
|
||||
totalCount: totalCountProp,
|
||||
|
|
@ -319,6 +349,9 @@ export default memo(function Map({
|
|||
const dataBoundsHeight = dimensions.height + Math.max(0, bottomScreenInset);
|
||||
const bounds = getBoundsFromViewState(renderedViewState, dimensions.width, dataBoundsHeight);
|
||||
const resolution = zoomToResolution(renderedViewState.zoom);
|
||||
const renderedVisibleCenter =
|
||||
getRenderedVisibleCenter(mapRef.current, dimensions, bottomScreenInset) ??
|
||||
renderedViewState;
|
||||
|
||||
onViewChange({
|
||||
resolution,
|
||||
|
|
@ -326,6 +359,8 @@ export default memo(function Map({
|
|||
zoom: renderedViewState.zoom,
|
||||
latitude: renderedViewState.latitude,
|
||||
longitude: renderedViewState.longitude,
|
||||
visibleLatitude: renderedVisibleCenter.latitude,
|
||||
visibleLongitude: renderedVisibleCenter.longitude,
|
||||
});
|
||||
};
|
||||
frame = window.requestAnimationFrame(emit);
|
||||
|
|
@ -389,6 +424,19 @@ export default memo(function Map({
|
|||
() => (bottomScreenInset > 0 ? { '--map-mobile-bottom-inset': `${bottomScreenInset}px` } : {}),
|
||||
[bottomScreenInset]
|
||||
);
|
||||
const hideDesktopTopCardsForWidth =
|
||||
hideTopCardsWhenNarrow &&
|
||||
dimensions.width > 0 &&
|
||||
dimensions.width < DESKTOP_TOP_CARDS_STACKED_MIN_MAP_WIDTH;
|
||||
const stackDesktopTopCards =
|
||||
hideTopCardsWhenNarrow &&
|
||||
dimensions.width >= DESKTOP_TOP_CARDS_STACKED_MIN_MAP_WIDTH &&
|
||||
dimensions.width < DESKTOP_TOP_CARDS_ROW_MIN_MAP_WIDTH;
|
||||
const showLocationSearch = !hideLocationSearch && !hideDesktopTopCardsForWidth;
|
||||
const showLegend = !hideLegend && !hideDesktopTopCardsForWidth;
|
||||
const desktopTopCardsLayoutClass = stackDesktopTopCards
|
||||
? 'flex-col items-start'
|
||||
: 'items-start justify-between';
|
||||
|
||||
const {
|
||||
layers,
|
||||
|
|
@ -452,11 +500,11 @@ export default memo(function Map({
|
|||
<div className="absolute inset-0 z-20 pointer-events-none flex flex-col">
|
||||
{/* Center: Logo card with hero text */}
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="flex items-center gap-8 bg-navy-900/90 rounded-3xl px-14 py-10">
|
||||
<LogoIcon className="w-24 h-24 text-teal-400" />
|
||||
<div className="flex items-center gap-8 bg-navy-900/90 rounded-3xl px-14 py-10 max-w-[1040px]">
|
||||
<LogoIcon className="w-24 h-24 shrink-0 text-teal-400" />
|
||||
<span
|
||||
className="font-bold text-white whitespace-nowrap"
|
||||
style={{ fontSize: '5rem' }}
|
||||
className="font-bold text-white/50"
|
||||
style={{ fontSize: '4rem', lineHeight: 1.05, maxWidth: '760px' }}
|
||||
>
|
||||
{t('map.ogTitle')}
|
||||
</span>
|
||||
|
|
@ -494,74 +542,83 @@ export default memo(function Map({
|
|||
) : null
|
||||
) : (
|
||||
<>
|
||||
<div className="absolute top-3 left-3 right-3 z-20 flex flex-wrap items-start justify-between gap-2 pointer-events-none">
|
||||
{!hideLocationSearch && (
|
||||
<LocationSearch
|
||||
onFlyTo={handleFlyTo}
|
||||
onLocationSearched={onLocationSearched}
|
||||
onCurrentLocationFound={onCurrentLocationFound}
|
||||
onMouseEnter={handleMouseLeave}
|
||||
/>
|
||||
)}
|
||||
{!hideLegend &&
|
||||
(viewFeature && colorRange ? (
|
||||
viewFeature.startsWith('tt_') ? (
|
||||
<MapLegend
|
||||
featureLabel={t('travel.travelTime', {
|
||||
mode: modes.label(
|
||||
viewFeature.split('_')[1] as 'car' | 'bicycle' | 'walking' | 'transit'
|
||||
),
|
||||
})}
|
||||
range={colorRange}
|
||||
showCancel={viewSource === 'eye'}
|
||||
onCancel={onCancelPin}
|
||||
onResetScale={viewSource === 'eye' ? onResetPreviewScale : undefined}
|
||||
resetScaleDisabled={!canResetPreviewScale}
|
||||
mode="feature"
|
||||
theme={theme}
|
||||
suffix=" min"
|
||||
/>
|
||||
) : colorFeatureMeta ? (
|
||||
<MapLegend
|
||||
featureLabel={
|
||||
viewSource === 'eye'
|
||||
? t('mapLegend.previewing', { name: ts(colorFeatureMeta.name) })
|
||||
: ts(colorFeatureMeta.name)
|
||||
}
|
||||
range={colorRange}
|
||||
showCancel={viewSource === 'eye'}
|
||||
onCancel={onCancelPin}
|
||||
onResetScale={viewSource === 'eye' ? onResetPreviewScale : undefined}
|
||||
resetScaleDisabled={!canResetPreviewScale}
|
||||
mode="feature"
|
||||
enumValues={
|
||||
colorFeatureMeta.type === 'enum' ? colorFeatureMeta.values : undefined
|
||||
}
|
||||
featureName={colorFeatureMeta.name}
|
||||
theme={theme}
|
||||
suffix={colorFeatureMeta.suffix}
|
||||
raw={colorFeatureMeta.raw}
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<MapLegend
|
||||
featureLabel={densityLabel}
|
||||
range={
|
||||
usePostcodeView
|
||||
? [postcodeCountRange.min, postcodeCountRange.max]
|
||||
: [countRange.min, countRange.max]
|
||||
}
|
||||
totalCount={
|
||||
totalCountProp ??
|
||||
(usePostcodeView ? postcodeCountRange.total : countRange.total)
|
||||
}
|
||||
showCancel={false}
|
||||
onCancel={onCancelPin}
|
||||
mode="density"
|
||||
theme={theme}
|
||||
{(showLocationSearch || showLegend) && (
|
||||
<div
|
||||
className={`absolute top-3 left-3 right-3 z-20 flex gap-2 pointer-events-none ${desktopTopCardsLayoutClass}`}
|
||||
>
|
||||
{showLocationSearch && (
|
||||
<LocationSearch
|
||||
onFlyTo={handleFlyTo}
|
||||
onLocationSearched={onLocationSearched}
|
||||
onCurrentLocationFound={onCurrentLocationFound}
|
||||
onMouseEnter={handleMouseLeave}
|
||||
className={DESKTOP_TOP_CARD_CLASS}
|
||||
inputClassName={DESKTOP_LOCATION_SEARCH_INPUT_CLASS}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{showLegend &&
|
||||
(viewFeature && colorRange ? (
|
||||
viewFeature.startsWith('tt_') ? (
|
||||
<MapLegend
|
||||
featureLabel={t('travel.travelTime', {
|
||||
mode: modes.label(
|
||||
viewFeature.split('_')[1] as 'car' | 'bicycle' | 'walking' | 'transit'
|
||||
),
|
||||
})}
|
||||
range={colorRange}
|
||||
showCancel={viewSource === 'eye'}
|
||||
onCancel={onCancelPin}
|
||||
onResetScale={viewSource === 'eye' ? onResetPreviewScale : undefined}
|
||||
resetScaleDisabled={!canResetPreviewScale}
|
||||
mode="feature"
|
||||
theme={theme}
|
||||
suffix=" min"
|
||||
className={DESKTOP_TOP_CARD_CLASS}
|
||||
/>
|
||||
) : colorFeatureMeta ? (
|
||||
<MapLegend
|
||||
featureLabel={
|
||||
viewSource === 'eye'
|
||||
? t('mapLegend.previewing', { name: ts(colorFeatureMeta.name) })
|
||||
: ts(colorFeatureMeta.name)
|
||||
}
|
||||
range={colorRange}
|
||||
showCancel={viewSource === 'eye'}
|
||||
onCancel={onCancelPin}
|
||||
onResetScale={viewSource === 'eye' ? onResetPreviewScale : undefined}
|
||||
resetScaleDisabled={!canResetPreviewScale}
|
||||
mode="feature"
|
||||
enumValues={
|
||||
colorFeatureMeta.type === 'enum' ? colorFeatureMeta.values : undefined
|
||||
}
|
||||
featureName={colorFeatureMeta.name}
|
||||
theme={theme}
|
||||
suffix={colorFeatureMeta.suffix}
|
||||
raw={colorFeatureMeta.raw}
|
||||
className={DESKTOP_TOP_CARD_CLASS}
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<MapLegend
|
||||
featureLabel={densityLabel}
|
||||
range={
|
||||
usePostcodeView
|
||||
? [postcodeCountRange.min, postcodeCountRange.max]
|
||||
: [countRange.min, countRange.max]
|
||||
}
|
||||
totalCount={
|
||||
totalCountProp ??
|
||||
(usePostcodeView ? postcodeCountRange.total : countRange.total)
|
||||
}
|
||||
showCancel={false}
|
||||
onCancel={onCancelPin}
|
||||
mode="density"
|
||||
theme={theme}
|
||||
className={DESKTOP_TOP_CARD_CLASS}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{popupInfo && (
|
||||
<div
|
||||
className="pointer-events-none absolute bg-white dark:bg-warm-800 rounded-lg shadow-lg text-sm dark:text-white"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue