Files
school_compare/nextjs-app/components/SchoolHeroMap.tsx
T

78 lines
2.8 KiB
TypeScript
Raw Normal View History

/**
* 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>
);
},
);