SingleTopNav: Add "Grafana" header to MegaMenu (#93798)

* add "Grafana" header to MegaMenu

* add truncation for really long custom app titles

* revert padding change since paddingLeft will handle it
This commit is contained in:
Ashley Harrison 2024-09-27 15:20:45 +01:00 committed by GitHub
parent 0e4c90dd87
commit bc6752a51c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 119 additions and 18 deletions

View File

@ -91,7 +91,7 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
left: 0,
position: 'fixed',
right: 0,
top: searchBarHidden ? 0 : TOP_BAR_LEVEL_HEIGHT,
top: searchBarHidden || config.featureToggles.singleTopNav ? 0 : TOP_BAR_LEVEL_HEIGHT,
zIndex: theme.zIndex.modalBackdrop,
[theme.breakpoints.up('md')]: {
@ -107,7 +107,7 @@ const getStyles = (theme: GrafanaTheme2, searchBarHidden?: boolean) => {
// Needs to below navbar should we change the navbarFixed? add add a new level?
zIndex: theme.zIndex.modal,
position: 'fixed',
top: searchBarHidden ? 0 : TOP_BAR_LEVEL_HEIGHT,
top: searchBarHidden || config.featureToggles.singleTopNav ? 0 : TOP_BAR_LEVEL_HEIGHT,
backgroundColor: theme.colors.background.primary,
flex: '1 1 0',

View File

@ -13,6 +13,7 @@ import { setBookmark } from 'app/core/reducers/navBarTree';
import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index';
import { useDispatch, useSelector } from 'app/types';
import { MegaMenuHeader } from './MegaMenuHeader';
import { MegaMenuItem } from './MegaMenuItem';
import { usePinnedItems } from './hooks';
import { enrichWithInteractionTracking, findByUrl, getActiveItem } from './utils';
@ -62,6 +63,10 @@ export const MegaMenu = memo(
const activeItem = getActiveItem(navItems, state.sectionNav.node, location.pathname);
const handleMegaMenu = () => {
chrome.setMegaMenuOpen(!state.megaMenuOpen);
};
const handleDockedMenu = () => {
chrome.setMegaMenuDocked(!state.megaMenuDocked);
if (state.megaMenuDocked) {
@ -109,22 +114,26 @@ export const MegaMenu = memo(
return (
<div data-testid={selectors.components.NavMenu.Menu} ref={ref} {...restProps}>
<div className={styles.mobileHeader}>
<Icon name="bars" size="xl" />
<IconButton
tooltip={t('navigation.megamenu.close', 'Close menu')}
name="times"
onClick={onClose}
size="xl"
variant="secondary"
/>
</div>
{config.featureToggles.singleTopNav ? (
<MegaMenuHeader handleDockedMenu={handleDockedMenu} handleMegaMenu={handleMegaMenu} onClose={onClose} />
) : (
<div className={styles.mobileHeader}>
<Icon name="bars" size="xl" />
<IconButton
tooltip={t('navigation.megamenu.close', 'Close menu')}
name="times"
onClick={onClose}
size="xl"
variant="secondary"
/>
</div>
)}
<nav className={styles.content}>
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
<ul className={styles.itemList} aria-label={t('navigation.megamenu.list-label', 'Navigation')}>
{navItems.map((link, index) => (
<Stack key={link.text} direction={index === 0 ? 'row-reverse' : 'row'} alignItems="start">
{index === 0 && (
{index === 0 && !config.featureToggles.singleTopNav && (
<IconButton
id="dock-menu-button"
className={styles.dockMenuButton}

View File

@ -0,0 +1,85 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { IconButton, Stack, Text, ToolbarButton, useTheme2 } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
import { t } from 'app/core/internationalization';
import { Branding } from '../../Branding/Branding';
import { TOP_BAR_LEVEL_HEIGHT } from '../types';
export interface Props {
handleMegaMenu: () => void;
handleDockedMenu: () => void;
onClose: () => void;
}
export function MegaMenuHeader({ handleMegaMenu, handleDockedMenu, onClose }: Props) {
const theme = useTheme2();
const { chrome } = useGrafana();
const state = chrome.useState();
const styles = getStyles(theme);
return (
<div className={styles.header}>
<Stack alignItems="center" minWidth={0}>
<ToolbarButton narrow onClick={handleMegaMenu}>
<Branding.MenuLogo className={styles.img} />
</ToolbarButton>
<Text truncate>{Branding.AppTitle}</Text>
</Stack>
<IconButton
id="dock-menu-button"
className={styles.dockMenuButton}
tooltip={
state.megaMenuDocked
? t('navigation.megamenu.undock', 'Undock menu')
: t('navigation.megamenu.dock', 'Dock menu')
}
name="web-section-alt"
onClick={handleDockedMenu}
variant="secondary"
/>
<IconButton
className={styles.mobileCloseButton}
tooltip={t('navigation.megamenu.close', 'Close menu')}
name="times"
onClick={onClose}
size="xl"
variant="secondary"
/>
</div>
);
}
MegaMenuHeader.displayName = 'MegaMenuHeader';
const getStyles = (theme: GrafanaTheme2) => ({
dockMenuButton: css({
display: 'none',
[theme.breakpoints.up('xl')]: {
display: 'inline-flex',
},
}),
header: css({
alignItems: 'center',
borderBottom: `1px solid ${theme.colors.border.weak}`,
display: 'flex',
gap: theme.spacing(1),
justifyContent: 'space-between',
padding: theme.spacing(0, 1, 0, 0.5),
height: TOP_BAR_LEVEL_HEIGHT,
minHeight: TOP_BAR_LEVEL_HEIGHT,
}),
img: css({
alignSelf: 'center',
height: theme.spacing(3),
width: theme.spacing(3),
}),
mobileCloseButton: css({
[theme.breakpoints.up('md')]: {
display: 'none',
},
}),
});

View File

@ -5,6 +5,7 @@ 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 { useGrafana } from 'app/core/context/GrafanaContext';
import { contextSrv } from 'app/core/core';
import { HOME_NAV_ID } from 'app/core/reducers/navModel';
import { ScopesSelector } from 'app/features/scopes';
@ -37,7 +38,10 @@ export const SingleTopBar = memo(function SingleTopBar({
pageNav,
sectionNav,
}: Props) {
const styles = useStyles2(getStyles);
const { chrome } = useGrafana();
const state = chrome.useState();
const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen;
const styles = useStyles2(getStyles, menuDockedAndOpen);
const navIndex = useSelector((state) => state.navIndex);
const helpNode = cloneDeep(navIndex['help']);
@ -49,9 +53,11 @@ export const SingleTopBar = memo(function SingleTopBar({
return (
<div className={styles.layout}>
<TopSearchBarSection>
<ToolbarButton narrow onClick={onToggleMegaMenu}>
<Branding.MenuLogo className={styles.img} />
</ToolbarButton>
{!menuDockedAndOpen && (
<ToolbarButton narrow onClick={onToggleMegaMenu}>
<Branding.MenuLogo className={styles.img} />
</ToolbarButton>
)}
<Breadcrumbs breadcrumbs={breadcrumbs} className={styles.breadcrumbsWrapper} />
<OrganizationSwitcher />
<ScopesSelector />
@ -85,13 +91,14 @@ export const SingleTopBar = memo(function SingleTopBar({
);
});
const getStyles = (theme: GrafanaTheme2) => ({
const getStyles = (theme: GrafanaTheme2, menuDockedAndOpen: boolean) => ({
layout: css({
height: TOP_BAR_LEVEL_HEIGHT,
display: 'flex',
gap: theme.spacing(1),
alignItems: 'center',
padding: theme.spacing(0, 1),
paddingLeft: menuDockedAndOpen ? theme.spacing(3.5) : theme.spacing(0.5),
borderBottom: `1px solid ${theme.colors.border.weak}`,
justifyContent: 'space-between',