style(FE/layout): Fix app scroll behaviour and sidenav footer section inconsistency (#3426)

This commit is contained in:
Kanishka Chowdhury 2023-09-06 13:00:20 +05:30 committed by GitHub
parent 32a55f3c4f
commit 9aa72f847c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 151 additions and 162 deletions

View File

@ -25,7 +25,7 @@ import {
} from 'types/actions/app'; } from 'types/actions/app';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { ChildrenContainer, Layout } from './styles'; import { ChildrenContainer, Layout, LayoutContent } from './styles';
import { getRouteKey } from './utils'; import { getRouteKey } from './utils';
function AppLayout(props: AppLayoutProps): JSX.Element { function AppLayout(props: AppLayoutProps): JSX.Element {
@ -240,12 +240,12 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
{isToDisplayLayout && <Header />} {isToDisplayLayout && <Header />}
<Layout> <Layout>
{isToDisplayLayout && <SideNav />} {isToDisplayLayout && <SideNav />}
<Layout.Content> <LayoutContent>
<ChildrenContainer> <ChildrenContainer>
{isToDisplayLayout && <TopNav />} {isToDisplayLayout && <TopNav />}
{children} {children}
</ChildrenContainer> </ChildrenContainer>
</Layout.Content> </LayoutContent>
</Layout> </Layout>
</Layout> </Layout>
); );

View File

