From 5aed0553316c092500d0ab1d4fdcdcea2df1f338 Mon Sep 17 00:00:00 2001 From: Tudor Date: Mon, 30 Mar 2026 09:21:44 +0100 Subject: [PATCH] feat(map): add fullscreen button using browser Fullscreen API Button sits top-right of the map (matching Leaflet control style), toggles expand/compress icon, and syncs state with Escape key via the fullscreenchange event. Co-Authored-By: Claude Sonnet 4.6 --- nextjs-app/components/SchoolMap.module.css | 28 +++++++++++++ nextjs-app/components/SchoolMap.tsx | 46 +++++++++++++++++++--- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/nextjs-app/components/SchoolMap.module.css b/nextjs-app/components/SchoolMap.module.css index 0f780c1..4c65387 100644 --- a/nextjs-app/components/SchoolMap.module.css +++ b/nextjs-app/components/SchoolMap.module.css @@ -4,6 +4,34 @@ position: relative; } +.mapWrapper.fullscreen { + width: 100vw; + height: 100vh; +} + +.fullscreenBtn { + position: absolute; + top: 0.625rem; + right: 0.625rem; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + background: white; + border: 2px solid rgba(0, 0, 0, 0.2); + border-radius: 4px; + cursor: pointer; + color: #333; + transition: background 0.15s ease, color 0.15s ease; +} + +.fullscreenBtn:hover { + background: #f4f4f4; + color: #000; +} + .mapLoading { width: 100%; height: 100%; diff --git a/nextjs-app/components/SchoolMap.tsx b/nextjs-app/components/SchoolMap.tsx index fa33d2f..e81993c 100644 --- a/nextjs-app/components/SchoolMap.tsx +++ b/nextjs-app/components/SchoolMap.tsx @@ -6,6 +6,7 @@ 'use client'; import dynamic from 'next/dynamic'; +import { useRef, useState, useEffect, useCallback } from 'react'; import type { School } from '@/lib/types'; import styles from './SchoolMap.module.css'; @@ -29,24 +30,59 @@ interface SchoolMapProps { } export function SchoolMap({ schools, center, zoom = 13, referencePoint, onMarkerClick }: SchoolMapProps) { + const wrapperRef = useRef(null); + const [isFullscreen, setIsFullscreen] = useState(false); + + // Sync state with browser fullscreen events (e.g. Escape key) + useEffect(() => { + const onFsChange = () => setIsFullscreen(!!document.fullscreenElement); + document.addEventListener('fullscreenchange', onFsChange); + return () => document.removeEventListener('fullscreenchange', onFsChange); + }, []); + + const toggleFullscreen = useCallback(() => { + if (!document.fullscreenElement) { + wrapperRef.current?.requestFullscreen(); + } else { + document.exitFullscreen(); + } + }, []); + // Calculate center if not provided const mapCenter: [number, number] = center || (() => { - if (schools.length === 0) return [51.5074, -0.1278]; // Default to London + if (schools.length === 0) return [51.5074, -0.1278]; if (schools.length === 1 && schools[0].latitude && schools[0].longitude) { return [schools[0].latitude, schools[0].longitude]; } - - // Calculate average position const validSchools = schools.filter(s => s.latitude && s.longitude); if (validSchools.length === 0) return [51.5074, -0.1278]; - const avgLat = validSchools.reduce((sum, s) => sum + (s.latitude || 0), 0) / validSchools.length; const avgLng = validSchools.reduce((sum, s) => sum + (s.longitude || 0), 0) / validSchools.length; return [avgLat, avgLng]; })(); return ( -
+
+