Skip to content

Commit 36c27e3

Browse files
style: refine bottom navigation styles
1 parent 0ccdf47 commit 36c27e3

File tree

3 files changed

+142
-63
lines changed

3 files changed

+142
-63
lines changed

src/pages/sidepanel/SidePanel.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
1-
import { useEffect, useState } from 'react';
2-
import { Route, Routes, useNavigate, useLocation } from 'react-router-dom';
3-
import BottomNavigation from '@root/src/pages/sidepanel/components/BottomNavigation';
4-
5-
// styles
6-
import { Box, CircularProgress } from '@mui/material';
71
import '@pages/sidepanel/SidePanel.css';
82

9-
// storage
10-
import tagConfigStore from '@src/shared/storages/tagConfigStorage';
11-
import entityStorage from '@src/shared/storages/entityStorage';
12-
import { TagConfigModel, TagConfigPathforaCandidates } from '@root/src/shared/models/tagConfigModel';
3+
import {
4+
useEffect,
5+
useState,
6+
} from 'react';
7+
8+
import {
9+
Route,
10+
Routes,
11+
useLocation,
12+
useNavigate,
13+
} from 'react-router-dom';
1314

14-
// components
15+
import {
16+
Box,
17+
CircularProgress,
18+
} from '@mui/material';
19+
import BottomNavigation from '@root/src/pages/sidepanel/components/BottomNavigation';
20+
import Configuration from '@root/src/pages/sidepanel/sections/Configuration';
1521
import Debugger from '@root/src/pages/sidepanel/sections/Debugger';
16-
import Profile from '@root/src/pages/sidepanel/sections/Profile';
1722
import Personalization from '@root/src/pages/sidepanel/sections/Personalization';
18-
import Configuration from '@root/src/pages/sidepanel/sections/Configuration';
23+
import Profile from '@root/src/pages/sidepanel/sections/Profile';
24+
import {
25+
TagConfigModel,
26+
TagConfigPathforaCandidates,
27+
} from '@root/src/shared/models/tagConfigModel';
1928
import { EmitLog } from '@src/shared/components/EmitLog';
29+
import entityStorage from '@src/shared/storages/entityStorage';
30+
import tagConfigStore from '@src/shared/storages/tagConfigStorage';
2031

