1+ import { useEffect , useRef , useState } from 'react' ;
12import type { SupportedLanguage } from '../sampleRegistry' ;
23import { translate } from '../i18n' ;
34import type { MapProvider } from './appRouting' ;
45
6+ const PROVIDERS : Array < { value : MapProvider ; label : string } > = [
7+ { value : 'maplibre' , label : 'MapLibre' } ,
8+ { value : 'mapbox' , label : 'Mapbox' } ,
9+ { value : 'leaflet' , label : 'Leaflet' } ,
10+ { value : 'openlayers' , label : 'OpenLayers' } ,
11+ { value : 'arcgis' , label : 'ArcGIS 2D' } ,
12+ { value : 'arcgis-3d' , label : 'ArcGIS 3D' } ,
13+ { value : 'cesium' , label : 'Cesium' } ,
14+ { value : 'here' , label : 'HERE' } ,
15+ { value : 'google' , label : 'Google Maps' } ,
16+ { value : 'google-3d' , label : 'Google Maps 3D' } ,
17+ ] ;
18+
19+ const LANGUAGES : Array < { value : SupportedLanguage ; label : string ; short : string } > = [
20+ { value : 'en' , label : 'English' , short : 'EN' } ,
21+ { value : 'ja' , label : '日本語' , short : 'JA' } ,
22+ { value : 'es-419' , label : 'Español (Latinoamérica)' , short : 'ES' } ,
23+ ] ;
24+
25+ type IconMenu = 'provider' | 'language' | null ;
26+
527export function AppHeader ( { language, provider, showProviderSelector, onOpenMenu, onProviderChange, onLanguageChange } : {
628 language : SupportedLanguage ;
729 provider : MapProvider | null ;
@@ -10,29 +32,112 @@ export function AppHeader({ language, provider, showProviderSelector, onOpenMenu
1032 onProviderChange ( provider : MapProvider ) : void ;
1133 onLanguageChange ( language : SupportedLanguage ) : void ;
1234} ) {
35+ const [ openMenu , setOpenMenu ] = useState < IconMenu > ( null ) ;
36+ const controlsRef = useRef < HTMLDivElement > ( null ) ;
37+
38+ useEffect ( ( ) => {
39+ if ( ! openMenu ) return ;
40+ const handlePointer = ( event : MouseEvent ) => {
41+ if ( controlsRef . current && ! controlsRef . current . contains ( event . target as Node ) ) {
42+ setOpenMenu ( null ) ;
43+ }
44+ } ;
45+ const handleEscape = ( event : KeyboardEvent ) => {
46+ if ( event . key === 'Escape' ) setOpenMenu ( null ) ;
47+ } ;
48+ document . addEventListener ( 'mousedown' , handlePointer ) ;
49+ document . addEventListener ( 'keydown' , handleEscape ) ;
50+ return ( ) => {
51+ document . removeEventListener ( 'mousedown' , handlePointer ) ;
52+ document . removeEventListener ( 'keydown' , handleEscape ) ;
53+ } ;
54+ } , [ openMenu ] ) ;
55+
56+ const activeLanguageShort = LANGUAGES . find ( l => l . value === language ) ?. short ?? 'EN' ;
57+ const activeProviderLabel = PROVIDERS . find ( p => p . value === provider ) ?. label ?? 'Provider' ;
58+
1359 return (
1460 < header className = "header" >
1561 < h1 > MapConductor React SDK Samples</ h1 >
16- < div className = "header-controls" >
62+ < div className = "header-controls" ref = { controlsRef } >
1763 < button type = "button" className = "menu-button" aria-label = "Open samples menu" onClick = { onOpenMenu } >
1864 < span /> < span /> < span />
1965 </ button >
2066 { showProviderSelector && < label className = "provider-control" > < span > Provider</ span >
2167 < select value = { provider ?? 'maplibre' } onChange = { event => onProviderChange ( event . target . value as MapProvider ) } >
22- < option value = "maplibre" > MapLibre</ option > < option value = "mapbox" > Mapbox</ option >
23- < option value = "leaflet" > Leaflet</ option > < option value = "openlayers" > OpenLayers</ option >
24- < option value = "arcgis" > ArcGIS 2D</ option > < option value = "arcgis-3d" > ArcGIS 3D</ option >
25- < option value = "cesium" > Cesium</ option > < option value = "here" > HERE</ option >
26- < option value = "google" > Google Maps</ option >
27- < option value = "google-3d" > Google Maps 3D</ option >
68+ { PROVIDERS . map ( p => < option key = { p . value } value = { p . value } > { p . label } </ option > ) }
2869 </ select >
2970 </ label > }
71+ { showProviderSelector && (
72+ < div className = "header-icon-control" >
73+ < button
74+ type = "button"
75+ className = "header-icon-button"
76+ aria-label = { `Provider: ${ activeProviderLabel } ` }
77+ aria-expanded = { openMenu === 'provider' }
78+ onClick = { ( ) => setOpenMenu ( openMenu === 'provider' ? null : 'provider' ) }
79+ >
80+ < svg viewBox = "0 0 24 24" width = "20" height = "20" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" aria-hidden = "true" >
81+ < polygon points = "12 2 2 7 12 12 22 7 12 2" />
82+ < polyline points = "2 17 12 22 22 17" />
83+ < polyline points = "2 12 12 17 22 12" />
84+ </ svg >
85+ </ button >
86+ { openMenu === 'provider' && (
87+ < div className = "header-icon-menu" role = "menu" >
88+ { PROVIDERS . map ( p => (
89+ < button
90+ key = { p . value }
91+ type = "button"
92+ role = "menuitemradio"
93+ aria-checked = { provider === p . value }
94+ className = { `header-icon-menu-item${ provider === p . value ? ' active' : '' } ` }
95+ onClick = { ( ) => { onProviderChange ( p . value ) ; setOpenMenu ( null ) ; } }
96+ >
97+ { p . label }
98+ </ button >
99+ ) ) }
100+ </ div >
101+ ) }
102+ </ div >
103+ ) }
30104 < label className = "language-control" > < span > { translate ( language , 'Language' , '言語' , 'Idioma' ) } </ span >
31105 < select value = { language } onChange = { event => onLanguageChange ( event . target . value as SupportedLanguage ) } >
32- < option value = "en" > English</ option > < option value = "ja" > 日本語</ option >
33- < option value = "es-419" > Español (Latinoamérica)</ option >
106+ { LANGUAGES . map ( l => < option key = { l . value } value = { l . value } > { l . label } </ option > ) }
34107 </ select >
35108 </ label >
109+ < div className = "header-icon-control" >
110+ < button
111+ type = "button"
112+ className = "header-icon-button header-language-icon-button"
113+ aria-label = { `Language: ${ activeLanguageShort } ` }
114+ aria-expanded = { openMenu === 'language' }
115+ onClick = { ( ) => setOpenMenu ( openMenu === 'language' ? null : 'language' ) }
116+ >
117+ < svg viewBox = "0 0 24 24" width = "20" height = "20" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" aria-hidden = "true" >
118+ < circle cx = "12" cy = "12" r = "10" />
119+ < line x1 = "2" y1 = "12" x2 = "22" y2 = "12" />
120+ < path d = "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
121+ </ svg >
122+ < span className = "header-language-short" > { activeLanguageShort } </ span >
123+ </ button >
124+ { openMenu === 'language' && (
125+ < div className = "header-icon-menu" role = "menu" >
126+ { LANGUAGES . map ( l => (
127+ < button
128+ key = { l . value }
129+ type = "button"
130+ role = "menuitemradio"
131+ aria-checked = { language === l . value }
132+ className = { `header-icon-menu-item${ language === l . value ? ' active' : '' } ` }
133+ onClick = { ( ) => { onLanguageChange ( l . value ) ; setOpenMenu ( null ) ; } }
134+ >
135+ { l . label }
136+ </ button >
137+ ) ) }
138+ </ div >
139+ ) }
140+ </ div >
36141 </ div >
37142 </ header >
38143 ) ;
0 commit comments