78 lines
2.8 KiB
TypeScript
78 lines
2.8 KiB
TypeScript
/**
|
|||
|
|
* SchoolHeroMap
|
||
|
|
* The location map that sits atop the school-detail hero. Shows a static
|
||
|
|
* preview (pin + tiles) that blends down into the title; the whole band — or
|
||
|
|
* the parent's "View on map" link, via the imperative `open()` handle — expands
|
||
|
|
* it to a fullscreen, interactive map.
|
||
|
|
*/
|
||
|
|
|
||
|
|
'use client';
|
||
|
|
|
||
|
|
import dynamic from 'next/dynamic';
|
||
|
|
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||
|
|
import styles from './SchoolHeroMap.module.css';
|
||
|
|
|
||
|
|
const LeafletHeroMap = dynamic(() => import('./LeafletHeroMapInner'), {
|
||
|
|
ssr: false,
|
||
|
|
loading: () => <div className={styles.skeleton} aria-hidden="true" />,
|
||
|
|
});
|
||
|
|
|
||
|
|
export interface SchoolHeroMapHandle {
|
||
|
|
open: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface SchoolHeroMapProps {
|
||
|
|
lat: number;
|
||
|
|
lng: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export const SchoolHeroMap = forwardRef<SchoolHeroMapHandle, SchoolHeroMapProps>(
|
||
|
|
function SchoolHeroMap({ lat, lng }, ref) {
|
||
|
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||
|
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||
|
|
|
||
|
|
const open = useCallback(() => {
|
||
|
|
wrapperRef.current?.requestFullscreen?.().catch(() => {});
|
||
|
|
}, []);
|
||
|
|
const close = useCallback(() => {
|
||
|
|
if (document.fullscreenElement) document.exitFullscreen().catch(() => {});
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
useImperativeHandle(ref, () => ({ open }), [open]);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const onChange = () => setIsFullscreen(!!document.fullscreenElement);
|
||
|
|
document.addEventListener('fullscreenchange', onChange);
|
||
|
|
return () => document.removeEventListener('fullscreenchange', onChange);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div ref={wrapperRef} className={styles.wrapper} data-fullscreen={isFullscreen || undefined}>
|
||
|
|
<LeafletHeroMap lat={lat} lng={lng} interactive={isFullscreen} />
|
||
|
|
|
||
|
|
{isFullscreen ? (
|
||
|
|
<button type="button" className={styles.closeBtn} onClick={close} aria-label="Close map">
|
||
|
|
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
|
|
<path d="M18 6 6 18M6 6l12 12" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
) : (
|
||
|
|
<>
|
||
|
|
{/* Whole-band click target that opens the full map. */}
|
||
|
|
<button type="button" className={styles.openBtn} onClick={open} aria-label="Open full map">
|
||
|
|
<span className={styles.openHint}>
|
||
|
|
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||
|
|
<path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7" />
|
||
|
|
</svg>
|
||
|
|
Open full map
|
||
|
|
</span>
|
||
|
|
</button>
|
||
|
|
{/* Diffuse blend into the hero body below. */}
|
||
|
|
<div className={styles.fade} aria-hidden="true" />
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
},
|
||
|
|
);
|