Skip to content

Commit

Permalink
FI-2405: Create skeleton loader for main app (#440)
Browse files Browse the repository at this point in the history
* create header and footer skeletons

* add footer skeleton to test session

* create skeleton structure

* add classes to skeletons

* add skeleton for tree

* add session skeleton

* add main component back to router

* add footer use case

* revert gemfile

* revert demo

* remove footer case

---------

Co-authored-by: Alyssa Wang <awang@mitre.org>
  • Loading branch information
AlyssaWang and Alyssa Wang committed Jan 24, 2024
1 parent 58320f3 commit 54713d5
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 33 deletions.
5 changes: 3 additions & 2 deletions client/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { useAppStore } from '~/store/app';
import useStyles from './styles';
import icon from '~/images/inferno_icon.png';
import lightTheme from '~/styles/theme';
import CustomTooltip from '../_common/CustomTooltip';
import CustomTooltip from '~/components/_common/CustomTooltip';
import HeaderSkeleton from '~/components/Skeletons/HeaderSkeleton';

export interface HeaderProps {
suiteId?: string;
Expand Down Expand Up @@ -142,7 +143,7 @@ const Header: FC<HeaderProps> = ({
</Toolbar>
</AppBar>
) : (
<></>
<HeaderSkeleton />
);
};

Expand Down
32 changes: 32 additions & 0 deletions client/src/components/Skeletons/AppSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { FC } from 'react';
import { Box } from '@mui/material';
import lightTheme from '~/styles/theme';
// import { AppBar, Box, Skeleton, Toolbar } from '@mui/material';
import { useAppStore } from '~/store/app';
import useStyles from '~/components/TestSuite/styles';
import DrawerSkeleton from '~/components/Skeletons/DrawerSkeleton';
import TestSessionSkeleton from '~/components/Skeletons/TestSessionSkeleton';

const AppSkeleton: FC<Record<string, never>> = () => {
const { classes } = useStyles();
const windowIsSmall = useAppStore((state) => state.windowIsSmall);

return (
<Box className={classes.testSuiteMain}>
<DrawerSkeleton />
<main
style={{
overflow: 'auto',
width: '100%',
backgroundColor: lightTheme.palette.common.grayLightest,
}}
>
<Box className={classes.contentContainer} p={windowIsSmall ? 1 : 4}>
<TestSessionSkeleton />
</Box>
</main>
</Box>
);
};

export default AppSkeleton;
46 changes: 46 additions & 0 deletions client/src/components/Skeletons/DrawerSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { FC } from 'react';
import { Box, Divider, Drawer, Skeleton } from '@mui/material';
import { useAppStore } from '~/store/app';
import useStylesDrawer from '~/components/TestSuite/styles';
import useStylesTree from '~/components/TestSuite/TestSuiteTree/styles';

const DrawerSkeleton: FC<Record<string, never>> = () => {
const drawerClasses = useStylesDrawer().classes;
const treeClasses = useStylesTree().classes;
const windowIsSmall = useAppStore((state) => state.windowIsSmall);
const skeletonCount = 3;

const treeItemSkeleton = (
<Box display="flex" flexDirection="row" alignItems="center" mx={2} my={2}>
<Skeleton variant="circular" height="14px" width="16px" />
<Skeleton height={10} width={200} sx={{ ml: 1 }} />
</Box>
);

const nestedTreeItemSkeleton = <Box ml={3}>{treeItemSkeleton}</Box>;

const skeletonList = [];
for (let index = 0; index < skeletonCount; index++) {
skeletonList.push(
<div key={index}>
{treeItemSkeleton}
{nestedTreeItemSkeleton}
{nestedTreeItemSkeleton}
</div>
);
}

return windowIsSmall ? (
<></>
) : (
<Drawer variant="permanent" anchor="left" classes={{ paper: drawerClasses.drawerPaper }}>
<Skeleton variant="rounded" height={32} sx={{ m: 2 }} />
<Divider />
<Box className={treeClasses.testSuiteTreePanel} py={1}>
{skeletonList}
</Box>
</Drawer>
);
};

export default DrawerSkeleton;
49 changes: 49 additions & 0 deletions client/src/components/Skeletons/FooterSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { FC } from 'react';
import { Box, Skeleton } from '@mui/material';
import { useAppStore } from '~/store/app';
import useStyles from '~/components/Footer/styles';

const FooterSkeleton: FC<Record<string, never>> = () => {
const { classes } = useStyles();
const footerHeight = useAppStore((state) => state.footerHeight);
const windowIsSmall = useAppStore((state) => state.windowIsSmall);

return (
<footer
className={classes.footer}
style={{
minHeight: `${footerHeight}px`,
maxHeight: `${footerHeight}px`,
backgroundColor: 'white',
}}
>
<Box display="flex" flexDirection="row" justifyContent="space-between" width="100%">
<Box display="flex" alignItems="center" px={2}>
{windowIsSmall ? (
<Skeleton variant="rectangular" height={22} width={80} sx={{ mr: 1 }} />
) : (
<Skeleton
variant="rectangular"
height={40}
width={100}
style={{ marginRight: '16px' }}
/>
)}
<Box display="flex" flexDirection="column">
{!windowIsSmall && <Skeleton height={10} width={60} />}
<Skeleton height={20} width={100} />
</Box>
</Box>
<Box display="flex" alignItems="center" pr={windowIsSmall ? 1 : 3} py={2}>
{windowIsSmall ? (
<Skeleton variant="circular" height={20} width={20} />
) : (
<Skeleton variant="rounded" height={24} width={200} />
)}
</Box>
</Box>
</footer>
);
};

export default FooterSkeleton;
43 changes: 43 additions & 0 deletions client/src/components/Skeletons/HeaderSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { FC } from 'react';
import { AppBar, Box, Skeleton, Toolbar } from '@mui/material';
import { useAppStore } from '~/store/app';
import useStyles from '~/components/Header/styles';

const HeaderSkeleton: FC<Record<string, never>> = () => {
const { classes } = useStyles();
const headerHeight = useAppStore((state) => state.headerHeight);
const windowIsSmall = useAppStore((state) => state.windowIsSmall);

return (
<AppBar
color="default"
className={classes.appbar}
style={{
minHeight: `${headerHeight}px`, // For responsive screens
maxHeight: `${headerHeight}px`, // For responsive screens
}}
>
<Toolbar className={classes.toolbar}>
{/* Home button */}
<Skeleton variant="circular" height={44} width={44} />

{/* Header Text */}
<Box display="flex" flexDirection="column" flexGrow="1" alignSelf="center" py={0.5} pl={2}>
<Skeleton height={35} width="70%" />
<Skeleton height={15} width="60%" />
</Box>

{/* New Session button */}
<Box display="flex" style={windowIsSmall ? { marginRight: '-16px' } : {}}>
{windowIsSmall ? (
<Skeleton variant="circular" height={32} width={32} sx={{ mr: 1 }} />
) : (
<Skeleton variant="rounded" height={30} width={140} />
)}
</Box>
</Toolbar>
</AppBar>
);
};

export default HeaderSkeleton;
66 changes: 66 additions & 0 deletions client/src/components/Skeletons/TestSessionSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { FC } from 'react';
import { Box, Card, Divider, Skeleton } from '@mui/material';
import { useAppStore } from '~/store/app';
import useStyles from '~/components/TestSuite/TestSuiteDetails/styles';

const TestSessionSkeleton: FC<Record<string, never>> = () => {
const { classes } = useStyles();
const windowIsSmall = useAppStore((state) => state.windowIsSmall);
const skeletonCount = 3;

const expandableItemSkeleton = (
<>
<Divider />
<Box
sx={{
display: 'flex',
alignItems: 'center',
minHeight: '36.5px',
px: 2,
py: 1,
}}
>
<Skeleton variant="circular" height={24} width={24} />
<span className={classes.testGroupCardHeaderText}>
<Skeleton height={20} width={240} />
</span>
</Box>
</>
);

const skeletonList = [];
for (let index = 0; index < skeletonCount; index++) {
skeletonList.push(<div key={index}>{expandableItemSkeleton}</div>);
}

return (
<Card variant="outlined" sx={{ mb: 3 }}>
<Box className={classes.testGroupCardHeader}>
<Skeleton variant="circular" height={24} width={24} />
<span className={classes.testGroupCardHeaderText}>
<Skeleton height={20} width={240} />
</span>
<span className={classes.testGroupCardHeaderButton}>
{windowIsSmall ? (
<Skeleton variant="circular" height={24} width={24} sx={{ mr: 1 }} />
) : (
<Skeleton variant="rounded" height={30} width={140} />
)}
</span>
</Box>
<Divider />
<Box m={2.5}>
<Skeleton height={10} sx={{ my: 2 }} />
<Skeleton height={10} width="90%" sx={{ my: 2 }} />
<Skeleton height={10} width="95%" sx={{ my: 2 }} />
<Skeleton height={10} sx={{ my: 2 }} />
<Skeleton height={10} width="90%" sx={{ my: 2 }} />
<Skeleton height={10} width="95%" sx={{ my: 2 }} />
<Skeleton height={10} width="50%" sx={{ my: 2 }} />
</Box>
{skeletonList}
</Card>
);
};

export default TestSessionSkeleton;
72 changes: 72 additions & 0 deletions client/src/components/Skeletons/__tests__/Skeletons.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { render } from '@testing-library/react';
import { SnackbarProvider } from 'notistack';
import ThemeProvider from 'components/ThemeProvider';
import AppSkeleton from 'components/Skeletons/AppSkeleton';
import DrawerSkeleton from 'components/Skeletons/DrawerSkeleton';
import FooterSkeleton from 'components/Skeletons/FooterSkeleton';
import HeaderSkeleton from 'components/Skeletons/HeaderSkeleton';
import TestSessionSkeleton from 'components/Skeletons/TestSessionSkeleton';

describe('Skeleton Components', () => {
it('renders App Skeleton', () => {
render(
<MemoryRouter>
<ThemeProvider>
<SnackbarProvider>
<AppSkeleton />
</SnackbarProvider>
</ThemeProvider>
</MemoryRouter>
);
});

it('renders Drawer Skeleton', () => {
render(
<MemoryRouter>
<ThemeProvider>
<SnackbarProvider>
<DrawerSkeleton />
</SnackbarProvider>
</ThemeProvider>
</MemoryRouter>
);
});

it('renders Footer Skeleton', () => {
render(
<MemoryRouter>
<ThemeProvider>
<SnackbarProvider>
<FooterSkeleton />
</SnackbarProvider>
</ThemeProvider>
</MemoryRouter>
);
});

it('renders Header Skeleton', () => {
render(
<MemoryRouter>
<ThemeProvider>
<SnackbarProvider>
<HeaderSkeleton />
</SnackbarProvider>
</ThemeProvider>
</MemoryRouter>
);
});

it('renders TestSessionSkeleton Skeleton', () => {
render(
<MemoryRouter>
<ThemeProvider>
<SnackbarProvider>
<TestSessionSkeleton />
</SnackbarProvider>
</ThemeProvider>
</MemoryRouter>
);
});
});

0 comments on commit 54713d5

Please sign in to comment.