2132
interface SidePanelProps {
2233
key: any;
@@ -244,7 +255,6 @@ const SidePanel: React.FC<SidePanelProps> = ({ key, isEnabled }) => {
244255
</Box>
245256
<BottomNavigation
246257
value={activePath}
247-
tagIsInstalled={tagIsInstalled}
248258
onChange={newValue => handleNavigation(newValue)}
249259
/>
250260
</>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React from 'react';
2+
3+
import { describe, expect, it, vi } from 'vitest';
4+
5+
import { createTheme, ThemeProvider } from '@mui/material/styles';
6+
import { appPalette } from '@root/src/theme/palette';
7+
import { fireEvent, render, screen } from '@testing-library/react';
8+
9+
import { BottomNav } from './BottomNavigation';
10+
11+
const theme = createTheme({ palette: appPalette });
12+
13+
const renderWithTheme = (component: React.ReactElement) => {
14+
return render(<ThemeProvider theme={theme}>{component}</ThemeProvider>);
15+
};
16+
17+
describe('BottomNav', () => {
18+
const mockOnChange = vi.fn();
19+
const defaultProps = {
20+
value: '/',
21+
onChange: mockOnChange,
22+
};
23+
24+
beforeEach(() => {
25+
mockOnChange.mockClear();
26+
});
27+
28+
it('renders all navigation sections', () => {
29+
renderWithTheme(<BottomNav {...defaultProps} />);
30+
31+
expect(screen.getByLabelText('Debug')).toBeInTheDocument();
32+
expect(screen.getByLabelText('Profile')).toBeInTheDocument();
33+
expect(screen.getByLabelText('Personalization')).toBeInTheDocument();
34+
});
35+
36+
it('calls onChange when navigation action is clicked', () => {
37+
renderWithTheme(<BottomNav {...defaultProps} />);
38+
39+
const profileButton = screen.getByLabelText('Profile');
40+
fireEvent.click(profileButton);
41+
42+
expect(mockOnChange).toHaveBeenCalledWith('/profile');
43+
});
44+
});

src/pages/sidepanel/components/BottomNavigation.tsx

Lines changed: 74 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,85 @@
11
import React from 'react';
2-
import BottomNavigation from '@mui/material/BottomNavigation';
3-
import BottomNavigationAction from '@mui/material/BottomNavigationAction';
4-
import Plumbing from '@mui/icons-material/Plumbing';
5-
import Person from '@mui/icons-material/Person';
6-
import Brush from '@mui/icons-material/Brush';
2+
3+
import { AutoFixHighOutlined, Person, PestControlOutlined } from '@mui/icons-material';
4+
import { BottomNavigation, BottomNavigationAction } from '@mui/material';
5+
import { styled } from '@mui/material/styles';
6+
import { appColors } from '@root/src/theme/palette';
77

88
interface BottomNavProps {
99
value: string;
10-
tagIsInstalled: boolean;
1110
onChange: (newValue: string) => void;
1211
}
1312

14-
const BottomNav: React.FC<BottomNavProps> = ({ value, tagIsInstalled, onChange }) => {
13+
interface NavigationSection {
14+
route: string;
15+
icon: React.ReactElement;
16+
ariaLabel: string;
17+
}
18+
19+
const StyledBottomNavigation = styled(BottomNavigation)(({ theme }) => ({
20+
position: 'fixed',
21+
bottom: 0,
22+
width: '100%',
23+
height: 'auto',
24+
display: 'flex',
25+
justifyContent: 'space-between',
26+
padding: theme.spacing(1.5, 2),
27+
cursor: 'default',
28+
transition: 'none',
29+
'&:hover': {
30+
boxShadow: 'none',
31+
transform: 'none',
32+
},
33+
}));
34+
35+
const StyledBottomNavigationAction = styled(BottomNavigationAction)<{
36+
isSelected: boolean;
37+
}>(({ theme, isSelected }) => ({
38+
flex: 'none',
39+
minWidth: 0,
40+
paddingBlock: theme.spacing(1.5),
41+
backgroundColor: isSelected ? appColors.common.colors.accent : 'transparent',
42+
borderRadius: theme.spacing(0.5),
43+
transition: 'none',
44+
'&.Mui-selected': {
45+
color: appColors.common.white,
46+
},
47+
}));
48+
49+
const navigationSections: NavigationSection[] = [
50+
{
51+
route: '/',
52+
icon: <PestControlOutlined />,
53+
ariaLabel: 'Debug',
54+
},
55+
{
56+
route: '/profile',
57+
icon: <Person />,
58+
ariaLabel: 'Profile',
59+
},
60+
{
61+
route: '/personalization',
62+
icon: <AutoFixHighOutlined />,
63+
ariaLabel: 'Personalization',
64+
},
65+
];
66+
67+
export const BottomNav = ({ value, onChange }: BottomNavProps): JSX.Element => {
1568
return (
16-
<BottomNavigation
17-
showLabels
18-
value={value}
19-
onChange={(_, newValue) => onChange(newValue)}
20-
sx={{
21-
position: 'fixed',
22-
bottom: 0,
23-
width: '100%',
24-
}}>
25-
<BottomNavigationAction
26-
label="Debugger"
27-
value="/"
28-
icon={<Plumbing />}
29-
sx={{
30-
'&.Mui-selected': {
31-
color: 'secondary.main',
32-
},
33-
}}
34-
/>
35-
<BottomNavigationAction
36-
label="Profile"
37-
value="/profile"
38-
disabled={!tagIsInstalled}
39-
icon={<Person />}
40-
sx={{
41-
'&.Mui-selected': {
42-
color: 'secondary.main',
43-
},
44-
}}
45-
/>
46-
<BottomNavigationAction
47-
label="Personalization"
48-
value="/personalization"
49-
disabled={!tagIsInstalled}
50-
icon={<Brush />}
51-
sx={{
52-
'&.Mui-selected': {
53-
color: 'secondary.main',
54-
},
55-
}}
56-
/>
57-
</BottomNavigation>
69+
<StyledBottomNavigation value={value} onChange={(_, newValue) => onChange(newValue)}>
70+
{navigationSections.map(section => {
71+
const isSelected = value === section.route;
72+
return (
73+
<StyledBottomNavigationAction
74+
key={section.route}
75+
value={section.route}
76+
icon={section.icon}
77+
isSelected={isSelected}
78+
aria-label={section.ariaLabel}
79+
/>
80+
);
81+
})}
82+
</StyledBottomNavigation>
5883
);
5984
};
6085

0 commit comments

Comments
 (0)