SingleTopNav: Initial changes (#93694)

initial changes for single top nav
This commit is contained in:
Ashley Harrison 2024-09-25 12:27:30 +01:00 committed by GitHub
parent 9a67cd614d
commit 8e5459791b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 166 additions and 18 deletions

View File

@ -224,6 +224,7 @@ const getStyles = (theme: GrafanaTheme2) => {
flexGrow: 1,
}),
content: css({
display: 'flex',
flexGrow: 1,
}),
contentWithIcon: css({

View File

@ -3,7 +3,7 @@ import classNames from 'classnames';
import { PropsWithChildren, useEffect } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { locationSearchToObject, locationService } from '@grafana/runtime';
import { config, locationSearchToObject, locationService } from '@grafana/runtime';
import { useStyles2, LinkButton, useTheme2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
@ -17,6 +17,7 @@ import { DOCKED_LOCAL_STORAGE_KEY, DOCKED_MENU_OPEN_LOCAL_STORAGE_KEY } from './
import { MegaMenu, MENU_WIDTH } from './MegaMenu/MegaMenu';
import { NavToolbar } from './NavToolbar/NavToolbar';
import { ReturnToPrevious } from './ReturnToPrevious/ReturnToPrevious';
import { SingleTopBar } from './TopBar/SingleTopBar';
import { TopSearchBar } from './TopBar/TopSearchBar';
import { TOP_BAR_LEVEL_HEIGHT } from './types';
@ -34,6 +35,7 @@ export function AppChrome({ children }: Props) {
const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen;
const scopesDashboardsState = useScopesDashboardsState();
const isScopesDashboardsOpen = Boolean(scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened);
const isSingleTopNav = config.featureToggles.singleTopNav;
useMediaQueryChange({
breakpoint: dockedMenuBreakpoint,
@ -92,6 +94,15 @@ export function AppChrome({ children }: Props) {
Skip to main content
</LinkButton>
<header className={cx(styles.topNav)}>
{isSingleTopNav ? (
<SingleTopBar
sectionNav={state.sectionNav.node}
pageNav={state.pageNav}
onToggleMegaMenu={handleMegaMenu}
onToggleKioskMode={chrome.onToggleKioskMode}
/>
) : (
<>
{!searchBarHidden && <TopSearchBar />}
<NavToolbar
searchBarHidden={searchBarHidden}
@ -102,6 +113,8 @@ export function AppChrome({ children }: Props) {
onToggleMegaMenu={handleMegaMenu}
onToggleKioskMode={chrome.onToggleKioskMode}
/>
</>
)}
</header>
</>
)}
@ -140,11 +153,12 @@ export function AppChrome({ children }: Props) {
}
const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => {
const isSingleTopNav = config.featureToggles.singleTopNav;
return {
content: css({
display: 'flex',
flexDirection: 'column',
paddingTop: TOP_BAR_LEVEL_HEIGHT * 2,
paddingTop: isSingleTopNav ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2,
flexGrow: 1,
height: 'auto',
}),
@ -167,13 +181,13 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => {
},
{
position: 'fixed',
height: `calc(100% - ${searchBarHidden ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2}px)`,
height: `calc(100% - ${searchBarHidden || isSingleTopNav ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2}px)`,
zIndex: 2,
}
),
scopesDashboardsContainer: css({
position: 'fixed',
height: `calc(100% - ${searchBarHidden ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2}px)`,
height: `calc(100% - ${searchBarHidden || isSingleTopNav ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2}px)`,
zIndex: 1,
}),
scopesDashboardsContainerDocked: css({

View File

@ -6,6 +6,7 @@ import { useRef } from 'react';
import CSSTransition from 'react-transition-group/CSSTransition';
import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { useStyles2, useTheme2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { KioskMode } from 'app/types';
@ -76,7 +77,8 @@ export function AppChromeMenu({}: Props) {
}
const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
const topPosition = searchBarHidden ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2;
const topPosition =
searchBarHidden || config.featureToggles.singleTopNav ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2;
return {
backdrop: css({

View File

@ -213,7 +213,7 @@ export class AppChromeService {
private getNextKioskMode() {
const { kioskMode, searchBarHidden } = this.state.getValue();
if (searchBarHidden || kioskMode === KioskMode.TV) {
if (searchBarHidden || kioskMode === KioskMode.TV || config.featureToggles.singleTopNav) {
appEvents.emit(AppEvents.alertInfo, [t('navigation.kiosk.tv-alert', 'Press ESC to exit kiosk mode')]);
return KioskMode.Full;
}

View File

@ -0,0 +1,131 @@
import { css } from '@emotion/css';
import { cloneDeep } from 'lodash';
import { memo } from 'react';
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
import { Dropdown, Icon, ToolbarButton, useStyles2 } from '@grafana/ui';
import { config } from 'app/core/config';
import { contextSrv } from 'app/core/core';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { ScopesSelector } from 'app/features/scopes';
import { useSelector } from 'app/types';
import { Branding } from '../../Branding/Branding';
import { Breadcrumbs } from '../../Breadcrumbs/Breadcrumbs';
import { buildBreadcrumbs } from '../../Breadcrumbs/utils';
import { enrichHelpItem } from '../MegaMenu/utils';
import { NewsContainer } from '../News/NewsContainer';
import { OrganizationSwitcher } from '../OrganizationSwitcher/OrganizationSwitcher';
import { QuickAdd } from '../QuickAdd/QuickAdd';
import { TOP_BAR_LEVEL_HEIGHT } from '../types';
import { SignInLink } from './SignInLink';
import { TopNavBarMenu } from './TopNavBarMenu';
import { TopSearchBarCommandPaletteTrigger } from './TopSearchBarCommandPaletteTrigger';
import { TopSearchBarSection } from './TopSearchBarSection';
interface Props {
sectionNav: NavModelItem;
pageNav?: NavModelItem;
onToggleMegaMenu(): void;
onToggleKioskMode(): void;
}
export const SingleTopBar = memo(function SingleTopBar({
onToggleMegaMenu,
onToggleKioskMode,
pageNav,
sectionNav,
}: Props) {
const styles = useStyles2(getStyles);
const navIndex = useSelector((state) => state.navIndex);
const helpNode = cloneDeep(navIndex['help']);
const enrichedHelpNode = helpNode ? enrichHelpItem(helpNode) : undefined;
const profileNode = navIndex['profile'];
const homeNav = useSelector((state) => state.navIndex)[HOME_NAV_ID];
const breadcrumbs = buildBreadcrumbs(sectionNav, pageNav, homeNav);
return (
<div className={styles.layout}>
<TopSearchBarSection>
<ToolbarButton narrow onClick={onToggleMegaMenu}>
<Branding.MenuLogo className={styles.img} />
</ToolbarButton>
<Breadcrumbs breadcrumbs={breadcrumbs} className={styles.breadcrumbsWrapper} />
<OrganizationSwitcher />
<ScopesSelector />
</TopSearchBarSection>
<TopSearchBarSection align="right">
<TopSearchBarCommandPaletteTrigger />
<QuickAdd />
{enrichedHelpNode && (
<Dropdown overlay={() => <TopNavBarMenu node={enrichedHelpNode} />} placement="bottom-end">
<ToolbarButton iconOnly icon="question-circle" aria-label="Help" />
</Dropdown>
)}
{config.newsFeedEnabled && <NewsContainer />}
{!contextSrv.user.isSignedIn && <SignInLink />}
{profileNode && (
<Dropdown overlay={() => <TopNavBarMenu node={profileNode} />} placement="bottom-end">
<ToolbarButton
className={styles.profileButton}
imgSrc={contextSrv.user.gravatarUrl}
imgAlt="User avatar"
aria-label="Profile"
/>
</Dropdown>
)}
<ToolbarButton className={styles.kioskToggle} onClick={onToggleKioskMode} narrow aria-label="Enable kiosk mode">
<Icon name="angle-up" size="xl" />
</ToolbarButton>
</TopSearchBarSection>
</div>
);
});
const getStyles = (theme: GrafanaTheme2) => ({
layout: css({
height: TOP_BAR_LEVEL_HEIGHT,
display: 'flex',
gap: theme.spacing(1),
alignItems: 'center',
padding: theme.spacing(0, 1),
borderBottom: `1px solid ${theme.colors.border.weak}`,
justifyContent: 'space-between',
[theme.breakpoints.up('sm')]: {
gridTemplateColumns: '2fr minmax(240px, 1fr)', // TODO probably change these values
display: 'grid',
justifyContent: 'flex-start',
},
}),
breadcrumbsWrapper: css({
display: 'flex',
overflow: 'hidden',
[theme.breakpoints.down('sm')]: {
minWidth: '50%',
},
}),
img: css({
alignSelf: 'center',
height: theme.spacing(3),
width: theme.spacing(3),
}),
profileButton: css({
padding: theme.spacing(0, 0.25),
img: {
borderRadius: theme.shape.radius.circle,
height: '24px',
marginRight: 0,
width: '24px',
},
}),
kioskToggle: css({
[theme.breakpoints.down('lg')]: {
display: 'none',
},
}),
});

View File

@ -1,7 +1,7 @@
import { createContext, useCallback, useContext } from 'react';
import { GrafanaConfig } from '@grafana/data';
import { LocationService, locationService, BackendSrv } from '@grafana/runtime';
import { LocationService, locationService, BackendSrv, config } from '@grafana/runtime';
import { AppChromeService } from '../components/AppChrome/AppChromeService';
import { NewFrontendAssetsChecker } from '../services/NewFrontendAssetsChecker';
@ -50,7 +50,7 @@ export function useChromeHeaderHeight() {
if (kioskMode || chromeless) {
return 0;
} else if (searchBarHidden) {
} else if (searchBarHidden || config.featureToggles.singleTopNav) {
return SINGLE_HEADER_BAR_HEIGHT;
} else {
return SINGLE_HEADER_BAR_HEIGHT * 2;