@ -7,9 +7,14 @@ export const Layout = styled(LayoutComponent)`
position: relative; position: relative;
min-height: calc(100vh - 4rem); min-height: calc(100vh - 4rem);
overflow: hidden; overflow: hidden;
height: 100%;
} }
`; `;
export const LayoutContent = styled(LayoutComponent.Content)`
overflow-y: auto;
`;
export const ChildrenContainer = styled.div` export const ChildrenContainer = styled.div`
margin: 0 1rem; margin: 0 1rem;
display: flex; display: flex;

View File

@ -1,33 +1,30 @@
import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons'; import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons';
import { Menu, MenuProps } from 'antd'; import { MenuProps } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get'; import getLocalStorageKey from 'api/browser/localstorage/get';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app'; import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import history from 'lib/history'; import history from 'lib/history';
import { import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
ReactNode,
useCallback,
useLayoutEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { SideBarCollapse } from 'store/actions/app'; import { sideBarCollapse } from 'store/actions/app';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { routeConfig, styles } from './config'; import { routeConfig, styles } from './config';
import { getQueryString } from './helper'; import { getQueryString } from './helper';
import menus from './menuItems'; import menuItems from './menuItems';
import { MenuItem, SecondaryMenuItemKey } from './sideNav.types';
import { getActiveMenuKeyFromPath } from './sideNav.utils';
import Slack from './Slack'; import Slack from './Slack';
import { import {
MenuLabelContainer,
RedDot, RedDot,
Sider, Sider,
SlackButton, StyledPrimaryMenu,
SlackMenuItemContainer, StyledSecondaryMenu,
VersionContainer, StyledText,
} from './styles'; } from './styles';
function SideNav(): JSX.Element { function SideNav(): JSX.Element {
@ -49,7 +46,7 @@ function SideNav(): JSX.Element {
}, []); }, []);
useLayoutEffect(() => { useLayoutEffect(() => {
dispatch(SideBarCollapse(collapsed)); dispatch(sideBarCollapse(collapsed));
}, [collapsed, dispatch]); }, [collapsed, dispatch]);
const onClickHandler = useCallback( const onClickHandler = useCallback(
@ -80,90 +77,56 @@ function SideNav(): JSX.Element {
const isNotCurrentVersion = currentVersion !== latestVersion; const isNotCurrentVersion = currentVersion !== latestVersion;
const sidebar: SidebarItem[] = [ const secondaryMenuItems: MenuItem[] = [
{ {
onClick: onClickSlackHandler, key: SecondaryMenuItemKey.Version,
icon: <Slack />,
text: <SlackButton>Support</SlackButton>,
key: 'slack',
},
{
onClick: onClickVersionHandler,
key: 'version',
icon: isNotCurrentVersion ? ( icon: isNotCurrentVersion ? (
<WarningOutlined style={{ color: '#E87040' }} /> <WarningOutlined style={{ color: '#E87040' }} />
) : ( ) : (
<CheckCircleTwoTone twoToneColor={['#D5F2BB', '#1f1f1f']} /> <CheckCircleTwoTone twoToneColor={['#D5F2BB', '#1f1f1f']} />
), ),
text: ( label: (
<VersionContainer> <MenuLabelContainer>
{!isCurrentVersionError ? ( <StyledText ellipsis>
<SlackButton>{currentVersion}</SlackButton> {!isCurrentVersionError ? currentVersion : t('n_a')}
) : ( </StyledText>
<SlackButton>{t('n_a')}</SlackButton>
)}
{isNotCurrentVersion && <RedDot />} {isNotCurrentVersion && <RedDot />}
</VersionContainer> </MenuLabelContainer>
), ),
onClick: onClickVersionHandler,
},
{
key: SecondaryMenuItemKey.Slack,
icon: <Slack />,
label: <StyledText>Support</StyledText>,
onClick: onClickSlackHandler,
}, },
]; ];
const currentMenu = useMemo(() => { const activeMenuKey = useMemo(() => getActiveMenuKeyFromPath(pathname), [
const routeKeys = Object.keys(ROUTES) as (keyof typeof ROUTES)[]; pathname,
const currentRouteKey = routeKeys.find((key) => { ]);
const route = ROUTES[key];
return pathname === route;
});
if (!currentRouteKey) return null;
return ROUTES[currentRouteKey];
}, [pathname]);
const sidebarItems = (props: SidebarItem, index: number): SidebarItem => ({
key: `${index}`,
icon: props.icon,
onClick: props.onClick,
label: props.text,
});
return ( return (
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}> <Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}>
<Menu <StyledPrimaryMenu
theme="dark" theme="dark"
defaultSelectedKeys={[ROUTES.APPLICATION]} defaultSelectedKeys={[ROUTES.APPLICATION]}
selectedKeys={currentMenu ? [currentMenu] : []} selectedKeys={activeMenuKey ? [activeMenuKey] : []}
mode="vertical" mode="vertical"
style={styles} style={styles}
items={menus} items={menuItems}
onClick={onClickMenuHandler} onClick={onClickMenuHandler}
/> />
{sidebar.map((props, index) => ( <StyledSecondaryMenu
<SlackMenuItemContainer theme="dark"
index={index + 1} selectedKeys={activeMenuKey ? [activeMenuKey] : []}
key={`${index + 1}`} mode="vertical"
collapsed={collapsed} style={styles}
> items={secondaryMenuItems}
<Menu />
theme="dark"
defaultSelectedKeys={[ROUTES.APPLICATION]}
selectedKeys={currentMenu ? [currentMenu] : []}
mode="inline"
style={styles}
items={[sidebarItems(props, index)]}
/>
</SlackMenuItemContainer>
))}
</Sider> </Sider>
); );
} }
interface SidebarItem {
onClick: VoidFunction;
icon?: ReactNode;
text?: ReactNode;
key: string;
label?: ReactNode;
}
export default SideNav; export default SideNav;

View File

@ -1,6 +1,6 @@
interface ISlackProps { interface ISlackProps {
width?: number; width?: number | string;
height?: number; height?: number | string;
} }
function Slack({ width, height }: ISlackProps): JSX.Element { function Slack({ width, height }: ISlackProps): JSX.Element {
return ( return (
@ -47,8 +47,8 @@ function Slack({ width, height }: ISlackProps): JSX.Element {
); );
} }
Slack.defaultProps = { Slack.defaultProps = {
width: 28, width: '1em',
height: 28, height: '1em',
}; };
export default Slack; export default Slack;

View File

@ -0,0 +1,3 @@
import SideNav from './SideNav';
export default SideNav;

View File

@ -10,28 +10,11 @@ import {
MenuOutlined, MenuOutlined,
SettingOutlined, SettingOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { MenuProps, Space, Typography } from 'antd';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { Tags } from './styles'; import { SidebarMenu } from './sideNav.types';
type MenuItem = Required<MenuProps>['items'][number]; const menuItems: SidebarMenu[] = [
export const createLabelWithTags = (
label: string,
tags: string[],
): JSX.Element => (
<Space>
<div>{label}</div>
{tags.map((tag) => (
<Tags key={tag}>
<Typography.Text>{tag}</Typography.Text>
</Tags>
))}
</Space>
);
const menus: SidebarMenu[] = [
{ {
key: ROUTES.APPLICATION, key: ROUTES.APPLICATION,
label: 'Services', label: 'Services',
@ -111,8 +94,11 @@ const menus: SidebarMenu[] = [
}, },
]; ];
type SidebarMenu = MenuItem & { /** Mapping of some newly added routes and their corresponding active sidebar menu key */
tags?: string[]; export const NEW_ROUTES_MENU_ITEM_KEY_MAP = {
[ROUTES.TRACES_EXPLORER]: ROUTES.TRACE,
[ROUTES.TRACE_EXPLORER]: ROUTES.TRACE,
[ROUTES.LOGS_EXPLORER]: ROUTES.LOGS,
}; };
export default menus; export default menuItems;

View File

@ -0,0 +1,21 @@
import { MenuProps } from 'antd';
import { ReactNode } from 'react';
export type MenuItem = Required<MenuProps>['items'][number];
export type SidebarMenu = MenuItem & {
tags?: string[];
};
export interface SidebarItem {
onClick: VoidFunction;
icon?: ReactNode;
text?: ReactNode;
key: string;
label?: ReactNode;
}
export enum SecondaryMenuItemKey {
Slack = 'slack',
Version = 'version',
}

View File

@ -0,0 +1,11 @@
import { NEW_ROUTES_MENU_ITEM_KEY_MAP } from './menuItems';
export const getActiveMenuKeyFromPath = (pathname: string): string => {
const basePath = pathname?.split('/')?.[1]; // Get the base path, Eg; /dashboard/dc5beb63-589c-46a3-ad4c-1b9ca248ee33 -> dashboard
if (!basePath) return '';
const baseRoute = `/${basePath}`;
return NEW_ROUTES_MENU_ITEM_KEY_MAP[baseRoute] || baseRoute;
};

View File

@ -1,87 +1,68 @@
import { Layout, Tag, Typography } from 'antd'; import { Layout, Menu, Typography } from 'antd';
import { StyledCSS } from 'container/GantChart/Trace/styles'; import styled from 'styled-components';
import styled, { css } from 'styled-components';
const { Sider: SiderComponent } = Layout; const { Sider: SiderComponent } = Layout;
interface LogoProps {
collapsed: boolean;
index: number;
}
export const Sider = styled(SiderComponent)` export const Sider = styled(SiderComponent)`
&&& { &&& {
background: #1f1f1f; background: #1f1f1f;
.ant-layout-sider-children {
display: flex;
flex-direction: column;
}
.ant-layout-sider-trigger { .ant-layout-sider-trigger {
background: #1f1f1f; background: #1f1f1f;
} }
} }
`; `;
export const SlackButton = styled(Typography)` export const StyledPrimaryMenu = styled(Menu)`
&&& { flex: 1;
margin-left: 1rem; overflow-y: auto;
color: white;
}
`; `;
export const SlackMenuItemContainer = styled.div<LogoProps>` export const StyledSecondaryMenu = styled(Menu)`
position: fixed;
bottom: ${({ index }): string => `${index * 48 + (index + 16)}px`};
background: #262626;
width: ${({ collapsed }): string => (!collapsed ? '200px' : '80px')};
transition: inherit;
background: #1f1f1f;
&&& { &&& {
li { :not(.ant-menu-inline-collapsed) > .ant-menu-item {
${({ collapsed }): StyledCSS => padding-inline: 48px;
collapsed &&
css` display: flex;
padding-left: 24px; align-items: center;
padding-top: 6px; justify-content: center;
`}
} }
svg {
margin-left: ${({ collapsed }): string => (collapsed ? '0' : '24px')};
width: 28px;
height: 28px;
${({ collapsed }): StyledCSS =>
collapsed &&
css`
height: 100%;
margin: 0 auto;
`}
}
.ant-menu-title-content { .ant-menu-title-content {
margin: 0; margin-inline-start: 10px;
width: 100%;
}
&.ant-menu-inline-collapsed .ant-menu-title-content {
opacity: 0;
} }
} }
`; `;
export const RedDot = styled.div` export const RedDot = styled.div`
width: 12px; width: 10px;
height: 12px; height: 10px;
background: #d32029; background: #d32029;
border-radius: 50%; border-radius: 50%;
margin-left: 0.5rem; margin-left: 0.5rem;
margin-top: 0.5rem;
`; `;
export const VersionContainer = styled.div` export const MenuLabelContainer = styled.div`
&&& { display: inline-flex;
display: flex; align-items: center;
} justify-content: center;
width: 100%;
`; `;
export const Tags = styled(Tag)` export const StyledText = styled(Typography.Text)`
&&& { width: 100%;
position: absolute;
top: 0; color: white;
border-radius: 0.5rem;
}
`; `;

View File

@ -4,6 +4,5 @@ import styled from 'styled-components';
export const Container = styled(Row)` export const Container = styled(Row)`
&&& { &&& {
margin-top: 2rem; margin-top: 2rem;
min-height: 8vh;
} }
`; `;

View File

@ -0,0 +1,18 @@
import { createGlobalStyle } from 'styled-components';
const GlobalStyles = createGlobalStyle`
#root,
html,
body {
height: 100%;
overflow: hidden;
}
body {
padding: 0;
margin: 0;
box-sizing: border-box;
}
`;
export default GlobalStyles;

View File

@ -57,7 +57,7 @@
rel="stylesheet" rel="stylesheet"
/> />
</head> </head>
<body style="margin: 0; padding: 0; box-sizing: border-box"> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
</body> </body>

View File

@ -2,6 +2,7 @@ import './wdyr';
import './ReactI18'; import './ReactI18';
import AppRoutes from 'AppRoutes'; import AppRoutes from 'AppRoutes';
import GlobalStyles from 'globalStyles';
import { ThemeProvider } from 'hooks/useDarkMode'; import { ThemeProvider } from 'hooks/useDarkMode';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { HelmetProvider } from 'react-helmet-async'; import { HelmetProvider } from 'react-helmet-async';
@ -33,6 +34,7 @@ if (container) {
<ThemeProvider> <ThemeProvider>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Provider store={store}> <Provider store={store}>
<GlobalStyles />
<AppRoutes /> <AppRoutes />
</Provider> </Provider>
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (

View File

@ -3,7 +3,7 @@ import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
export const SideBarCollapse = ( export const sideBarCollapse = (
collapseState: boolean, collapseState: boolean,
): ((dispatch: Dispatch<AppActions>) => void) => { ): ((dispatch: Dispatch<AppActions>) => void) => {
setLocalStorageKey(IS_SIDEBAR_COLLAPSED, `${collapseState}`); setLocalStorageKey(IS_SIDEBAR_COLLAPSED, `${collapseState}`);