From 8e5459791bd7814f55c0bee9385af88c105195a5 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 25 Sep 2024 12:27:30 +0100 Subject: [PATCH] SingleTopNav: Initial changes (#93694) initial changes for single top nav --- .../ToolbarButton/ToolbarButton.tsx | 1 + .../core/components/AppChrome/AppChrome.tsx | 42 ++++-- .../components/AppChrome/AppChromeMenu.tsx | 4 +- .../components/AppChrome/AppChromeService.tsx | 2 +- .../AppChrome/TopBar/SingleTopBar.tsx | 131 ++++++++++++++++++ public/app/core/context/GrafanaContext.ts | 4 +- 6 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index b99e176a142..05a3c5c5bdb 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -224,6 +224,7 @@ const getStyles = (theme: GrafanaTheme2) => { flexGrow: 1, }), content: css({ + display: 'flex', flexGrow: 1, }), contentWithIcon: css({ diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index 64d9b0bb058..7e5cfd0d365 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -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,16 +94,27 @@ export function AppChrome({ children }: Props) { Skip to main content
- {!searchBarHidden && } - + {isSingleTopNav ? ( + + ) : ( + <> + {!searchBarHidden && } + + + )}
)} @@ -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({ diff --git a/public/app/core/components/AppChrome/AppChromeMenu.tsx b/public/app/core/components/AppChrome/AppChromeMenu.tsx index fb670aff321..bf5022ec01c 100644 --- a/public/app/core/components/AppChrome/AppChromeMenu.tsx +++ b/public/app/core/components/AppChrome/AppChromeMenu.tsx @@ -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({ diff --git a/public/app/core/components/AppChrome/AppChromeService.tsx b/public/app/core/components/AppChrome/AppChromeService.tsx index 5c1d88ebb41..c041574aff9 100644 --- a/public/app/core/components/AppChrome/AppChromeService.tsx +++ b/public/app/core/components/AppChrome/AppChromeService.tsx @@ -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; } diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx new file mode 100644 index 00000000000..1499882fdf9 --- /dev/null +++ b/public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx @@ -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 ( +
+ + + + + + + + + + + + + {enrichedHelpNode && ( + } placement="bottom-end"> + + + )} + {config.newsFeedEnabled && } + {!contextSrv.user.isSignedIn && } + {profileNode && ( + } placement="bottom-end"> + + + )} + + + + +
+ ); +}); + +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', + }, + }), +}); diff --git a/public/app/core/context/GrafanaContext.ts b/public/app/core/context/GrafanaContext.ts index 068f55b48e0..424bd71ee68 100644 --- a/public/app/core/context/GrafanaContext.ts +++ b/public/app/core/context/GrafanaContext.ts @@ -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;