feat: add animation for pages
This commit is contained in:
parent
50c7f4f76c
commit
5313c5c134
@ -99,6 +99,11 @@
|
|||||||
"**/.eslintrc.[j|t]s"
|
"**/.eslintrc.[j|t]s"
|
||||||
],
|
],
|
||||||
"optionalDependencies": false
|
"optionalDependencies": false
|
||||||
|
}],
|
||||||
|
"react/function-component-definition": [
|
||||||
|
"error", {
|
||||||
|
"namedComponents": "arrow-function",
|
||||||
|
"unnamedComponents": "arrow-function"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
offline_node_modules/@react-spring-animated-npm-9.7.3-4aff527e22-5151da4fa7.zip
generated
Normal file
BIN
offline_node_modules/@react-spring-animated-npm-9.7.3-4aff527e22-5151da4fa7.zip
generated
Normal file
Binary file not shown.
BIN
offline_node_modules/@react-spring-core-npm-9.7.3-00b8dd52b3-e28c05de84.zip
generated
Normal file
BIN
offline_node_modules/@react-spring-core-npm-9.7.3-00b8dd52b3-e28c05de84.zip
generated
Normal file
Binary file not shown.
BIN
offline_node_modules/@react-spring-shared-npm-9.7.3-cefcfdc40d-afb03ed28c.zip
generated
Normal file
BIN
offline_node_modules/@react-spring-shared-npm-9.7.3-cefcfdc40d-afb03ed28c.zip
generated
Normal file
Binary file not shown.
BIN
offline_node_modules/@react-spring-types-npm-9.7.3-f9de91ab6e-d645044f3c.zip
generated
Normal file
BIN
offline_node_modules/@react-spring-types-npm-9.7.3-f9de91ab6e-d645044f3c.zip
generated
Normal file
Binary file not shown.
BIN
offline_node_modules/@react-spring-web-npm-9.7.3-95390d075a-a5b4847a29.zip
generated
Normal file
BIN
offline_node_modules/@react-spring-web-npm-9.7.3-95390d075a-a5b4847a29.zip
generated
Normal file
Binary file not shown.
@ -36,6 +36,7 @@
|
|||||||
"@emotion/styled": "11.11.5",
|
"@emotion/styled": "11.11.5",
|
||||||
"@mui/icons-material": "^5.15.19",
|
"@mui/icons-material": "^5.15.19",
|
||||||
"@mui/material": "5.15.19",
|
"@mui/material": "5.15.19",
|
||||||
|
"@react-spring/web": "9.7.3",
|
||||||
"classnames": "2.5.1",
|
"classnames": "2.5.1",
|
||||||
"notistack": "3.0.1",
|
"notistack": "3.0.1",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
|
import CalendarMonthIcon from '@mui/icons-material/CalendarMonth';
|
||||||
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
|
import FormatListBulletedIcon from '@mui/icons-material/FormatListBulleted';
|
||||||
import PieChartIcon from '@mui/icons-material/PieChart';
|
import PieChartIcon from '@mui/icons-material/PieChart';
|
||||||
import { CircularProgress } from '@mui/material';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { createBrowserRouter, Navigate, RouterProvider as RRDRouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, Navigate, RouterProvider as RRDRouterProvider } from 'react-router-dom';
|
||||||
|
|
||||||
// todo lazy & access
|
|
||||||
import { AnalyzePage, AnalyzePageToolbar } from 'pages/AnalyzePage';
|
import { AnalyzePage, AnalyzePageToolbar } from 'pages/AnalyzePage';
|
||||||
import { ErrorPage, ErrorPageToolbar } from 'pages/ErrorPage';
|
import { ErrorPage, ErrorPageToolbar } from 'pages/ErrorPage';
|
||||||
import { PaymentsPage, PaymentsPageToolbar } from 'pages/PaymentsPage';
|
import { PaymentsPage, PaymentsPageToolbar } from 'pages/PaymentsPage';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import { SchedulePage, SchedulePageToolbar } from 'pages/SchedulePage';
|
import { SchedulePage, SchedulePageToolbar } from 'pages/SchedulePage';
|
||||||
|
|
||||||
@ -7,12 +7,15 @@ describe('Test SchedulePage', () => {
|
|||||||
<SchedulePage data-testid="schedule-page" />,
|
<SchedulePage data-testid="schedule-page" />,
|
||||||
);
|
);
|
||||||
|
|
||||||
test('should render correctly', () => {
|
test('should render correctly', async () => {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
const pageElem = screen.getByTestId('schedule-page');
|
await waitFor(() => {
|
||||||
expect(pageElem).toBeVisible();
|
const pageElem = screen.getByTestId('schedule-page');
|
||||||
expect(pageElem).toHaveTextContent('Schedule Page');
|
expect(pageElem).toBeVisible();
|
||||||
|
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
|
||||||
|
expect(pageElem).toHaveTextContent('Schedule Page');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
// todo fix eslint
|
|
||||||
|
|
||||||
import type { FC, PropsWithChildren } from 'react';
|
import type { FC, PropsWithChildren } from 'react';
|
||||||
|
|
||||||
// eslint-disable-next-line react/function-component-definition
|
|
||||||
const withProvider = <T,>(Provider: FC<PropsWithChildren>) => (Component: FC<T>) => (...params: T[]) => (
|
const withProvider = <T,>(Provider: FC<PropsWithChildren>) => (Component: FC<T>) => (...params: T[]) => (
|
||||||
<Provider>
|
<Provider>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { renderWithProvider } from '__spec__/custon-renders';
|
import { renderWithProvider } from '__spec__/custon-renders';
|
||||||
|
|
||||||
import PieChartIcon from '@mui/icons-material/PieChart';
|
import PieChartIcon from '@mui/icons-material/PieChart';
|
||||||
import { fireEvent, screen } from '@testing-library/react';
|
import { fireEvent, screen, waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
import AsideMenuLayout from './AsideMenuLayout.ui';
|
import AsideMenuLayout from './AsideMenuLayout.ui';
|
||||||
|
|
||||||
@ -41,10 +41,6 @@ describe('Test AsideMenuLayout', () => {
|
|||||||
<AsideMenuLayout
|
<AsideMenuLayout
|
||||||
Content={<Content />}
|
Content={<Content />}
|
||||||
data-testid="aside-menu-layout"
|
data-testid="aside-menu-layout"
|
||||||
DrawerSwitcherProps={{
|
|
||||||
// @ts-ignore
|
|
||||||
'data-testid': 'drawer-switcher',
|
|
||||||
}}
|
|
||||||
pages={pages}
|
pages={pages}
|
||||||
Toolbar={<Toolbar />}
|
Toolbar={<Toolbar />}
|
||||||
/>,
|
/>,
|
||||||
@ -58,18 +54,22 @@ describe('Test AsideMenuLayout', () => {
|
|||||||
expect(layoutElem).toBeVisible();
|
expect(layoutElem).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render toolbar', () => {
|
test('should render toolbar', async () => {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
const toolbarElem = screen.getByText('Test Toolbar');
|
await waitFor(() => {
|
||||||
expect(toolbarElem).toBeVisible();
|
const toolbarElem = screen.getByText('Test Toolbar');
|
||||||
|
expect(toolbarElem).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render content', () => {
|
test('should render content', async () => {
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
const toolbarElem = screen.getByText('Test Content');
|
await waitFor(() => {
|
||||||
expect(toolbarElem).toBeVisible();
|
const toolbarElem = screen.getByText('Test Content');
|
||||||
|
expect(toolbarElem).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should render navigation routes', () => {
|
test('should render navigation routes', () => {
|
||||||
|
|||||||
@ -1,104 +1,137 @@
|
|||||||
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
import { useMediaQuery } from '@mui/material';
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import CssBaseline from '@mui/material/CssBaseline';
|
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import List from '@mui/material/List';
|
import List from '@mui/material/List';
|
||||||
import { useState } from 'react';
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
import { animated, useTransition } from '@react-spring/web';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import AppBar from './components/AppBar';
|
import { DesktopAppBar, MobileAppBar } from './components/AppBar';
|
||||||
import Drawer from './components/Drawer';
|
import { AsideMenuContext, AsideMenuProvider } from './components/AsideMenuProvider';
|
||||||
import DrawerHeader from './components/DrawerMenu';
|
import { DesktopDrawer } from './components/DesktopDrawer';
|
||||||
import DrawerSwitcher from './components/DrawerSwitcher';
|
import { MenuItem } from './components/MenuItem';
|
||||||
import MenuItem from './components/MenuItem/MenuItem.ui';
|
import { MobileDrawer } from './components/MobileDrawer';
|
||||||
|
|
||||||
import type { MenuItemProps } from './components/MenuItem/MenuItem.ui';
|
import type { MenuItemProps } from './components/MenuItem/ui/MenuItem.ui';
|
||||||
import type { BoxProps } from '@mui/material/Box/Box';
|
import type { BoxProps } from '@mui/material/Box/Box';
|
||||||
import type {
|
import type { FC, ReactNode } from 'react';
|
||||||
FC,
|
|
||||||
ReactNode,
|
|
||||||
DetailedHTMLProps,
|
|
||||||
HTMLAttributes,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
const drawerWidth = 250;
|
export const DESKTOP_DRAWER_WIDTH = 250;
|
||||||
|
export const MOBILE_DRAWER_WIDTH = 250;
|
||||||
|
|
||||||
type AsideMenuLayoutProps = BoxProps & {
|
export type AsideMenuLayoutProps = BoxProps & {
|
||||||
pages: MenuItemProps['page'][];
|
pages: MenuItemProps['page'][];
|
||||||
Content: ReactNode;
|
Content: ReactNode;
|
||||||
Toolbar?: ReactNode;
|
Toolbar?: ReactNode;
|
||||||
DrawerSwitcherProps?: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
|
|
||||||
};
|
};
|
||||||
const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
|
const AsideMenuLayout: FC<AsideMenuLayoutProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
pages,
|
pages,
|
||||||
Content,
|
Content,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
DrawerSwitcherProps,
|
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(true);
|
const { desktopDrawerOpen, setMobileDrawerOpen } = useContext(AsideMenuContext);
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobileView = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
const handleDrawerOpen = () => {
|
const location = useLocation();
|
||||||
setIsOpen(true);
|
const transitions = useTransition(location, {
|
||||||
};
|
from: { opacity: 0 },
|
||||||
const handleDrawerClose = () => {
|
enter: { opacity: 1 },
|
||||||
setIsOpen(false);
|
leave: { opacity: 0 },
|
||||||
};
|
unique: true,
|
||||||
|
reset: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const AnimatedContent = (
|
||||||
|
<>
|
||||||
|
{transitions((styles) => (
|
||||||
|
<animated.div style={styles}>
|
||||||
|
{Content}
|
||||||
|
</animated.div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AnimatedToolBar = (
|
||||||
|
<>
|
||||||
|
{transitions((styles) => (
|
||||||
|
<animated.div style={styles}>
|
||||||
|
{Toolbar}
|
||||||
|
</animated.div>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMobileView) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column' }} {...restProps}>
|
||||||
|
{
|
||||||
|
Boolean(Toolbar) && (
|
||||||
|
<MobileAppBar position="static">
|
||||||
|
<MobileDrawer width={MOBILE_DRAWER_WIDTH}>
|
||||||
|
<List>
|
||||||
|
{pages.map((page) => (
|
||||||
|
<MenuItem
|
||||||
|
key={page.path}
|
||||||
|
page={page}
|
||||||
|
onClick={() => { setMobileDrawerOpen(false); }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</MobileDrawer>
|
||||||
|
|
||||||
|
{AnimatedToolBar}
|
||||||
|
</MobileAppBar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||||
|
{AnimatedContent}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex' }} {...restProps}>
|
<Box sx={{ display: 'flex' }} {...restProps}>
|
||||||
<CssBaseline />
|
<DesktopDrawer width={DESKTOP_DRAWER_WIDTH}>
|
||||||
|
|
||||||
<Drawer
|
|
||||||
drawerWidth={drawerWidth}
|
|
||||||
open={isOpen}
|
|
||||||
variant="permanent"
|
|
||||||
>
|
|
||||||
<DrawerHeader>
|
|
||||||
<DrawerSwitcher
|
|
||||||
open={isOpen}
|
|
||||||
onClick={isOpen ? handleDrawerClose : handleDrawerOpen}
|
|
||||||
{...DrawerSwitcherProps}
|
|
||||||
>
|
|
||||||
{ isOpen ? <ChevronLeftIcon /> : <MenuIcon /> }
|
|
||||||
</DrawerSwitcher>
|
|
||||||
</DrawerHeader>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<List>
|
<List>
|
||||||
{pages.map((page) => (
|
{pages.map((page) => (
|
||||||
<MenuItem key={page.path} isOpen={isOpen} page={page} />
|
desktopDrawerOpen
|
||||||
))}
|
? (
|
||||||
|
<MenuItem key={page.path} isOpen={desktopDrawerOpen} page={page} />
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Tooltip key={page.path} placement="right" title={page.title}>
|
||||||
|
<MenuItem isOpen={desktopDrawerOpen} page={page} />
|
||||||
|
</Tooltip>
|
||||||
|
)))}
|
||||||
</List>
|
</List>
|
||||||
</Drawer>
|
</DesktopDrawer>
|
||||||
|
|
||||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
{
|
{ Boolean(Toolbar) && (
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
<DesktopAppBar position="static">
|
||||||
Toolbar
|
{AnimatedToolBar}
|
||||||
? (
|
</DesktopAppBar>
|
||||||
<>
|
)}
|
||||||
<AppBar
|
|
||||||
drawerWidth={drawerWidth}
|
|
||||||
open={isOpen}
|
|
||||||
position="fixed"
|
|
||||||
>
|
|
||||||
{Toolbar}
|
|
||||||
</AppBar>
|
|
||||||
|
|
||||||
<DrawerHeader />
|
<Box component="main" sx={{ p: 3 }}>
|
||||||
</>
|
{AnimatedContent}
|
||||||
)
|
</Box>
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
{Content}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AsideMenuLayout;
|
const withProvider = (Component: FC<AsideMenuLayoutProps>) => (props: AsideMenuLayoutProps) => (
|
||||||
|
<AsideMenuProvider>
|
||||||
|
<Component {...props} />
|
||||||
|
</AsideMenuProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default withProvider(AsideMenuLayout);
|
||||||
|
|||||||
@ -1,30 +1,21 @@
|
|||||||
import MuiAppBar from '@mui/material/AppBar';
|
import MuiAppBar from '@mui/material/AppBar';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
import type { AppBarProps as MuiAppBarProps } from '@mui/material/AppBar/AppBar';
|
export const DesktopAppBar = styled(MuiAppBar)(
|
||||||
|
({ theme }) => ({
|
||||||
type AppBarProps = MuiAppBarProps & {
|
|
||||||
open?: boolean;
|
|
||||||
drawerWidth: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AppBar = styled(MuiAppBar, { shouldForwardProp: (prop) => !['open', 'drawerWidth'].includes(prop as string) })<AppBarProps>(
|
|
||||||
({ theme, open, drawerWidth }) => ({
|
|
||||||
transition: theme.transitions.create(['width', 'margin'], {
|
transition: theme.transitions.create(['width', 'margin'], {
|
||||||
easing: theme.transitions.easing.sharp,
|
easing: theme.transitions.easing.sharp,
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
duration: theme.transitions.duration.leavingScreen,
|
||||||
}),
|
}),
|
||||||
marginLeft: `calc(${theme.spacing(7)} + 1px)`,
|
display: 'flex',
|
||||||
width: `calc(100% - calc(${theme.spacing(7)} + 1px))`,
|
flexDirection: 'row',
|
||||||
...Boolean(open) && {
|
alignItems: 'center',
|
||||||
marginLeft: drawerWidth,
|
|
||||||
width: `calc(100% - ${drawerWidth}px)`,
|
|
||||||
transition: theme.transitions.create(['width', 'margin'], {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default AppBar;
|
export const MobileAppBar = styled(MuiAppBar)(() => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '0 24px',
|
||||||
|
}));
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export { default as AsideMenuProvider } from './ui/AsideMenuProvider.ui';
|
||||||
|
export { AsideMenuContext } from './model/context';
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
export type AsideMenuContextType = {
|
||||||
|
desktopDrawerOpen: boolean;
|
||||||
|
setDesktopDrawerOpen: (isOpen: boolean) => void;
|
||||||
|
mobileDrawerOpen: boolean;
|
||||||
|
setMobileDrawerOpen: (isOpen: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AsideMenuContext = createContext<AsideMenuContextType>({} as AsideMenuContextType);
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { AsideMenuContext, type AsideMenuContextType } from '../model/context';
|
||||||
|
|
||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
type AsideMenuProviderProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
const AsideMenuProvider: FC<AsideMenuProviderProps> = (props) => {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
// todo desktop from default value localstorage
|
||||||
|
const [desktopDrawerOpen, setDesktopDrawerOpen] = useState(true);
|
||||||
|
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
|
||||||
|
|
||||||
|
const value = useMemo<AsideMenuContextType>(() => ({
|
||||||
|
desktopDrawerOpen,
|
||||||
|
setDesktopDrawerOpen,
|
||||||
|
mobileDrawerOpen,
|
||||||
|
setMobileDrawerOpen,
|
||||||
|
}), [desktopDrawerOpen, mobileDrawerOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AsideMenuContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</AsideMenuContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AsideMenuProvider;
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export type { DesktopDrawerProps } from './ui/DesktopDrawer.ui';
|
||||||
|
export { default as DesktopDrawer } from './ui/DesktopDrawer.ui';
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
|
||||||
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { AsideMenuContext } from '../../AsideMenuProvider';
|
||||||
|
|
||||||
|
import Drawer from './components/Drawer';
|
||||||
|
import DrawerHeader from './components/DrawerMenu';
|
||||||
|
import DrawerSwitcher from './components/DrawerSwitcher';
|
||||||
|
|
||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
export type DesktopDrawerProps = {
|
||||||
|
width: number;
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DesktopDrawer: FC<DesktopDrawerProps> = (props) => {
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
children,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { desktopDrawerOpen, setDesktopDrawerOpen } = useContext(AsideMenuContext);
|
||||||
|
|
||||||
|
const handleDrawerOpen = () => {
|
||||||
|
setDesktopDrawerOpen(true);
|
||||||
|
};
|
||||||
|
const handleDrawerClose = () => {
|
||||||
|
setDesktopDrawerOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer open={desktopDrawerOpen} variant="permanent" width={width}>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerSwitcher
|
||||||
|
data-testid="drawer-switcher"
|
||||||
|
open={desktopDrawerOpen}
|
||||||
|
onClick={desktopDrawerOpen ? handleDrawerClose : handleDrawerOpen}
|
||||||
|
>
|
||||||
|
{ desktopDrawerOpen ? <ChevronLeftIcon /> : <MenuIcon /> }
|
||||||
|
</DrawerSwitcher>
|
||||||
|
</DrawerHeader>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DesktopDrawer;
|
||||||
@ -3,11 +3,11 @@ import { type CSSObject, styled, type Theme } from '@mui/material/styles';
|
|||||||
|
|
||||||
type DrawerProps = {
|
type DrawerProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
drawerWidth: number;
|
width: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const openedMixin = (theme: Theme, { drawerWidth }: Pick<DrawerProps, 'drawerWidth'>): CSSObject => ({
|
const openedMixin = (theme: Theme, { width }: Pick<DrawerProps, 'width'>): CSSObject => ({
|
||||||
width: drawerWidth,
|
width,
|
||||||
transition: theme.transitions.create('width', {
|
transition: theme.transitions.create('width', {
|
||||||
easing: theme.transitions.easing.sharp,
|
easing: theme.transitions.easing.sharp,
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
duration: theme.transitions.duration.enteringScreen,
|
||||||
@ -24,15 +24,15 @@ const closedMixin = (theme: Theme): CSSObject => ({
|
|||||||
width: `calc(${theme.spacing(8)} + 1px)`,
|
width: `calc(${theme.spacing(8)} + 1px)`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => !['open', 'drawerWidth'].includes(prop as string) })<DrawerProps>(
|
const Drawer = styled(MuiDrawer, { shouldForwardProp: (prop) => !['open', 'width'].includes(prop as string) })<DrawerProps>(
|
||||||
({ theme, open, drawerWidth }) => ({
|
({ theme, open, width }) => ({
|
||||||
width: drawerWidth,
|
width,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
...open && {
|
...open && {
|
||||||
...openedMixin(theme, { drawerWidth }),
|
...openedMixin(theme, { width }),
|
||||||
'& .MuiDrawer-paper': openedMixin(theme, { drawerWidth }),
|
'& .MuiDrawer-paper': openedMixin(theme, { width }),
|
||||||
},
|
},
|
||||||
...!open && {
|
...!open && {
|
||||||
...closedMixin(theme),
|
...closedMixin(theme),
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export { default as MenuItem } from './ui/MenuItem.ui';
|
||||||
@ -4,10 +4,11 @@ import ListItemIcon from '@mui/material/ListItemIcon';
|
|||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import { styled, useTheme } from '@mui/material/styles';
|
import { styled, useTheme } from '@mui/material/styles';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
import { NavLink as RRDNavLink } from 'react-router-dom';
|
import { NavLink as RRDNavLink } from 'react-router-dom';
|
||||||
|
|
||||||
import type { ListItemProps } from '@mui/material/ListItem/ListItem';
|
import type { ListItemProps } from '@mui/material/ListItem/ListItem';
|
||||||
import type { FC, ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import type { NavLinkProps } from 'react-router-dom';
|
import type { NavLinkProps } from 'react-router-dom';
|
||||||
|
|
||||||
const NavLink = styled(RRDNavLink)(({ theme }) => ({
|
const NavLink = styled(RRDNavLink)(({ theme }) => ({
|
||||||
@ -26,7 +27,7 @@ export type MenuItemProps = ListItemProps & {
|
|||||||
NavLinkProps?: NavLinkProps;
|
NavLinkProps?: NavLinkProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItem: FC<MenuItemProps> = (props) => {
|
const MenuItem = forwardRef<HTMLLIElement, MenuItemProps>((props, ref) => {
|
||||||
const {
|
const {
|
||||||
page,
|
page,
|
||||||
isOpen = true,
|
isOpen = true,
|
||||||
@ -36,7 +37,7 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem disablePadding {...restProps}>
|
<ListItem ref={ref} disablePadding {...restProps}>
|
||||||
<NavLink to={page.path} {...NavLinkProps}>
|
<NavLink to={page.path} {...NavLinkProps}>
|
||||||
{({ isActive }) => (
|
{({ isActive }) => (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
@ -72,6 +73,6 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default MenuItem;
|
export default MenuItem;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export { default as MobileDrawer } from './ui/MobileDrawer.ui';
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import MuiDrawer from '@mui/material/Drawer';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { AsideMenuContext } from '../../AsideMenuProvider';
|
||||||
|
|
||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
type MobileDrawerProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
width: number;
|
||||||
|
|
||||||
|
onOpen?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MobileDrawer: FC<MobileDrawerProps> = (props) => {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
width,
|
||||||
|
onOpen,
|
||||||
|
onClose,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { mobileDrawerOpen, setMobileDrawerOpen } = useContext(AsideMenuContext);
|
||||||
|
|
||||||
|
const handleDrawerOpen = () => {
|
||||||
|
setMobileDrawerOpen(true);
|
||||||
|
onOpen?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrawerClose = () => {
|
||||||
|
setMobileDrawerOpen(false);
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IconButton onClick={handleDrawerOpen}>
|
||||||
|
<MenuIcon style={{ fill: '#fff' }} />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<MuiDrawer
|
||||||
|
open={mobileDrawerOpen}
|
||||||
|
sx={{ width }}
|
||||||
|
onClose={handleDrawerClose}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MuiDrawer>
|
||||||
|
</>
|
||||||
|
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MobileDrawer;
|
||||||
59
yarn.lock
59
yarn.lock
@ -4496,6 +4496,64 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-spring/animated@npm:~9.7.3":
|
||||||
|
version: 9.7.3
|
||||||
|
resolution: "@react-spring/animated@npm:9.7.3"
|
||||||
|
dependencies:
|
||||||
|
"@react-spring/shared": "npm:~9.7.3"
|
||||||
|
"@react-spring/types": "npm:~9.7.3"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: 10c0/5151da4fa7da010bb2edbee05871aa7a4aea8763fe617389d17605810aa0dd817374205e5fb3930b650f4a7f25fcdf23205fdfb7365686ff75888bdfd0b39839
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-spring/core@npm:~9.7.3":
|
||||||
|
version: 9.7.3
|
||||||
|
resolution: "@react-spring/core@npm:9.7.3"
|
||||||
|
dependencies:
|
||||||
|
"@react-spring/animated": "npm:~9.7.3"
|
||||||
|
"@react-spring/shared": "npm:~9.7.3"
|
||||||
|
"@react-spring/types": "npm:~9.7.3"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: 10c0/e28c05de8435bf2eaf8481f8acdf093909d4be9881a9a854c51dfac7c2d5562088d0fb2ce04e2f07e1b3bf621d8da3ab57bf6fedb4fdc954e3aa263bc1e393af
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-spring/shared@npm:~9.7.3":
|
||||||
|
version: 9.7.3
|
||||||
|
resolution: "@react-spring/shared@npm:9.7.3"
|
||||||
|
dependencies:
|
||||||
|
"@react-spring/types": "npm:~9.7.3"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: 10c0/afb03ed28ccf62efa4012e531c3659999bb364d1e0eb0fb729b962f9bd21a0b772a2d98e862062c9c32c06edf72327afcc45984d9eb22fdd961706b6ddf6950d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-spring/types@npm:~9.7.3":
|
||||||
|
version: 9.7.3
|
||||||
|
resolution: "@react-spring/types@npm:9.7.3"
|
||||||
|
checksum: 10c0/d645044f3cc9ceb7c4f6c4d061aaf6660018568a1553d05638f56b3328c5f91597ee4118334abe22fc8f07f5ee02f054340170c1d52e11b3faea22888b5170d4
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@react-spring/web@npm:9.7.3":
|
||||||
|
version: 9.7.3
|
||||||
|
resolution: "@react-spring/web@npm:9.7.3"
|
||||||
|
dependencies:
|
||||||
|
"@react-spring/animated": "npm:~9.7.3"
|
||||||
|
"@react-spring/core": "npm:~9.7.3"
|
||||||
|
"@react-spring/shared": "npm:~9.7.3"
|
||||||
|
"@react-spring/types": "npm:~9.7.3"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
checksum: 10c0/a5b4847a2921a29a3e8ce569f4951abeb268b6e8eb230f8c49d98709216b2b3a23ba1e58628c51c9eaa0bd20d83f9d7b8f8f03df98215e933de4c66c39e17fe1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@remix-run/router@npm:1.16.1":
|
"@remix-run/router@npm:1.16.1":
|
||||||
version: 1.16.1
|
version: 1.16.1
|
||||||
resolution: "@remix-run/router@npm:1.16.1"
|
resolution: "@remix-run/router@npm:1.16.1"
|
||||||
@ -12874,6 +12932,7 @@ __metadata:
|
|||||||
"@emotion/styled": "npm:11.11.5"
|
"@emotion/styled": "npm:11.11.5"
|
||||||
"@mui/icons-material": "npm:^5.15.19"
|
"@mui/icons-material": "npm:^5.15.19"
|
||||||
"@mui/material": "npm:5.15.19"
|
"@mui/material": "npm:5.15.19"
|
||||||
|
"@react-spring/web": "npm:9.7.3"
|
||||||
"@testing-library/jest-dom": "npm:6.4.2"
|
"@testing-library/jest-dom": "npm:6.4.2"
|
||||||
"@testing-library/react": "npm:14.3.1"
|
"@testing-library/react": "npm:14.3.1"
|
||||||
"@testing-library/user-event": "npm:^14.5.2"
|
"@testing-library/user-event": "npm:^14.5.2"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user