mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-07-25 03:24:25 +08:00
style(FE/layout): Fix app scroll behaviour and sidenav footer section inconsistency (#3426)
This commit is contained in:
parent
32a55f3c4f
commit
9aa72f847c
@ -25,7 +25,7 @@ import {
|
||||
} from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { ChildrenContainer, Layout } from './styles';
|
||||
import { ChildrenContainer, Layout, LayoutContent } from './styles';
|
||||
import { getRouteKey } from './utils';
|
||||
|
||||
function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
@ -240,12 +240,12 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
{isToDisplayLayout && <Header />}
|
||||
<Layout>
|
||||
{isToDisplayLayout && <SideNav />}
|
||||
<Layout.Content>
|
||||
<LayoutContent>
|
||||
<ChildrenContainer>
|
||||
{isToDisplayLayout && <TopNav />}
|
||||
{children}
|
||||
</ChildrenContainer>
|
||||
</Layout.Content>
|
||||
</LayoutContent>
|
||||
</Layout>
|
||||
</Layout>
|
||||
);
|
||||
|
@ -7,9 +7,14 @@ export const Layout = styled(LayoutComponent)`
|
||||
position: relative;
|
||||
min-height: calc(100vh - 4rem);
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const LayoutContent = styled(LayoutComponent.Content)`
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
export const ChildrenContainer = styled.div`
|
||||
margin: 0 1rem;
|
||||
display: flex;
|
||||
|
@ -1,33 +1,30 @@
|
||||
import { CheckCircleTwoTone, WarningOutlined } from '@ant-design/icons';
|
||||
import { Menu, MenuProps } from 'antd';
|
||||
import { MenuProps } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import {
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SideBarCollapse } from 'store/actions/app';
|
||||
import { sideBarCollapse } from 'store/actions/app';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { routeConfig, styles } from './config';
|
||||
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 {
|
||||
MenuLabelContainer,
|
||||
RedDot,
|
||||
Sider,
|
||||
SlackButton,
|
||||
SlackMenuItemContainer,
|
||||
VersionContainer,
|
||||
StyledPrimaryMenu,
|
||||
StyledSecondaryMenu,
|
||||
StyledText,
|
||||
} from './styles';
|
||||
|
||||
function SideNav(): JSX.Element {
|
||||
@ -49,7 +46,7 @@ function SideNav(): JSX.Element {
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
dispatch(SideBarCollapse(collapsed));
|
||||
dispatch(sideBarCollapse(collapsed));
|
||||
}, [collapsed, dispatch]);
|
||||
|
||||
const onClickHandler = useCallback(
|
||||
@ -80,90 +77,56 @@ function SideNav(): JSX.Element {
|
||||
|
||||
const isNotCurrentVersion = currentVersion !== latestVersion;
|
||||
|
||||
const sidebar: SidebarItem[] = [
|
||||
const secondaryMenuItems: MenuItem[] = [
|
||||
{
|
||||
onClick: onClickSlackHandler,
|
||||
icon: <Slack />,
|
||||
text: <SlackButton>Support</SlackButton>,
|
||||
key: 'slack',
|
||||
},
|
||||
{
|
||||
onClick: onClickVersionHandler,
|
||||
key: 'version',
|
||||
key: SecondaryMenuItemKey.Version,
|
||||
icon: isNotCurrentVersion ? (
|
||||
<WarningOutlined style={{ color: '#E87040' }} />
|
||||
) : (
|
||||
<CheckCircleTwoTone twoToneColor={['#D5F2BB', '#1f1f1f']} />
|
||||
),
|
||||
text: (
|
||||
<VersionContainer>
|
||||
{!isCurrentVersionError ? (
|
||||
<SlackButton>{currentVersion}</SlackButton>
|
||||
) : (
|
||||
<SlackButton>{t('n_a')}</SlackButton>
|
||||
)}
|
||||
label: (
|
||||
<MenuLabelContainer>
|
||||
<StyledText ellipsis>
|
||||
{!isCurrentVersionError ? currentVersion : t('n_a')}
|
||||
</StyledText>
|
||||
{isNotCurrentVersion && <RedDot />}
|
||||
</VersionContainer>
|
||||
</MenuLabelContainer>
|
||||
),
|
||||
onClick: onClickVersionHandler,
|
||||
},
|
||||
{
|
||||
key: SecondaryMenuItemKey.Slack,
|
||||
icon: <Slack />,
|
||||
label: <StyledText>Support</StyledText>,
|
||||
onClick: onClickSlackHandler,
|
||||
},
|
||||
];
|
||||
|
||||
const currentMenu = useMemo(() => {
|
||||
const routeKeys = Object.keys(ROUTES) as (keyof typeof ROUTES)[];
|
||||
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,
|
||||
});
|
||||
const activeMenuKey = useMemo(() => getActiveMenuKeyFromPath(pathname), [
|
||||
pathname,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}>
|
||||
<Menu
|
||||
<StyledPrimaryMenu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={[ROUTES.APPLICATION]}
|
||||
selectedKeys={currentMenu ? [currentMenu] : []}
|
||||
selectedKeys={activeMenuKey ? [activeMenuKey] : []}
|
||||
mode="vertical"
|
||||
style={styles}
|
||||
items={menus}
|
||||
items={menuItems}
|
||||
onClick={onClickMenuHandler}
|
||||
/>
|
||||
{sidebar.map((props, index) => (
|
||||
<SlackMenuItemContainer
|
||||
index={index + 1}
|
||||
key={`${index + 1}`}
|
||||
collapsed={collapsed}
|
||||
>
|
||||
<Menu
|
||||
theme="dark"
|
||||
defaultSelectedKeys={[ROUTES.APPLICATION]}
|
||||
selectedKeys={currentMenu ? [currentMenu] : []}
|
||||
mode="inline"
|
||||
style={styles}
|
||||
items={[sidebarItems(props, index)]}
|
||||
/>
|
||||
</SlackMenuItemContainer>
|
||||
))}
|
||||
<StyledSecondaryMenu
|
||||
theme="dark"
|
||||
selectedKeys={activeMenuKey ? [activeMenuKey] : []}
|
||||
mode="vertical"
|
||||
style={styles}
|
||||
items={secondaryMenuItems}
|
||||
/>
|
||||
</Sider>
|
||||
);
|
||||
}
|
||||
|
||||
interface SidebarItem {
|
||||
onClick: VoidFunction;
|
||||
icon?: ReactNode;
|
||||
text?: ReactNode;
|
||||
key: string;
|
||||
label?: ReactNode;
|
||||
}
|
||||
|
||||
export default SideNav;
|
@ -1,6 +1,6 @@
|
||||
interface ISlackProps {
|
||||
width?: number;
|
||||
height?: number;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
}
|
||||
function Slack({ width, height }: ISlackProps): JSX.Element {
|
||||
return (
|
||||
@ -47,8 +47,8 @@ function Slack({ width, height }: ISlackProps): JSX.Element {
|
||||
);
|
||||
}
|
||||
Slack.defaultProps = {
|
||||
width: 28,
|
||||
height: 28,
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
};
|
||||
|
||||
export default Slack;
|
||||
|
3
frontend/src/container/SideNav/index.ts
Normal file
3
frontend/src/container/SideNav/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import SideNav from './SideNav';
|
||||
|
||||
export default SideNav;
|
@ -10,28 +10,11 @@ import {
|
||||
MenuOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { MenuProps, Space, Typography } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import { Tags } from './styles';
|
||||
import { SidebarMenu } from './sideNav.types';
|
||||
|
||||
type MenuItem = Required<MenuProps>['items'][number];
|
||||
|
||||
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[] = [
|
||||
const menuItems: SidebarMenu[] = [
|
||||
{
|
||||
key: ROUTES.APPLICATION,
|
||||
label: 'Services',
|
||||
@ -111,8 +94,11 @@ const menus: SidebarMenu[] = [
|
||||
},
|
||||
];
|
||||
|
||||
type SidebarMenu = MenuItem & {
|
||||
tags?: string[];
|
||||
/** Mapping of some newly added routes and their corresponding active sidebar menu key */
|
||||
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;
|
||||
|
21
frontend/src/container/SideNav/sideNav.types.ts
Normal file
21
frontend/src/container/SideNav/sideNav.types.ts
Normal 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',
|
||||
}
|
11
frontend/src/container/SideNav/sideNav.utils.ts
Normal file
11
frontend/src/container/SideNav/sideNav.utils.ts
Normal 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;
|
||||
};
|
@ -1,87 +1,68 @@
|
||||
import { Layout, Tag, Typography } from 'antd';
|
||||
import { StyledCSS } from 'container/GantChart/Trace/styles';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { Layout, Menu, Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const { Sider: SiderComponent } = Layout;
|
||||
|
||||
interface LogoProps {
|
||||
collapsed: boolean;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const Sider = styled(SiderComponent)`
|
||||
&&& {
|
||||
background: #1f1f1f;
|
||||
|
||||
.ant-layout-sider-children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ant-layout-sider-trigger {
|
||||
background: #1f1f1f;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SlackButton = styled(Typography)`
|
||||
&&& {
|
||||
margin-left: 1rem;
|
||||
color: white;
|
||||
}
|
||||
export const StyledPrimaryMenu = styled(Menu)`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
export const SlackMenuItemContainer = styled.div<LogoProps>`
|
||||
position: fixed;
|
||||
bottom: ${({ index }): string => `${index * 48 + (index + 16)}px`};
|
||||
background: #262626;
|
||||
width: ${({ collapsed }): string => (!collapsed ? '200px' : '80px')};
|
||||
transition: inherit;
|
||||
background: #1f1f1f;
|
||||
|
||||
export const StyledSecondaryMenu = styled(Menu)`
|
||||
&&& {
|
||||
li {
|
||||
${({ collapsed }): StyledCSS =>
|
||||
collapsed &&
|
||||
css`
|
||||
padding-left: 24px;
|
||||
padding-top: 6px;
|
||||
`}
|
||||
:not(.ant-menu-inline-collapsed) > .ant-menu-item {
|
||||
padding-inline: 48px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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 {
|
||||
margin: 0;
|
||||
margin-inline-start: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.ant-menu-inline-collapsed .ant-menu-title-content {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const RedDot = styled.div`
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #d32029;
|
||||
border-radius: 50%;
|
||||
|
||||
margin-left: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
`;
|
||||
|
||||
export const VersionContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
}
|
||||
export const MenuLabelContainer = styled.div`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const Tags = styled(Tag)`
|
||||
&&& {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
export const StyledText = styled(Typography.Text)`
|
||||
width: 100%;
|
||||
|
||||
color: white;
|
||||
`;
|
||||
|
@ -4,6 +4,5 @@ import styled from 'styled-components';
|
||||
export const Container = styled(Row)`
|
||||
&&& {
|
||||
margin-top: 2rem;
|
||||
min-height: 8vh;
|
||||
}
|
||||
`;
|
||||
|
18
frontend/src/globalStyles.ts
Normal file
18
frontend/src/globalStyles.ts
Normal 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;
|
@ -57,7 +57,7 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; box-sizing: border-box">
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
@ -2,6 +2,7 @@ import './wdyr';
|
||||
import './ReactI18';
|
||||
|
||||
import AppRoutes from 'AppRoutes';
|
||||
import GlobalStyles from 'globalStyles';
|
||||
import { ThemeProvider } from 'hooks/useDarkMode';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
@ -33,6 +34,7 @@ if (container) {
|
||||
<ThemeProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<GlobalStyles />
|
||||
<AppRoutes />
|
||||
</Provider>
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
|
@ -3,7 +3,7 @@ import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
export const SideBarCollapse = (
|
||||
export const sideBarCollapse = (
|
||||
collapseState: boolean,
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
setLocalStorageKey(IS_SIDEBAR_COLLAPSED, `${collapseState}`);
|
||||
|
Loading…
x
Reference in New Issue
Block a user