mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SingleTopNav: Initial changes (#93694)
initial changes for single top nav
This commit is contained in:
parent
9a67cd614d
commit
8e5459791b
@ -224,6 +224,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}),
|
}),
|
||||||
content: css({
|
content: css({
|
||||||
|
display: 'flex',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}),
|
}),
|
||||||
contentWithIcon: css({
|
contentWithIcon: css({
|
||||||
|
@ -3,7 +3,7 @@ import classNames from 'classnames';
|
|||||||
import { PropsWithChildren, useEffect } from 'react';
|
import { PropsWithChildren, useEffect } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
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 { useStyles2, LinkButton, useTheme2 } from '@grafana/ui';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange';
|
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 { MegaMenu, MENU_WIDTH } from './MegaMenu/MegaMenu';
|
||||||
import { NavToolbar } from './NavToolbar/NavToolbar';
|
import { NavToolbar } from './NavToolbar/NavToolbar';
|
||||||
import { ReturnToPrevious } from './ReturnToPrevious/ReturnToPrevious';
|
import { ReturnToPrevious } from './ReturnToPrevious/ReturnToPrevious';
|
||||||
|
import { SingleTopBar } from './TopBar/SingleTopBar';
|
||||||
import { TopSearchBar } from './TopBar/TopSearchBar';
|
import { TopSearchBar } from './TopBar/TopSearchBar';
|
||||||
import { TOP_BAR_LEVEL_HEIGHT } from './types';
|
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 menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen;
|
||||||
const scopesDashboardsState = useScopesDashboardsState();
|
const scopesDashboardsState = useScopesDashboardsState();
|
||||||
const isScopesDashboardsOpen = Boolean(scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened);
|
const isScopesDashboardsOpen = Boolean(scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened);
|
||||||
|
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||||
|
|
||||||
useMediaQueryChange({
|
useMediaQueryChange({
|
||||||
breakpoint: dockedMenuBreakpoint,
|
breakpoint: dockedMenuBreakpoint,
|
||||||
@ -92,16 +94,27 @@ export function AppChrome({ children }: Props) {
|
|||||||
Skip to main content
|
Skip to main content
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<header className={cx(styles.topNav)}>
|
<header className={cx(styles.topNav)}>
|
||||||
{!searchBarHidden && <TopSearchBar />}
|
{isSingleTopNav ? (
|
||||||
<NavToolbar
|
<SingleTopBar
|
||||||
searchBarHidden={searchBarHidden}
|
sectionNav={state.sectionNav.node}
|
||||||
sectionNav={state.sectionNav.node}
|
pageNav={state.pageNav}
|
||||||
pageNav={state.pageNav}
|
onToggleMegaMenu={handleMegaMenu}
|
||||||
actions={state.actions}
|
onToggleKioskMode={chrome.onToggleKioskMode}
|
||||||
onToggleSearchBar={chrome.onToggleSearchBar}
|
/>
|
||||||
onToggleMegaMenu={handleMegaMenu}
|
) : (
|
||||||
onToggleKioskMode={chrome.onToggleKioskMode}
|
<>
|
||||||
/>
|
{!searchBarHidden && <TopSearchBar />}
|
||||||
|
<NavToolbar
|
||||||
|
searchBarHidden={searchBarHidden}
|
||||||
|
sectionNav={state.sectionNav.node}
|
||||||
|
pageNav={state.pageNav}
|
||||||
|
actions={state.actions}
|
||||||
|
onToggleSearchBar={chrome.onToggleSearchBar}
|
||||||
|
onToggleMegaMenu={handleMegaMenu}
|
||||||
|
onToggleKioskMode={chrome.onToggleKioskMode}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -140,11 +153,12 @@ export function AppChrome({ children }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => {
|
const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => {
|
||||||
|
const isSingleTopNav = config.featureToggles.singleTopNav;
|
||||||
return {
|
return {
|
||||||
content: css({
|
content: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
paddingTop: TOP_BAR_LEVEL_HEIGHT * 2,
|
paddingTop: isSingleTopNav ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
}),
|
}),
|
||||||
@ -167,13 +181,13 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
position: 'fixed',
|
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,
|
zIndex: 2,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
scopesDashboardsContainer: css({
|
scopesDashboardsContainer: css({
|
||||||
position: 'fixed',
|
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,
|
zIndex: 1,
|
||||||
}),
|
}),
|
||||||
scopesDashboardsContainerDocked: css({
|
scopesDashboardsContainerDocked: css({
|
||||||
|
@ -6,6 +6,7 @@ import { useRef } from 'react';
|
|||||||
import CSSTransition from 'react-transition-group/CSSTransition';
|
import CSSTransition from 'react-transition-group/CSSTransition';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { useStyles2, useTheme2 } from '@grafana/ui';
|
import { useStyles2, useTheme2 } from '@grafana/ui';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
import { KioskMode } from 'app/types';
|
import { KioskMode } from 'app/types';
|
||||||
@ -76,7 +77,8 @@ export function AppChromeMenu({}: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
|
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 {
|
return {
|
||||||
backdrop: css({
|
backdrop: css({
|
||||||
|
@ -213,7 +213,7 @@ export class AppChromeService {
|
|||||||
private getNextKioskMode() {
|
private getNextKioskMode() {
|
||||||
const { kioskMode, searchBarHidden } = this.state.getValue();
|
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')]);
|
appEvents.emit(AppEvents.alertInfo, [t('navigation.kiosk.tv-alert', 'Press ESC to exit kiosk mode')]);
|
||||||
return KioskMode.Full;
|
return KioskMode.Full;
|
||||||
}
|
}
|
||||||
|
131
public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx
Normal file
131
public/app/core/components/AppChrome/TopBar/SingleTopBar.tsx
Normal 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',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
import { createContext, useCallback, useContext } from 'react';
|
import { createContext, useCallback, useContext } from 'react';
|
||||||
|
|
||||||
import { GrafanaConfig } from '@grafana/data';
|
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 { AppChromeService } from '../components/AppChrome/AppChromeService';
|
||||||
import { NewFrontendAssetsChecker } from '../services/NewFrontendAssetsChecker';
|
import { NewFrontendAssetsChecker } from '../services/NewFrontendAssetsChecker';
|
||||||
@ -50,7 +50,7 @@ export function useChromeHeaderHeight() {
|
|||||||
|
|
||||||
if (kioskMode || chromeless) {
|
if (kioskMode || chromeless) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (searchBarHidden) {
|
} else if (searchBarHidden || config.featureToggles.singleTopNav) {
|
||||||
return SINGLE_HEADER_BAR_HEIGHT;
|
return SINGLE_HEADER_BAR_HEIGHT;
|
||||||
} else {
|
} else {
|
||||||
return SINGLE_HEADER_BAR_HEIGHT * 2;
|
return SINGLE_HEADER_BAR_HEIGHT * 2;
|
||||||
|
Loading…
Reference in New Issue
Block a user