feat: Unify navigation with consistent header and centered bottom menu
- Removed duplicate MobileNav AppBar from desktop view - Added unified header bar across all screen sizes (mobile + desktop) - Positioned connection status on left, user menu on right in header - Centered bottom navigation menu on desktop (30% width with 20px margin) - Hidden floating voice button on desktop (using center button instead) - Updated voice command button to pink color (#FF69B4) matching design - Added rounded corners to desktop bottom navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -62,134 +62,30 @@ export const AppShell = ({ children }: AppShellProps) => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
bgcolor: 'background.default',
|
bgcolor: 'background.default',
|
||||||
pb: { xs: '64px', md: 0 }, // Space for tab bar on mobile
|
pb: '64px', // Space for tab bar on both mobile and desktop
|
||||||
}}>
|
}}>
|
||||||
{!isMobile && <MobileNav />}
|
{/* Header Bar - Both Mobile and Desktop */}
|
||||||
|
<Box
|
||||||
{/* Mobile Header Bar */}
|
sx={{
|
||||||
{isMobile && (
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: 48,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
px: 2,
|
||||||
|
zIndex: 1200,
|
||||||
|
boxShadow: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Connection Status & Presence Indicator */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
height: 48,
|
|
||||||
bgcolor: 'background.paper',
|
|
||||||
borderBottom: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
px: 2,
|
|
||||||
zIndex: 1200,
|
|
||||||
boxShadow: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Connection Status & Presence Indicator */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip title={isConnected ? t('connection.syncActive') : t('connection.syncDisconnected')}>
|
|
||||||
<Chip
|
|
||||||
icon={isConnected ? <Wifi /> : <WifiOff />}
|
|
||||||
label={isConnected ? t('connection.live') : t('connection.offline')}
|
|
||||||
size="small"
|
|
||||||
color={isConnected ? 'success' : 'default'}
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
{isConnected && presence.count > 1 && (
|
|
||||||
<Tooltip title={t('connection.familyMembersOnline', { count: presence.count })}>
|
|
||||||
<Chip
|
|
||||||
icon={<People />}
|
|
||||||
label={presence.count}
|
|
||||||
size="small"
|
|
||||||
color="primary"
|
|
||||||
sx={{
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* User Menu Button - Top Right */}
|
|
||||||
<IconButton
|
|
||||||
onClick={handleMenuOpen}
|
|
||||||
size="small"
|
|
||||||
aria-label="user menu"
|
|
||||||
aria-controls={anchorEl ? 'user-menu' : undefined}
|
|
||||||
aria-haspopup="true"
|
|
||||||
aria-expanded={anchorEl ? 'true' : undefined}
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
sx={{
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
bgcolor: 'primary.main',
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{user?.name?.charAt(0).toUpperCase() || 'U'}
|
|
||||||
</Avatar>
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<Menu
|
|
||||||
id="user-menu"
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
open={Boolean(anchorEl)}
|
|
||||||
onClose={handleMenuClose}
|
|
||||||
onClick={handleMenuClose}
|
|
||||||
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
|
||||||
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
|
||||||
sx={{
|
|
||||||
mt: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={() => handleNavigate('/settings')}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Settings fontSize="small" />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{t('navigation.settings')}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() => handleNavigate('/children')}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<ChildCare fontSize="small" />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{t('navigation.children')}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem onClick={() => handleNavigate('/family')}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Group fontSize="small" />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{t('navigation.family')}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
<Divider />
|
|
||||||
<MenuItem onClick={handleLogout}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<Logout fontSize="small" color="error" />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{t('navigation.logout')}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Connection Status & Presence Indicator - Desktop Only */}
|
|
||||||
{!isMobile && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: 16,
|
|
||||||
right: 16,
|
|
||||||
zIndex: 1200,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
@@ -202,7 +98,6 @@ export const AppShell = ({ children }: AppShellProps) => {
|
|||||||
color={isConnected ? 'success' : 'default'}
|
color={isConnected ? 'success' : 'default'}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
boxShadow: 1,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -216,13 +111,72 @@ export const AppShell = ({ children }: AppShellProps) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
boxShadow: 1,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
|
{/* User Menu Button - Top Right */}
|
||||||
|
<IconButton
|
||||||
|
onClick={handleMenuOpen}
|
||||||
|
size="small"
|
||||||
|
aria-label="user menu"
|
||||||
|
aria-controls={anchorEl ? 'user-menu' : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded={anchorEl ? 'true' : undefined}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
sx={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
bgcolor: 'primary.main',
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{user?.name?.charAt(0).toUpperCase() || 'U'}
|
||||||
|
</Avatar>
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
id="user-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleMenuClose}
|
||||||
|
onClick={handleMenuClose}
|
||||||
|
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||||
|
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||||
|
sx={{
|
||||||
|
mt: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={() => handleNavigate('/settings')}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Settings fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{t('navigation.settings')}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => handleNavigate('/children')}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ChildCare fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{t('navigation.children')}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => handleNavigate('/family')}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Group fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{t('navigation.family')}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
<MenuItem onClick={handleLogout}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Logout fontSize="small" color="error" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{t('navigation.logout')}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Container
|
<Container
|
||||||
maxWidth={isTablet ? 'md' : 'lg'}
|
maxWidth={isTablet ? 'md' : 'lg'}
|
||||||
@@ -230,13 +184,13 @@ export const AppShell = ({ children }: AppShellProps) => {
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
px: { xs: 2, md: 3 },
|
px: { xs: 2, md: 3 },
|
||||||
py: 3,
|
py: 3,
|
||||||
pt: { xs: '64px', md: 3 }, // Add top padding for header bar on mobile
|
pt: '64px', // Add top padding for header bar on both mobile and desktop
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
{isMobile && <TabBar />}
|
<TabBar />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { BottomNavigation, BottomNavigationAction, Paper, Fab, Box } from '@mui/material';
|
import { BottomNavigation, BottomNavigationAction, Paper, Fab, Box, IconButton } from '@mui/material';
|
||||||
import {
|
import {
|
||||||
Home,
|
Home,
|
||||||
Timeline,
|
Timeline,
|
||||||
@@ -33,10 +33,13 @@ export const TabBar = () => {
|
|||||||
aria-label="Primary navigation"
|
aria-label="Primary navigation"
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: 0,
|
bottom: { xs: 0, md: 20 },
|
||||||
left: 0,
|
left: { xs: 0, md: '50%' },
|
||||||
right: 0,
|
right: { xs: 0, md: 'auto' },
|
||||||
|
transform: { xs: 'none', md: 'translateX(-50%)' },
|
||||||
|
width: { xs: '100%', md: '30%' },
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
|
borderRadius: { xs: 0, md: 2 },
|
||||||
}}
|
}}
|
||||||
elevation={3}
|
elevation={3}
|
||||||
>
|
>
|
||||||
@@ -50,6 +53,7 @@ export const TabBar = () => {
|
|||||||
showLabels
|
showLabels
|
||||||
sx={{
|
sx={{
|
||||||
height: 64,
|
height: 64,
|
||||||
|
borderRadius: { xs: 0, md: 2 },
|
||||||
'& .MuiBottomNavigationAction-root': {
|
'& .MuiBottomNavigationAction-root': {
|
||||||
minWidth: 60,
|
minWidth: 60,
|
||||||
'&.Mui-selected': {
|
'&.Mui-selected': {
|
||||||
@@ -60,7 +64,7 @@ export const TabBar = () => {
|
|||||||
>
|
>
|
||||||
{tabs.map((tab) => {
|
{tabs.map((tab) => {
|
||||||
if (tab.value === 'voice') {
|
if (tab.value === 'voice') {
|
||||||
// Center voice button placeholder
|
// Center voice button
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
key="voice-placeholder"
|
key="voice-placeholder"
|
||||||
@@ -70,7 +74,28 @@ export const TabBar = () => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<IconButton
|
||||||
|
aria-label="voice command"
|
||||||
|
onClick={() => {
|
||||||
|
const voiceButton = document.querySelector('[aria-label="voice input"]') as HTMLButtonElement;
|
||||||
|
if (voiceButton) {
|
||||||
|
voiceButton.click();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
bgcolor: '#FF69B4',
|
||||||
|
color: 'white',
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
'&:hover': {
|
||||||
|
bgcolor: '#FF1493',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Mic />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -85,7 +110,7 @@ export const TabBar = () => {
|
|||||||
</BottomNavigation>
|
</BottomNavigation>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Voice Command Floating Button - Centered */}
|
{/* Voice Command Floating Button - Mobile Only */}
|
||||||
<Fab
|
<Fab
|
||||||
color="secondary"
|
color="secondary"
|
||||||
aria-label="voice command"
|
aria-label="voice command"
|
||||||
@@ -97,6 +122,7 @@ export const TabBar = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
|
display: { xs: 'flex', md: 'none' },
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: 40,
|
bottom: 40,
|
||||||
left: '50%',
|
left: '50%',
|
||||||
@@ -104,9 +130,9 @@ export const TabBar = () => {
|
|||||||
zIndex: 1100,
|
zIndex: 1100,
|
||||||
width: 56,
|
width: 56,
|
||||||
height: 56,
|
height: 56,
|
||||||
bgcolor: 'secondary.main',
|
bgcolor: '#FF69B4',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
bgcolor: 'secondary.dark',
|
bgcolor: '#FF1493',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ export function VoiceFloatingButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Floating button positioned in bottom-right - Hidden on mobile since we have TabBar center button */}
|
{/* Floating button positioned in bottom-right - Hidden on desktop since we have TabBar center button */}
|
||||||
<Tooltip title="Voice Command (Beta)" placement="left">
|
<Tooltip title="Voice Command (Beta)" placement="left">
|
||||||
<Fab
|
<Fab
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -335,7 +335,7 @@ export function VoiceFloatingButton() {
|
|||||||
bottom: 24,
|
bottom: 24,
|
||||||
right: 24,
|
right: 24,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
display: { xs: 'none', md: 'flex' }, // Hide on mobile (xs) and small screens, show on medium+
|
display: 'none', // Hidden - using TabBar center button instead
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MicIcon />
|
<MicIcon />
|
||||||
|
|||||||
Reference in New Issue
Block a user