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',
|
||||
minHeight: '100vh',
|
||||
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 />}
|
||||
|
||||
{/* Mobile Header Bar */}
|
||||
{isMobile && (
|
||||
{/* Header Bar - Both Mobile and Desktop */}
|
||||
<Box
|
||||
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={{
|
||||
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',
|
||||
gap: 1,
|
||||
}}
|
||||
@@ -202,7 +98,6 @@ export const AppShell = ({ children }: AppShellProps) => {
|
||||
color={isConnected ? 'success' : 'default'}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
boxShadow: 1,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -216,13 +111,72 @@ export const AppShell = ({ children }: AppShellProps) => {
|
||||
color="primary"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
boxShadow: 1,
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
|
||||
<Container
|
||||
maxWidth={isTablet ? 'md' : 'lg'}
|
||||
@@ -230,13 +184,13 @@ export const AppShell = ({ children }: AppShellProps) => {
|
||||
flex: 1,
|
||||
px: { xs: 2, md: 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}
|
||||
</Container>
|
||||
|
||||
{isMobile && <TabBar />}
|
||||
<TabBar />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import { BottomNavigation, BottomNavigationAction, Paper, Fab, Box } from '@mui/material';
|
||||
import { BottomNavigation, BottomNavigationAction, Paper, Fab, Box, IconButton } from '@mui/material';
|
||||
import {
|
||||
Home,
|
||||
Timeline,
|
||||
@@ -33,10 +33,13 @@ export const TabBar = () => {
|
||||
aria-label="Primary navigation"
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: { xs: 0, md: 20 },
|
||||
left: { xs: 0, md: '50%' },
|
||||
right: { xs: 0, md: 'auto' },
|
||||
transform: { xs: 'none', md: 'translateX(-50%)' },
|
||||
width: { xs: '100%', md: '30%' },
|
||||
zIndex: 1000,
|
||||
borderRadius: { xs: 0, md: 2 },
|
||||
}}
|
||||
elevation={3}
|
||||
>
|
||||
@@ -50,6 +53,7 @@ export const TabBar = () => {
|
||||
showLabels
|
||||
sx={{
|
||||
height: 64,
|
||||
borderRadius: { xs: 0, md: 2 },
|
||||
'& .MuiBottomNavigationAction-root': {
|
||||
minWidth: 60,
|
||||
'&.Mui-selected': {
|
||||
@@ -60,7 +64,7 @@ export const TabBar = () => {
|
||||
>
|
||||
{tabs.map((tab) => {
|
||||
if (tab.value === 'voice') {
|
||||
// Center voice button placeholder
|
||||
// Center voice button
|
||||
return (
|
||||
<Box
|
||||
key="voice-placeholder"
|
||||
@@ -70,7 +74,28 @@ export const TabBar = () => {
|
||||
alignItems: '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 (
|
||||
@@ -85,7 +110,7 @@ export const TabBar = () => {
|
||||
</BottomNavigation>
|
||||
</Paper>
|
||||
|
||||
{/* Voice Command Floating Button - Centered */}
|
||||
{/* Voice Command Floating Button - Mobile Only */}
|
||||
<Fab
|
||||
color="secondary"
|
||||
aria-label="voice command"
|
||||
@@ -97,6 +122,7 @@ export const TabBar = () => {
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
display: { xs: 'flex', md: 'none' },
|
||||
position: 'fixed',
|
||||
bottom: 40,
|
||||
left: '50%',
|
||||
@@ -104,9 +130,9 @@ export const TabBar = () => {
|
||||
zIndex: 1100,
|
||||
width: 56,
|
||||
height: 56,
|
||||
bgcolor: 'secondary.main',
|
||||
bgcolor: '#FF69B4',
|
||||
'&:hover': {
|
||||
bgcolor: 'secondary.dark',
|
||||
bgcolor: '#FF1493',
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -323,7 +323,7 @@ export function VoiceFloatingButton() {
|
||||
|
||||
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">
|
||||
<Fab
|
||||
color="primary"
|
||||
@@ -335,7 +335,7 @@ export function VoiceFloatingButton() {
|
||||
bottom: 24,
|
||||
right: 24,
|
||||
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 />
|
||||
|
||||
Reference in New Issue
Block a user