@@ -7,7 +7,7 @@ import { Redirect, Slot } from 'expo-router';
77import { Menu } from 'lucide-react-native' ;
88import React , { useCallback , useEffect , useRef } from 'react' ;
99import { useTranslation } from 'react-i18next' ;
10- import { ActivityIndicator , Platform , StyleSheet } from 'react-native' ;
10+ import { ActivityIndicator , Platform , StyleSheet , Text as RNText , TouchableOpacity , View as RNView } from 'react-native' ;
1111import { useSafeAreaInsets } from 'react-native-safe-area-context' ;
1212
1313import { NotificationButton } from '@/components/notifications/NotificationButton' ;
@@ -41,6 +41,7 @@ export default function TabLayout() {
4141 const [ isFirstTime , _setIsFirstTime ] = useIsFirstTime ( ) ;
4242 const [ isOpen , setIsOpen ] = React . useState ( false ) ;
4343 const [ isNotificationsOpen , setIsNotificationsOpen ] = React . useState ( false ) ;
44+ const [ webColorScheme , setWebColorScheme ] = React . useState < 'light' | 'dark' > ( 'light' ) ;
4445
4546 // Get store states first (hooks must be at top level)
4647 const config = useCoreStore ( ( state ) => state . config ) ;
@@ -59,6 +60,16 @@ export default function TabLayout() {
5960 const { isActive, appState } = useAppLifecycle ( ) ;
6061 const insets = useSafeAreaInsets ( ) ;
6162
63+ // Web dark mode detection (safe - only runs on web)
64+ React . useEffect ( ( ) => {
65+ if ( Platform . OS !== 'web' ) return ;
66+ const mediaQuery = window . matchMedia ( '(prefers-color-scheme: dark)' ) ;
67+ setWebColorScheme ( mediaQuery . matches ? 'dark' : 'light' ) ;
68+ const handler = ( e : MediaQueryListEvent ) => setWebColorScheme ( e . matches ? 'dark' : 'light' ) ;
69+ mediaQuery . addEventListener ( 'change' , handler ) ;
70+ return ( ) => mediaQuery . removeEventListener ( 'change' , handler ) ;
71+ } , [ ] ) ;
72+
6273 // Refs to track initialization state
6374 const hasInitialized = useRef ( false ) ;
6475 const isInitializing = useRef ( false ) ;
@@ -348,75 +359,66 @@ export default function TabLayout() {
348359 } ,
349360 } ) ;
350361
351- const content = (
352- < View style = { styles . container } >
353- { /* Top Navigation Bar */ }
354- < View className = "flex-row items-center justify-between bg-primary-600 px-4" style = { { paddingTop : insets . top } } >
355- < CreateDrawerMenuButton setIsOpen = { setIsOpen } />
356- < View className = "flex-1 items-center" >
357- < Text className = "text-lg font-semibold text-white" > { t ( 'app.title' , 'Resgrid Responder' ) } </ Text >
362+ // Web theme with dark mode support (matching panel header and button colors)
363+ const webIsDark = webColorScheme === 'dark' ;
364+ const webTheme = {
365+ navBar : { backgroundColor : webIsDark ? '#1f2937' : '#f9fafb' } , // gray-800 / gray-50 (panel header colors)
366+ navBarText : { color : webIsDark ? '#f9fafb' : '#030712' } , // gray-50 / gray-950
367+ sidebar : {
368+ backgroundColor : webIsDark ? '#030712' : '#f3f4f6' , // gray-950 / gray-100
369+ borderRightColor : webIsDark ? '#1f2937' : '#e5e7eb' , // gray-800 / gray-200
370+ } ,
371+ sidebarFooter : {
372+ borderTopColor : webIsDark ? '#1f2937' : '#e5e7eb' ,
373+ backgroundColor : webIsDark ? '#111827' : '#ffffff' , // gray-900 / white
374+ } ,
375+ closeButton : { backgroundColor : '#2563eb' } , // blue-600 (panel button color)
376+ closeButtonText : { color : '#ffffff' } ,
377+ mainContent : { backgroundColor : webIsDark ? '#030712' : '#f3f4f6' } , // gray-950 / gray-100
378+ } ;
379+
380+ const content =
381+ Platform . OS === 'web' ? (
382+ < RNView style = { styles . container } >
383+ { /* Top Navigation Bar */ }
384+ < RNView style = { [ layoutStyles . navBar , { paddingTop : insets . top } , webTheme . navBar ] } >
385+ < CreateDrawerMenuButton setIsOpen = { setIsOpen } colorScheme = { webColorScheme } />
386+ < RNView style = { layoutStyles . navBarTitle } >
387+ < RNText style = { [ layoutStyles . navBarTitleText , webTheme . navBarText ] } > { t ( 'app.title' , 'Resgrid Responder' ) } </ RNText >
388+ </ RNView >
389+ </ RNView >
390+
391+ < RNView style = { { flex : 1 , flexDirection : 'row' } } ref = { parentRef } >
392+ { /* Sidebar - simple show/hide */ }
393+ { isOpen ? (
394+ < RNView style = { [ layoutStyles . webSidebar , webTheme . sidebar ] } >
395+ < SideMenu onNavigate = { handleNavigate } colorScheme = { webColorScheme } />
396+ < RNView style = { [ layoutStyles . sidebarFooter , webTheme . sidebarFooter ] } >
397+ < TouchableOpacity onPress = { ( ) => setIsOpen ( false ) } style = { [ layoutStyles . closeButton , webTheme . closeButton ] } >
398+ < RNText style = { [ layoutStyles . closeButtonText , webTheme . closeButtonText ] } > { t ( 'menu.close' , 'Close Menu' ) } </ RNText >
399+ </ TouchableOpacity >
400+ </ RNView >
401+ </ RNView >
402+ ) : null }
403+
404+ { /* Main content area */ }
405+ < RNView style = { [ layoutStyles . mainContent , webTheme . mainContent ] } >
406+ < Slot />
407+ </ RNView >
408+ </ RNView >
409+ </ RNView >
410+ ) : (
411+ < View style = { styles . container } >
412+ { /* Top Navigation Bar */ }
413+ < View className = "flex-row items-center justify-between bg-primary-600 px-4" style = { { paddingTop : insets . top } } >
414+ < CreateDrawerMenuButton setIsOpen = { setIsOpen } />
415+ < View className = "flex-1 items-center" >
416+ < Text className = "text-lg font-semibold text-white" > { t ( 'app.title' , 'Resgrid Responder' ) } </ Text >
417+ </ View >
358418 </ View >
359- </ View >
360419
361- < View className = "flex-1" ref = { parentRef } >
362- { /* Drawer menu - always rendered as modal, closed by default */ }
363- { Platform . OS === 'web' ? (
364- // Web-specific drawer implementation with fixed positioning
365- isOpen && (
366- < View
367- // @ts -ignore - web specific styles
368- style = { {
369- position : 'fixed' ,
370- top : 0 ,
371- left : 0 ,
372- right : 0 ,
373- bottom : 0 ,
374- zIndex : 9999 ,
375- display : 'flex' ,
376- flexDirection : 'row' ,
377- } }
378- >
379- { /* Backdrop */ }
380- < Pressable
381- onPress = { ( ) => setIsOpen ( false ) }
382- // @ts -ignore - web specific styles
383- style = { {
384- position : 'absolute' ,
385- top : 0 ,
386- left : 0 ,
387- right : 0 ,
388- bottom : 0 ,
389- backgroundColor : 'rgba(0, 0, 0, 0.5)' ,
390- } }
391- />
392- { /* Drawer Content */ }
393- < View
394- className = "bg-white dark:bg-gray-900"
395- // @ts -ignore - web specific styles
396- style = { {
397- position : 'relative' ,
398- width : '80%' ,
399- maxWidth : 320 ,
400- height : '100%' ,
401- display : 'flex' ,
402- flexDirection : 'column' ,
403- zIndex : 1 ,
404- boxShadow : '2px 0 8px rgba(0, 0, 0, 0.15)' ,
405- } }
406- >
407- < View style = { { flex : 1 , overflow : 'scroll' as 'visible' | 'hidden' | 'scroll' } } >
408- < SideMenu onNavigate = { handleNavigate } />
409- </ View >
410- < View className = "border-t border-gray-200 p-4 dark:border-gray-700" >
411- < Button onPress = { ( ) => setIsOpen ( false ) } className = "w-full bg-primary-600" >
412- < ButtonText > Close</ ButtonText >
413- </ Button >
414- </ View >
415- </ View >
416- </ View >
417- )
418- ) : (
419- // Native drawer implementation
420+ < View className = "flex-1" ref = { parentRef } >
421+ { /* Native drawer implementation */ }
420422 < Drawer isOpen = { isOpen } onClose = { ( ) => setIsOpen ( false ) } >
421423 < DrawerBackdrop onPress = { ( ) => setIsOpen ( false ) } />
422424 < DrawerContent className = "w-4/5 max-w-xs bg-white p-0 dark:bg-gray-900" >
@@ -430,15 +432,14 @@ export default function TabLayout() {
430432 </ DrawerFooter >
431433 </ DrawerContent >
432434 </ Drawer >
433- ) }
434435
435- { /* Main content area */ }
436- < View className = "w-full flex-1" >
437- < Slot />
436+ { /* Main content area */ }
437+ < View className = "w-full flex-1" >
438+ < Slot />
439+ </ View >
438440 </ View >
439441 </ View >
440- </ View >
441- ) ;
442+ ) ;
442443
443444 // On web, skip Novu integration as it may cause rendering issues
444445 if ( Platform . OS === 'web' ) {
@@ -465,9 +466,20 @@ export default function TabLayout() {
465466
466467interface CreateDrawerMenuButtonProps {
467468 setIsOpen : ( isOpen : boolean ) => void ;
469+ colorScheme ?: 'light' | 'dark' ;
468470}
469471
470- const CreateDrawerMenuButton = ( { setIsOpen } : CreateDrawerMenuButtonProps ) => {
472+ const CreateDrawerMenuButton = ( { setIsOpen, colorScheme } : CreateDrawerMenuButtonProps ) => {
473+ // Use React Native primitives on web to avoid infinite render loops from gluestack-ui/lucide
474+ if ( Platform . OS === 'web' ) {
475+ const isDark = colorScheme === 'dark' ;
476+ return (
477+ < TouchableOpacity onPress = { ( ) => setIsOpen ( true ) } testID = "drawer-menu-button" style = { layoutStyles . menuButton } >
478+ < RNText style = { [ layoutStyles . menuIcon , { color : isDark ? '#f9fafb' : '#030712' } ] } > ☰</ RNText >
479+ </ TouchableOpacity >
480+ ) ;
481+ }
482+
471483 return (
472484 < Pressable
473485 className = "p-2"
@@ -506,3 +518,71 @@ const styles = StyleSheet.create({
506518 height : '100%' ,
507519 } ,
508520} ) ;
521+
522+ const layoutStyles = StyleSheet . create ( {
523+ navBar : {
524+ flexDirection : 'row' ,
525+ alignItems : 'center' ,
526+ justifyContent : 'space-between' ,
527+ paddingHorizontal : 16 ,
528+ paddingVertical : 12 ,
529+ shadowColor : '#000' ,
530+ shadowOffset : { width : 0 , height : 2 } ,
531+ shadowOpacity : 0.1 ,
532+ shadowRadius : 3 ,
533+ elevation : 3 ,
534+ } ,
535+ navBarTitle : {
536+ flex : 1 ,
537+ alignItems : 'center' ,
538+ } ,
539+ navBarTitleText : {
540+ fontSize : 18 ,
541+ fontWeight : '700' ,
542+ color : 'white' ,
543+ letterSpacing : 0.5 ,
544+ } ,
545+ menuButton : {
546+ padding : 8 ,
547+ borderRadius : 6 ,
548+ } ,
549+ menuIcon : {
550+ fontSize : 24 ,
551+ color : 'white' ,
552+ fontWeight : 'bold' ,
553+ } ,
554+ webSidebar : {
555+ width : 280 ,
556+ borderRightWidth : 1 ,
557+ shadowColor : '#000' ,
558+ shadowOffset : { width : 2 , height : 0 } ,
559+ shadowOpacity : 0.1 ,
560+ shadowRadius : 3 ,
561+ elevation : 2 ,
562+ } ,
563+ sidebarFooter : {
564+ borderTopWidth : 1 ,
565+ padding : 16 ,
566+ } ,
567+ mainContent : {
568+ flex : 1 ,
569+ } ,
570+ backdrop : {
571+ position : 'absolute' ,
572+ top : 0 ,
573+ left : 0 ,
574+ right : 0 ,
575+ bottom : 0 ,
576+ backgroundColor : 'rgba(0, 0, 0, 0.5)' ,
577+ } ,
578+ closeButton : {
579+ padding : 12 ,
580+ borderRadius : 8 ,
581+ alignItems : 'center' ,
582+ } ,
583+ closeButtonText : {
584+ color : 'white' ,
585+ fontWeight : '600' ,
586+ fontSize : 14 ,
587+ } ,
588+ } ) ;
0 commit comments