1- import { useState , useCallback , useMemo , useEffect } from "react"
1+ import { useState , useCallback , useMemo , useEffect , useRef } from "react"
22import { MapContainer } from "./components/Map/MapContainer"
33import { useMapData } from "./hooks/useMapData"
44import { useCameras } from "./hooks/useCameras"
@@ -21,6 +21,8 @@ function App() {
2121 useState < FreeSpotFilterValue > ( "all" )
2222 const [ selectedCameraId , setSelectedCameraId ] = useState < number | null > ( null )
2323 const [ filtersVisible , setFiltersVisible ] = useState ( false )
24+ const filtersRef = useRef < HTMLDivElement > ( null )
25+ const toggleButtonRef = useRef < HTMLButtonElement > ( null )
2426
2527 const { zones, loading, error, total, refetch } = useMapData ( {
2628 autoFetch : true ,
@@ -113,6 +115,28 @@ function App() {
113115 return ( ) => clearInterval ( interval )
114116 } , [ refetch ] )
115117
118+ useEffect ( ( ) => {
119+ const handleClickOutside = ( event : MouseEvent ) => {
120+ if (
121+ filtersVisible &&
122+ filtersRef . current &&
123+ toggleButtonRef . current &&
124+ ! filtersRef . current . contains ( event . target as Node ) &&
125+ ! toggleButtonRef . current . contains ( event . target as Node )
126+ ) {
127+ setFiltersVisible ( false )
128+ }
129+ }
130+
131+ if ( filtersVisible ) {
132+ document . addEventListener ( "mousedown" , handleClickOutside )
133+ }
134+
135+ return ( ) => {
136+ document . removeEventListener ( "mousedown" , handleClickOutside )
137+ }
138+ } , [ filtersVisible ] )
139+
116140 return (
117141 < div className = "min-h-screen bg-gray-50" >
118142 < main className = "mx-auto" >
@@ -126,26 +150,45 @@ function App() {
126150 />
127151
128152 < button
153+ ref = { toggleButtonRef }
129154 onClick = { ( ) => setFiltersVisible ( ! filtersVisible ) }
130155 className = "absolute top-20 left-2 sm:top-20 sm:left-4 z-[1001] bg-white/90 backdrop-blur-sm rounded-lg shadow-lg border border-gray-200 p-2 hover:bg-white transition-colors"
131156 aria-label = "Toggle filters"
132157 >
133- < svg
134- xmlns = "http://www.w3.org/2000/svg"
135- className = "h-5 w-5 text-gray-700"
136- viewBox = "0 0 20 20"
137- fill = "currentColor"
138- >
139- < path
140- fillRule = "evenodd"
141- d = "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z"
142- clipRule = "evenodd"
143- />
144- </ svg >
158+ { filtersVisible ? (
159+ < svg
160+ xmlns = "http://www.w3.org/2000/svg"
161+ className = "h-5 w-5 text-gray-700"
162+ viewBox = "0 0 20 20"
163+ fill = "currentColor"
164+ >
165+ < path
166+ fillRule = "evenodd"
167+ d = "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
168+ clipRule = "evenodd"
169+ />
170+ </ svg >
171+ ) : (
172+ < svg
173+ xmlns = "http://www.w3.org/2000/svg"
174+ className = "h-5 w-5 text-gray-700"
175+ viewBox = "0 0 20 20"
176+ fill = "currentColor"
177+ >
178+ < path
179+ fillRule = "evenodd"
180+ d = "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z"
181+ clipRule = "evenodd"
182+ />
183+ </ svg >
184+ ) }
145185 </ button >
146186
147187 { filtersVisible && (
148- < div className = "absolute top-2 left-16 right-2 sm:top-4 sm:left-16 sm:right-auto sm:max-w-md z-[1000] bg-white/90 backdrop-blur-sm rounded-lg shadow-lg border border-gray-200" >
188+ < div
189+ ref = { filtersRef }
190+ className = "absolute top-2 left-16 right-2 sm:top-4 sm:left-16 sm:right-auto sm:max-w-md z-[1000] bg-white/90 backdrop-blur-sm rounded-lg shadow-lg border border-gray-200"
191+ >
149192 < div className = "p-3 sm:p-4" >
150193 < div className = "flex flex-col gap-3" >
151194 < div >
0 commit comments