|
1 | 1 | import { CloseFullscreenTwoTone, OpenInFullTwoTone } from '@mui/icons-material' |
2 | 2 | import { IconButton } from '@mui/material' |
3 | | -import { PropsWithChildren, ReactNode, useCallback, useRef, useState } from 'react' |
| 3 | +import { PropsWithChildren, ReactNode, useCallback, useState } from 'react' |
4 | 4 | import { useTranslation } from 'react-i18next' |
5 | | -import { AttributionControl, MapContainer, MapContainerProps, ZoomControl } from 'react-leaflet' |
6 | | -import { useConstrainedFloatingButton } from 'src/hooks/useConstrainedFloatingButton' |
| 5 | +import { AttributionControl, MapContainer, MapContainerProps } from 'react-leaflet' |
7 | 6 | import { useTheme } from 'src/layout/ThemeContext' |
| 7 | +import { MapZoomBar } from './MapZoomBar' |
8 | 8 |
|
9 | 9 | /** |
10 | 10 | * Shared shell for every map page: the `.map-info` wrapper (with the |
11 | | - * expand/collapse + dark-theme classes), the floating expand button, and a |
12 | | - * MapContainer whose zoom/attribution controls are placed direction-aware |
13 | | - * (zoom at the inline-end, attribution at the inline-start). Pages pass the |
14 | | - * MapContainer props (center/zoom/…) and the layers as children. An optional |
15 | | - * `legend` renders in the shared `.map-legend` box (top inline-start corner) |
16 | | - * so every map page places and themes its legend the same way — pages provide |
17 | | - * only the legend content. |
| 11 | + * expand/collapse + dark-theme classes) and a MapContainer whose controls are |
| 12 | + * placed direction-aware — the zoom bar at the inline-end with the |
| 13 | + * expand/collapse button as its bottom segment, attribution at the |
| 14 | + * inline-start. Pages pass the MapContainer props (center/zoom/…) and the |
| 15 | + * layers as children. An optional `legend` renders in the shared `.map-legend` |
| 16 | + * box (top inline-start corner) so every map page places and themes its legend |
| 17 | + * the same way — pages provide only the legend content. |
18 | 18 | */ |
19 | 19 | type MapShellProps = PropsWithChildren<MapContainerProps & { legend?: ReactNode }> |
20 | 20 |
|
21 | 21 | export function MapShell({ children, legend, ...mapProps }: MapShellProps) { |
22 | 22 | const [isExpanded, setIsExpanded] = useState<boolean>(false) |
23 | 23 | const toggleExpanded = useCallback(() => setIsExpanded((expanded) => !expanded), []) |
24 | 24 |
|
25 | | - const mapContainerRef = useRef<HTMLDivElement>(null) |
26 | | - const buttonRef = useRef<HTMLButtonElement>(null) |
27 | | - useConstrainedFloatingButton(mapContainerRef, buttonRef, isExpanded) |
28 | | - |
29 | 25 | const { isDarkTheme } = useTheme() |
30 | 26 | const { i18n } = useTranslation() |
31 | 27 | const isRtl = i18n.dir() === 'rtl' |
32 | 28 |
|
33 | 29 | return ( |
34 | 30 | <div |
35 | | - ref={mapContainerRef} |
36 | 31 | className={`map-info ${isExpanded ? 'expanded' : 'collapsed'}${isDarkTheme ? ' dark' : ''}`}> |
37 | | - <IconButton |
38 | | - ref={buttonRef} |
39 | | - color="primary" |
40 | | - className="expand-button" |
41 | | - onClick={toggleExpanded}> |
42 | | - {isExpanded ? ( |
43 | | - <CloseFullscreenTwoTone fontSize="large" /> |
44 | | - ) : ( |
45 | | - <OpenInFullTwoTone fontSize="large" /> |
46 | | - )} |
47 | | - </IconButton> |
48 | 32 | {legend && <div className="map-legend">{legend}</div>} |
49 | 33 | <MapContainer {...mapProps} zoomControl={false} attributionControl={false}> |
50 | | - <ZoomControl position={isRtl ? 'topleft' : 'topright'} /> |
| 34 | + <MapZoomBar position={isRtl ? 'topleft' : 'topright'}> |
| 35 | + <IconButton className="expand-button" onClick={toggleExpanded}> |
| 36 | + {isExpanded ? ( |
| 37 | + <CloseFullscreenTwoTone fontSize="small" /> |
| 38 | + ) : ( |
| 39 | + <OpenInFullTwoTone fontSize="small" /> |
| 40 | + )} |
| 41 | + </IconButton> |
| 42 | + </MapZoomBar> |
51 | 43 | <AttributionControl position={isRtl ? 'bottomright' : 'bottomleft'} prefix={false} /> |
52 | 44 | {children} |
53 | 45 | </MapContainer> |
|
0 commit comments