Skip to content

Commit

Permalink
Handle dynamic sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
abailly-akamai committed Jan 6, 2025
1 parent 8372aa2 commit 9464acf
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 93 deletions.
164 changes: 84 additions & 80 deletions packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { makeStyles } from 'tss-react/mui';
import Logo from 'src/assets/logo/akamai-logo.svg';
import { MainContentBanner } from 'src/components/MainContentBanner';
import { MaintenanceScreen } from 'src/components/MaintenanceScreen';
import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/constants';
import { SideMenu } from 'src/components/PrimaryNav/SideMenu';
import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/SideMenu';
import { SuspenseLoader } from 'src/components/SuspenseLoader';
import { useDialogContext } from 'src/context/useDialogContext';
import { Footer } from 'src/features/Footer';
Expand Down Expand Up @@ -303,90 +303,94 @@ export const MainContent = () => {
openSideMenu={() => toggleMenu(true)}
username={username}
/>
<SideMenu
closeMenu={() => toggleMenu(false)}
collapse={desktopMenuIsOpen || false}
desktopMenuToggle={desktopMenuToggle}
open={menuIsOpen}
/>
<div
className={cx(classes.content, {
[classes.fullWidthContent]:
desktopMenuIsOpen ||
(desktopMenuIsOpen && desktopMenuIsOpen === true),
})}
>
<MainContentBanner />
<main
className={classes.cmrWrapper}
id="main-content"
role="main"
<Box display="flex">
<Box height="100vh" position="sticky" top="0" zIndex={10000}>
<SideMenu
closeMenu={() => toggleMenu(false)}
collapse={desktopMenuIsOpen || false}
desktopMenuToggle={desktopMenuToggle}
open={menuIsOpen}
/>
</Box>
<div
className={cx(classes.content, {
[classes.fullWidthContent]:
desktopMenuIsOpen ||
(desktopMenuIsOpen && desktopMenuIsOpen === true),
})}
>
<Grid className={classes.grid} container spacing={0}>
<Grid className={cx(classes.switchWrapper, 'p0')}>
<GlobalNotifications />
<React.Suspense fallback={<SuspenseLoader />}>
<Switch>
<Route component={LinodesRoutes} path="/linodes" />
{isPlacementGroupsEnabled && (
<MainContentBanner />
<main
className={classes.cmrWrapper}
id="main-content"
role="main"
>
<Grid className={classes.grid} container spacing={0}>
<Grid className={cx(classes.switchWrapper, 'p0')}>
<GlobalNotifications />
<React.Suspense fallback={<SuspenseLoader />}>
<Switch>
<Route component={LinodesRoutes} path="/linodes" />
{isPlacementGroupsEnabled && (
<Route
component={PlacementGroups}
path="/placement-groups"
/>
)}
<Route
component={NodeBalancers}
path="/nodebalancers"
/>
<Route component={Managed} path="/managed" />
<Route component={Longview} path="/longview" />
<Route component={Images} path="/images" />
<Route
component={PlacementGroups}
path="/placement-groups"
component={StackScripts}
path="/stackscripts"
/>
)}
<Route
component={NodeBalancers}
path="/nodebalancers"
/>
<Route component={Managed} path="/managed" />
<Route component={Longview} path="/longview" />
<Route component={Images} path="/images" />
<Route
component={StackScripts}
path="/stackscripts"
/>
<Route
component={ObjectStorage}
path="/object-storage"
/>
<Route component={Kubernetes} path="/kubernetes" />
{isIAMEnabled && (
<Route component={IAM} path="/iam" />
)}
<Route component={Account} path="/account" />
<Route component={Profile} path="/profile" />
<Route component={Help} path="/support" />
<Route component={SearchLanding} path="/search" />
<Route component={EventsLanding} path="/events" />
<Route component={Firewalls} path="/firewalls" />
{isDatabasesEnabled && (
<Route component={Databases} path="/databases" />
)}
<Route component={VPC} path="/vpcs" />
{isACLPEnabled && (
<Route component={CloudPulse} path="/monitor" />
)}
<Redirect exact from="/" to={defaultRoot} />
{/** We don't want to break any bookmarks. This can probably be removed eventually. */}
<Redirect from="/dashboard" to={defaultRoot} />
{/**
* This is the catch all routes that allows TanStack Router to take over.
* When a route is not found here, it will be handled by the migration router, which in turns handles the NotFound component.
* It is currently set to the migration router in order to incrementally migrate the app to the new routing.
* This is a temporary solution until we are ready to fully migrate to TanStack Router.
*/}
<Route path="*">
<RouterProvider
context={{ queryClient }}
router={migrationRouter as AnyRouter}
<Route
component={ObjectStorage}
path="/object-storage"
/>
</Route>
</Switch>
</React.Suspense>
<Route component={Kubernetes} path="/kubernetes" />
{isIAMEnabled && (
<Route component={IAM} path="/iam" />
)}
<Route component={Account} path="/account" />
<Route component={Profile} path="/profile" />
<Route component={Help} path="/support" />
<Route component={SearchLanding} path="/search" />
<Route component={EventsLanding} path="/events" />
<Route component={Firewalls} path="/firewalls" />
{isDatabasesEnabled && (
<Route component={Databases} path="/databases" />
)}
<Route component={VPC} path="/vpcs" />
{isACLPEnabled && (
<Route component={CloudPulse} path="/monitor" />
)}
<Redirect exact from="/" to={defaultRoot} />
{/** We don't want to break any bookmarks. This can probably be removed eventually. */}
<Redirect from="/dashboard" to={defaultRoot} />
{/**
* This is the catch all routes that allows TanStack Router to take over.
* When a route is not found here, it will be handled by the migration router, which in turns handles the NotFound component.
* It is currently set to the migration router in order to incrementally migrate the app to the new routing.
* This is a temporary solution until we are ready to fully migrate to TanStack Router.
*/}
<Route path="*">
<RouterProvider
context={{ queryClient }}
router={migrationRouter as AnyRouter}
/>
</Route>
</Switch>
</React.Suspense>
</Grid>
</Grid>
</Grid>
</main>
</div>
</main>
</div>
</Box>
</NotificationProvider>
<Footer />
</ComplianceUpdateProvider>
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/Root.styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { makeStyles } from 'tss-react/mui';

import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/SideMenu';
import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/constants';

import type { Theme } from '@mui/material/styles';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import Grid from '@mui/material/Unstable_Grid2';
import { Link } from 'react-router-dom';

import AkamaiLogo from 'src/assets/logo/akamai-logo.svg';
import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/SideMenu';
import { SIDEBAR_WIDTH } from 'src/components/PrimaryNav/constants';
import { FOOTER_HEIGHT } from 'src/features/Footer';

export const StyledGrid = styled(Grid, {
label: 'StyledGrid',
Expand Down Expand Up @@ -113,6 +114,7 @@ export const StyledMenuGrid = styled(Grid, {
flex: '1 1 0%',
overflowX: 'hidden',
overflowY: 'auto',
paddingBottom: FOOTER_HEIGHT,
scrollbarColor: `${theme.color.grey4} transparent `,
[theme.breakpoints.down('md')]: {
borderRight: `1px solid ${theme.tokens.border.Normal}`,
Expand Down
3 changes: 2 additions & 1 deletion packages/manager/src/components/PrimaryNav/SideMenu.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import React from 'react';

import { renderWithTheme, resizeScreenSize } from 'src/utilities/testHelpers';

import { SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_WIDTH, SideMenu } from './SideMenu';
import { SIDEBAR_COLLAPSED_WIDTH, SIDEBAR_WIDTH } from './constants';
import { SideMenu } from './SideMenu';

// Mock PrimaryNav component
vi.mock('./PrimaryNav', () => {
Expand Down
21 changes: 11 additions & 10 deletions packages/manager/src/components/PrimaryNav/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { styled } from '@mui/material/styles';
import * as React from 'react';

import { Hidden } from 'src/components/Hidden';
import { FOOTER_HEIGHT } from 'src/features/Footer';
import {
SIDEBAR_COLLAPSED_WIDTH,
SIDEBAR_WIDTH,
} from 'src/components/PrimaryNav/constants';
import { TOPMENU_HEIGHT } from 'src/features/TopMenu/TopMenu';

import PrimaryNav from './PrimaryNav';

export const SIDEBAR_WIDTH = 232;
export const SIDEBAR_COLLAPSED_WIDTH = 52;
import { useScrollingUtils } from './utils';

export interface SideMenuProps {
/**
Expand All @@ -34,7 +35,7 @@ export interface SideMenuProps {
*/
export const SideMenu = (props: SideMenuProps) => {
const { closeMenu, collapse, desktopMenuToggle, open } = props;

const { isAtTop } = useScrollingUtils();
return (
<>
<Hidden mdUp>
Expand All @@ -58,6 +59,7 @@ export const SideMenu = (props: SideMenuProps) => {
<StyledDrawer
collapse={collapse}
data-testid="side-menu"
isAtTop={isAtTop}
open
variant="permanent"
>
Expand All @@ -74,18 +76,17 @@ export const SideMenu = (props: SideMenuProps) => {

const StyledDrawer = styled(Drawer, {
label: 'StyledSideMenuDrawer',
shouldForwardProp: (prop) => prop !== 'collapse',
})<{ collapse?: boolean }>(({ theme, ...props }) => ({
shouldForwardProp: (prop) => prop !== 'collapse' && prop !== 'isAtTop',
})<{ collapse?: boolean; isAtTop?: boolean }>(({ theme, ...props }) => ({
'& .MuiDrawer-paper': {
backgroundColor: theme.tokens.sideNavigation.Background.Default,
boxShadow: 'none',
height: '100%',
height: props.isAtTop ? `calc(100% - ${TOPMENU_HEIGHT}px)` : '100%',
left: 'inherit',
overflowX: 'hidden',
position: 'absolute',
[theme.breakpoints.up('md')]: {
borderRight: `1px solid ${theme.tokens.sideNavigation.Border}`,
height: `calc(100% - ${TOPMENU_HEIGHT}px - ${FOOTER_HEIGHT}px)`,
top: TOPMENU_HEIGHT,
},
transform: 'none',
transition: 'width linear .1s',
Expand Down
2 changes: 2 additions & 0 deletions packages/manager/src/components/PrimaryNav/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const SIDEBAR_WIDTH = 232;
export const SIDEBAR_COLLAPSED_WIDTH = 52;
47 changes: 47 additions & 0 deletions packages/manager/src/components/PrimaryNav/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import React from 'react';
import { debounce } from 'throttle-debounce';

import { isPathOneOf } from 'src/utilities/routing/isPathOneOf';

export const linkIsActive = (
Expand All @@ -19,3 +22,47 @@ export const linkIsActive = (

return isPathOneOf([href, ...activeLinks], locationPathname);
};

/**
* A hook to provide scrolling information relative to the viewport
*
* documentHeight - the height of the document
* isAtBottom - whether the user is at the bottom of the viewport
* isAtTop - whether the user is at the top of the viewport
* scrollTop - the current scroll position
* windowHeight - the height of the window
*
* @returns {boolean}
*/
export const useScrollingUtils = () => {
const [isAtBottom, setIsAtBottom] = React.useState(false);
const [isAtTop, setIsAtTop] = React.useState(false);
let windowHeight = 0;
let documentHeight = 0;
let scrollTop = 0;

const checkIfBottom = React.useCallback(
debounce(10, () => {
windowHeight = window.innerHeight;
documentHeight = document.documentElement.scrollHeight;
scrollTop = window.scrollY;
const bottom = Math.ceil(windowHeight + scrollTop) >= documentHeight;
const top = scrollTop === 0;

setIsAtBottom(bottom);
setIsAtTop(top);
}),
[]
);

React.useEffect(() => {
checkIfBottom();
const onScroll = () => requestAnimationFrame(checkIfBottom);
document.body.onscroll = onScroll;
return () => {
document.body.onscroll = null;
};
}, [checkIfBottom]);

return { documentHeight, isAtBottom, isAtTop, scrollTop, windowHeight };
};

0 comments on commit 9464acf

Please sign in to comment.