mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Navigation: Implement logic for docking nav menu (#76188)
* Create a state for dockedMegaMenu and the function to manage it * Add the dockedMenu icon and handle the status when clicking it * Add Megamenu to section nav area when it is docked * get logic working * fix mobile * refactor state + persist in localStorage * adjust icon and don't use position absolute * restore old rudderstack tracking * use Flex instead * adjust feature toggle to be experimental * extract out localStorage handling into utils * don't need separate file * use store.set/get instead --------- Co-authored-by: eledobleefe <laura.fernandez@grafana.com>
This commit is contained in:
parent
de2d8f50e8
commit
930c753340
@ -64,7 +64,6 @@ Some features are enabled by default. You can disable these feature by setting t
|
|||||||
| `newDBLibrary` | Use jmoiron/sqlx rather than xorm for a few backend services |
|
| `newDBLibrary` | Use jmoiron/sqlx rather than xorm for a few backend services |
|
||||||
| `autoMigrateOldPanels` | Migrate old angular panels to supported versions (graph, table-old, worldmap, etc) |
|
| `autoMigrateOldPanels` | Migrate old angular panels to supported versions (graph, table-old, worldmap, etc) |
|
||||||
| `disableAngular` | Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime. |
|
| `disableAngular` | Dynamic flag to disable angular at runtime. The preferred method is to set `angular_support_enabled` to `false` in the [security] settings, which allows you to change the state at runtime. |
|
||||||
| `dockedMegaMenu` | Enable support for a persistent (docked) navigation menu |
|
|
||||||
| `grpcServer` | Run the GRPC server |
|
| `grpcServer` | Run the GRPC server |
|
||||||
| `accessControlOnCall` | Access control primitives for OnCall |
|
| `accessControlOnCall` | Access control primitives for OnCall |
|
||||||
| `nestedFolders` | Enable folder nesting |
|
| `nestedFolders` | Enable folder nesting |
|
||||||
@ -97,6 +96,7 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `scenes` | Experimental framework to build interactive dashboards |
|
| `scenes` | Experimental framework to build interactive dashboards |
|
||||||
| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables |
|
| `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables |
|
||||||
| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown |
|
| `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown |
|
||||||
|
| `dockedMegaMenu` | Enable support for a persistent (docked) navigation menu |
|
||||||
| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema |
|
| `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema |
|
||||||
| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query |
|
| `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query |
|
||||||
| `alertingBacktesting` | Rule backtesting API for alerting |
|
| `alertingBacktesting` | Rule backtesting API for alerting |
|
||||||
|
@ -225,6 +225,7 @@ export const availableIconsIndex = {
|
|||||||
'vertical-align-bottom': true,
|
'vertical-align-bottom': true,
|
||||||
'vertical-align-center': true,
|
'vertical-align-center': true,
|
||||||
'vertical-align-top': true,
|
'vertical-align-top': true,
|
||||||
|
'web-section-alt': true,
|
||||||
'wrap-text': true,
|
'wrap-text': true,
|
||||||
rss: true,
|
rss: true,
|
||||||
x: true,
|
x: true,
|
||||||
|
@ -164,7 +164,7 @@ var (
|
|||||||
{
|
{
|
||||||
Name: "dockedMegaMenu",
|
Name: "dockedMegaMenu",
|
||||||
Description: "Enable support for a persistent (docked) navigation menu",
|
Description: "Enable support for a persistent (docked) navigation menu",
|
||||||
Stage: FeatureStagePublicPreview,
|
Stage: FeatureStageExperimental,
|
||||||
FrontendOnly: true,
|
FrontendOnly: true,
|
||||||
Owner: grafanaFrontendPlatformSquad,
|
Owner: grafanaFrontendPlatformSquad,
|
||||||
},
|
},
|
||||||
|
@ -22,7 +22,7 @@ disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,fals
|
|||||||
logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false,false
|
logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false,false
|
||||||
dataConnectionsConsole,GA,@grafana/plugins-platform-backend,false,false,false,false
|
dataConnectionsConsole,GA,@grafana/plugins-platform-backend,false,false,false,false
|
||||||
topnav,deprecated,@grafana/grafana-frontend-platform,false,false,false,false
|
topnav,deprecated,@grafana/grafana-frontend-platform,false,false,false,false
|
||||||
dockedMegaMenu,preview,@grafana/grafana-frontend-platform,false,false,false,true
|
dockedMegaMenu,experimental,@grafana/grafana-frontend-platform,false,false,false,true
|
||||||
grpcServer,preview,@grafana/grafana-app-platform-squad,false,false,false,false
|
grpcServer,preview,@grafana/grafana-app-platform-squad,false,false,false,false
|
||||||
entityStore,experimental,@grafana/grafana-app-platform-squad,true,false,false,false
|
entityStore,experimental,@grafana/grafana-app-platform-squad,true,false,false,false
|
||||||
cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,false,false,false,false
|
cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,false,false,false,false
|
||||||
|
|
@ -10,6 +10,7 @@ import { CommandPalette } from 'app/features/commandPalette/CommandPalette';
|
|||||||
import { KioskMode } from 'app/types';
|
import { KioskMode } from 'app/types';
|
||||||
|
|
||||||
import { AppChromeMenu } from './AppChromeMenu';
|
import { AppChromeMenu } from './AppChromeMenu';
|
||||||
|
import { MegaMenu as DockedMegaMenu } from './DockedMegaMenu/MegaMenu';
|
||||||
import { MegaMenu } from './MegaMenu/MegaMenu';
|
import { MegaMenu } from './MegaMenu/MegaMenu';
|
||||||
import { NavToolbar } from './NavToolbar/NavToolbar';
|
import { NavToolbar } from './NavToolbar/NavToolbar';
|
||||||
import { SectionNav } from './SectionNav/SectionNav';
|
import { SectionNav } from './SectionNav/SectionNav';
|
||||||
@ -53,7 +54,7 @@ export function AppChrome({ children }: Props) {
|
|||||||
pageNav={state.pageNav}
|
pageNav={state.pageNav}
|
||||||
actions={state.actions}
|
actions={state.actions}
|
||||||
onToggleSearchBar={chrome.onToggleSearchBar}
|
onToggleSearchBar={chrome.onToggleSearchBar}
|
||||||
onToggleMegaMenu={chrome.onToggleMegaMenu}
|
onToggleMegaMenu={() => chrome.setMegaMenu(state.megaMenu === 'closed' ? 'open' : 'closed')}
|
||||||
onToggleKioskMode={chrome.onToggleKioskMode}
|
onToggleKioskMode={chrome.onToggleKioskMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -64,6 +65,9 @@ export function AppChrome({ children }: Props) {
|
|||||||
{state.layout === PageLayoutType.Standard && state.sectionNav && !config.featureToggles.dockedMegaMenu && (
|
{state.layout === PageLayoutType.Standard && state.sectionNav && !config.featureToggles.dockedMegaMenu && (
|
||||||
<SectionNav model={state.sectionNav} />
|
<SectionNav model={state.sectionNav} />
|
||||||
)}
|
)}
|
||||||
|
{config.featureToggles.dockedMegaMenu && !state.chromeless && state.megaMenu === 'docked' && (
|
||||||
|
<DockedMegaMenu className={styles.dockedMegaMenu} onClose={() => chrome.setMegaMenu('closed')} />
|
||||||
|
)}
|
||||||
<div className={styles.pageContainer} id="pageContent">
|
<div className={styles.pageContainer} id="pageContent">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
@ -74,7 +78,7 @@ export function AppChrome({ children }: Props) {
|
|||||||
{config.featureToggles.dockedMegaMenu ? (
|
{config.featureToggles.dockedMegaMenu ? (
|
||||||
<AppChromeMenu />
|
<AppChromeMenu />
|
||||||
) : (
|
) : (
|
||||||
<MegaMenu searchBarHidden={searchBarHidden} onClose={() => chrome.setMegaMenu(false)} />
|
<MegaMenu searchBarHidden={searchBarHidden} onClose={() => chrome.setMegaMenu('closed')} />
|
||||||
)}
|
)}
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
</>
|
</>
|
||||||
@ -102,6 +106,12 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
contentChromeless: css({
|
contentChromeless: css({
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
}),
|
}),
|
||||||
|
dockedMegaMenu: css({
|
||||||
|
background: theme.colors.background.primary,
|
||||||
|
borderRight: `1px solid ${theme.colors.border.weak}`,
|
||||||
|
borderTop: `1px solid ${theme.colors.border.weak}`,
|
||||||
|
zIndex: theme.zIndex.navbarFixed,
|
||||||
|
}),
|
||||||
topNav: css({
|
topNav: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
@ -27,8 +27,8 @@ export function AppChromeMenu({}: Props) {
|
|||||||
const animationSpeed = theme.transitions.duration.shortest;
|
const animationSpeed = theme.transitions.duration.shortest;
|
||||||
const animationStyles = useStyles2(getAnimStyles, animationSpeed);
|
const animationStyles = useStyles2(getAnimStyles, animationSpeed);
|
||||||
|
|
||||||
const isOpen = state.megaMenuOpen;
|
const isOpen = state.megaMenu === 'open';
|
||||||
const onClose = () => chrome.setMegaMenu(false);
|
const onClose = () => chrome.setMegaMenu('closed');
|
||||||
|
|
||||||
const { overlayProps, underlayProps } = useOverlay(
|
const { overlayProps, underlayProps } = useOverlay(
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,7 @@ import { useObservable } from 'react-use';
|
|||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { AppEvents, NavModel, NavModelItem, PageLayoutType, UrlQueryValue } from '@grafana/data';
|
import { AppEvents, NavModel, NavModelItem, PageLayoutType, UrlQueryValue } from '@grafana/data';
|
||||||
import { locationService, reportInteraction } from '@grafana/runtime';
|
import { config, locationService, reportInteraction } from '@grafana/runtime';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { t } from 'app/core/internationalization';
|
import { t } from 'app/core/internationalization';
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
@ -17,11 +17,13 @@ export interface AppChromeState {
|
|||||||
pageNav?: NavModelItem;
|
pageNav?: NavModelItem;
|
||||||
actions?: React.ReactNode;
|
actions?: React.ReactNode;
|
||||||
searchBarHidden?: boolean;
|
searchBarHidden?: boolean;
|
||||||
megaMenuOpen?: boolean;
|
megaMenu: 'open' | 'closed' | 'docked';
|
||||||
kioskMode: KioskMode | null;
|
kioskMode: KioskMode | null;
|
||||||
layout: PageLayoutType;
|
layout: PageLayoutType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DOCKED_LOCAL_STORAGE_KEY = 'grafana.navigation.docked';
|
||||||
|
|
||||||
export class AppChromeService {
|
export class AppChromeService {
|
||||||
searchBarStorageKey = 'SearchBar_Hidden';
|
searchBarStorageKey = 'SearchBar_Hidden';
|
||||||
private currentRoute?: RouteDescriptor;
|
private currentRoute?: RouteDescriptor;
|
||||||
@ -31,6 +33,8 @@ export class AppChromeService {
|
|||||||
chromeless: true, // start out hidden to not flash it on pages without chrome
|
chromeless: true, // start out hidden to not flash it on pages without chrome
|
||||||
sectionNav: { node: { text: t('nav.home.title', 'Home') }, main: { text: '' } },
|
sectionNav: { node: { text: t('nav.home.title', 'Home') }, main: { text: '' } },
|
||||||
searchBarHidden: store.getBool(this.searchBarStorageKey, false),
|
searchBarHidden: store.getBool(this.searchBarStorageKey, false),
|
||||||
|
megaMenu:
|
||||||
|
config.featureToggles.dockedMegaMenu && store.getBool(DOCKED_LOCAL_STORAGE_KEY, false) ? 'docked' : 'closed',
|
||||||
kioskMode: null,
|
kioskMode: null,
|
||||||
layout: PageLayoutType.Canvas,
|
layout: PageLayoutType.Canvas,
|
||||||
});
|
});
|
||||||
@ -93,14 +97,14 @@ export class AppChromeService {
|
|||||||
return useObservable(this.state, this.state.getValue());
|
return useObservable(this.state, this.state.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public onToggleMegaMenu = () => {
|
public setMegaMenu = (newMegaMenuState: AppChromeState['megaMenu']) => {
|
||||||
const isOpen = !this.state.getValue().megaMenuOpen;
|
if (config.featureToggles.dockedMegaMenu) {
|
||||||
reportInteraction('grafana_toggle_menu_clicked', { action: isOpen ? 'open' : 'close' });
|
store.set(DOCKED_LOCAL_STORAGE_KEY, newMegaMenuState === 'docked');
|
||||||
this.update({ megaMenuOpen: isOpen });
|
reportInteraction('grafana_mega_menu_state', { state: newMegaMenuState });
|
||||||
};
|
} else {
|
||||||
|
reportInteraction('grafana_toggle_menu_clicked', { action: newMegaMenuState === 'open' ? 'open' : 'close' });
|
||||||
public setMegaMenu = (megaMenuOpen: boolean) => {
|
}
|
||||||
this.update({ megaMenuOpen });
|
this.update({ megaMenu: newMegaMenuState });
|
||||||
};
|
};
|
||||||
|
|
||||||
public onToggleSearchBar = () => {
|
public onToggleSearchBar = () => {
|
||||||
|
@ -35,7 +35,7 @@ const setup = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const grafanaContext = getGrafanaContextMock();
|
const grafanaContext = getGrafanaContextMock();
|
||||||
grafanaContext.chrome.onToggleMegaMenu();
|
grafanaContext.chrome.setMegaMenu('open');
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<TestProvider storeState={{ navBarTree }} grafanaContext={grafanaContext}>
|
<TestProvider storeState={{ navBarTree }} grafanaContext={grafanaContext}>
|
||||||
|
@ -6,6 +6,9 @@ import { useLocation } from 'react-router-dom';
|
|||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { CustomScrollbar, Icon, IconButton, useStyles2 } from '@grafana/ui';
|
import { CustomScrollbar, Icon, IconButton, useStyles2 } from '@grafana/ui';
|
||||||
|
import { Flex } from '@grafana/ui/src/unstable';
|
||||||
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
|
import { t } from 'app/core/internationalization';
|
||||||
import { useSelector } from 'app/types';
|
import { useSelector } from 'app/types';
|
||||||
|
|
||||||
import { MegaMenuItem } from './MegaMenuItem';
|
import { MegaMenuItem } from './MegaMenuItem';
|
||||||
@ -22,6 +25,8 @@ export const MegaMenu = React.memo(
|
|||||||
const navBarTree = useSelector((state) => state.navBarTree);
|
const navBarTree = useSelector((state) => state.navBarTree);
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { chrome } = useGrafana();
|
||||||
|
const state = chrome.useState();
|
||||||
|
|
||||||
const navTree = cloneDeep(navBarTree);
|
const navTree = cloneDeep(navBarTree);
|
||||||
|
|
||||||
@ -32,13 +37,16 @@ export const MegaMenu = React.memo(
|
|||||||
|
|
||||||
const activeItem = getActiveItem(navItems, location.pathname);
|
const activeItem = getActiveItem(navItems, location.pathname);
|
||||||
|
|
||||||
|
const handleDockedMenu = () => {
|
||||||
|
chrome.setMegaMenu(state.megaMenu === 'docked' ? 'closed' : 'docked');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="navbarmenu" ref={ref} {...restProps}>
|
<div data-testid="navbarmenu" ref={ref} {...restProps}>
|
||||||
<div className={styles.mobileHeader}>
|
<div className={styles.mobileHeader}>
|
||||||
<Icon name="bars" size="xl" />
|
<Icon name="bars" size="xl" />
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="Close navigation menu"
|
tooltip={t('navigation.megamenu.close', 'Close menu')}
|
||||||
tooltip="Close menu"
|
|
||||||
name="times"
|
name="times"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
size="xl"
|
size="xl"
|
||||||
@ -48,8 +56,27 @@ export const MegaMenu = React.memo(
|
|||||||
<nav className={styles.content}>
|
<nav className={styles.content}>
|
||||||
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
|
<CustomScrollbar showScrollIndicators hideHorizontalTrack>
|
||||||
<ul className={styles.itemList}>
|
<ul className={styles.itemList}>
|
||||||
{navItems.map((link) => (
|
{navItems.map((link, index) => (
|
||||||
<MegaMenuItem link={link} onClose={onClose} activeItem={activeItem} key={link.text} />
|
<Flex key={link.text} direction="row" alignItems="center">
|
||||||
|
<MegaMenuItem
|
||||||
|
link={link}
|
||||||
|
onClick={state.megaMenu === 'open' ? onClose : undefined}
|
||||||
|
activeItem={activeItem}
|
||||||
|
/>
|
||||||
|
{index === 0 && (
|
||||||
|
<IconButton
|
||||||
|
className={styles.dockMenuButton}
|
||||||
|
tooltip={
|
||||||
|
state.megaMenu === 'docked'
|
||||||
|
? t('navigation.megamenu.undock', 'Undock menu')
|
||||||
|
: t('navigation.megamenu.dock', 'Dock menu')
|
||||||
|
}
|
||||||
|
name="web-section-alt"
|
||||||
|
onClick={handleDockedMenu}
|
||||||
|
variant="secondary"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
@ -65,8 +92,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
content: css({
|
content: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
flexGrow: 1,
|
height: '100%',
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
|
position: 'relative',
|
||||||
}),
|
}),
|
||||||
mobileHeader: css({
|
mobileHeader: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -79,10 +107,15 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
itemList: css({
|
itemList: css({
|
||||||
display: 'grid',
|
display: 'flex',
|
||||||
gridAutoRows: `minmax(${theme.spacing(6)}, auto)`,
|
flexDirection: 'column',
|
||||||
gridTemplateColumns: `minmax(${MENU_WIDTH}, auto)`,
|
|
||||||
listStyleType: 'none',
|
listStyleType: 'none',
|
||||||
minWidth: MENU_WIDTH,
|
minWidth: MENU_WIDTH,
|
||||||
|
[theme.breakpoints.up('md')]: {
|
||||||
|
width: MENU_WIDTH,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
dockMenuButton: css({
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
@ -15,11 +15,11 @@ import { hasChildMatch } from './utils';
|
|||||||
interface Props {
|
interface Props {
|
||||||
link: NavModelItem;
|
link: NavModelItem;
|
||||||
activeItem?: NavModelItem;
|
activeItem?: NavModelItem;
|
||||||
onClose?: () => void;
|
onClick?: () => void;
|
||||||
level?: number;
|
level?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MegaMenuItem({ link, activeItem, level = 0, onClose }: Props) {
|
export function MegaMenuItem({ link, activeItem, level = 0, onClick }: Props) {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const FeatureHighlightWrapper = link.highlightText ? FeatureHighlight : React.Fragment;
|
const FeatureHighlightWrapper = link.highlightText ? FeatureHighlight : React.Fragment;
|
||||||
const isActive = link === activeItem;
|
const isActive = link === activeItem;
|
||||||
@ -29,13 +29,13 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClose }: Props) {
|
|||||||
const showExpandButton = linkHasChildren(link) || link.emptyMessage;
|
const showExpandButton = linkHasChildren(link) || link.emptyMessage;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li>
|
<li className={styles.listItem}>
|
||||||
<div className={styles.collapsibleSectionWrapper}>
|
<div className={styles.collapsibleSectionWrapper}>
|
||||||
<MegaMenuItemText
|
<MegaMenuItemText
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
link.onClick?.();
|
link.onClick?.();
|
||||||
onClose?.();
|
onClick?.();
|
||||||
}}
|
}}
|
||||||
target={link.target}
|
target={link.target}
|
||||||
url={link.url}
|
url={link.url}
|
||||||
@ -75,7 +75,7 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClose }: Props) {
|
|||||||
key={`${link.text}-${childLink.text}`}
|
key={`${link.text}-${childLink.text}`}
|
||||||
link={childLink}
|
link={childLink}
|
||||||
activeItem={activeItem}
|
activeItem={activeItem}
|
||||||
onClose={onClose}
|
onClick={onClick}
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
@ -121,6 +121,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
fontWeight: theme.typography.fontWeightMedium,
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
}),
|
}),
|
||||||
|
listItem: css({
|
||||||
|
flex: 1,
|
||||||
|
}),
|
||||||
isActive: css({
|
isActive: css({
|
||||||
color: theme.colors.text.primary,
|
color: theme.colors.text.primary,
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ const setup = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const grafanaContext = getGrafanaContextMock();
|
const grafanaContext = getGrafanaContextMock();
|
||||||
grafanaContext.chrome.onToggleMegaMenu();
|
grafanaContext.chrome.setMegaMenu('open');
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<TestProvider storeState={{ navBarTree }} grafanaContext={grafanaContext}>
|
<TestProvider storeState={{ navBarTree }} grafanaContext={grafanaContext}>
|
||||||
|
@ -46,10 +46,10 @@ export function NavBarMenu({ activeItem, navItems, searchBarHidden, onClose }: P
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state.megaMenuOpen) {
|
if (state.megaMenu === 'open') {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
}
|
}
|
||||||
}, [state.megaMenuOpen]);
|
}, [state.megaMenu]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayContainer>
|
<OverlayContainer>
|
||||||
|
@ -3,6 +3,7 @@ import { css, cx } from '@emotion/css';
|
|||||||
import React, { useLayoutEffect } from 'react';
|
import React, { useLayoutEffect } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
import { GrafanaTheme2, PageLayoutType } from '@grafana/data';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
import { CustomScrollbar, useStyles2 } from '@grafana/ui';
|
||||||
import { useGrafana } from 'app/core/context/GrafanaContext';
|
import { useGrafana } from 'app/core/context/GrafanaContext';
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
margin: theme.spacing(0, 0, 0, 0),
|
margin: theme.spacing(0, 0, 0, 0),
|
||||||
|
|
||||||
[theme.breakpoints.up('md')]: {
|
[theme.breakpoints.up('md')]: {
|
||||||
margin: theme.spacing(2, 2, 0, 1),
|
margin: theme.spacing(2, 2, 0, config.featureToggles.dockedMegaMenu ? 2 : 1),
|
||||||
padding: theme.spacing(3),
|
padding: theme.spacing(3),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -722,6 +722,11 @@
|
|||||||
"kiosk": {
|
"kiosk": {
|
||||||
"tv-alert": "Drücke ESC, um den Kiosk-Modus zu verlassen"
|
"tv-alert": "Drücke ESC, um den Kiosk-Modus zu verlassen"
|
||||||
},
|
},
|
||||||
|
"megamenu": {
|
||||||
|
"close": "",
|
||||||
|
"dock": "",
|
||||||
|
"undock": ""
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"enable-kiosk": "Kiosk-Modus aktivieren",
|
"enable-kiosk": "Kiosk-Modus aktivieren",
|
||||||
"toggle-menu": "Menü umschalten",
|
"toggle-menu": "Menü umschalten",
|
||||||
|
@ -722,6 +722,11 @@
|
|||||||
"kiosk": {
|
"kiosk": {
|
||||||
"tv-alert": "Press ESC to exit kiosk mode"
|
"tv-alert": "Press ESC to exit kiosk mode"
|
||||||
},
|
},
|
||||||
|
"megamenu": {
|
||||||
|
"close": "Close menu",
|
||||||
|
"dock": "Dock menu",
|
||||||
|
"undock": "Undock menu"
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"enable-kiosk": "Enable kiosk mode",
|
"enable-kiosk": "Enable kiosk mode",
|
||||||
"toggle-menu": "Toggle menu",
|
"toggle-menu": "Toggle menu",
|
||||||
|
@ -728,6 +728,11 @@
|
|||||||
"kiosk": {
|
"kiosk": {
|
||||||
"tv-alert": "Pulse ESC para salir del modo de quiosco"
|
"tv-alert": "Pulse ESC para salir del modo de quiosco"
|
||||||
},
|
},
|
||||||
|
"megamenu": {
|
||||||
|
"close": "",
|
||||||
|
"dock": "",
|
||||||
|
"undock": ""
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"enable-kiosk": "Activar el modo de quiosco",
|
"enable-kiosk": "Activar el modo de quiosco",
|
||||||
"toggle-menu": "Activar o desactivar menú",
|
"toggle-menu": "Activar o desactivar menú",
|
||||||
|
@ -728,6 +728,11 @@
|
|||||||
"kiosk": {
|
"kiosk": {
|
||||||
"tv-alert": "Appuyez sur ESC pour quitter le mode kiosque"
|
"tv-alert": "Appuyez sur ESC pour quitter le mode kiosque"
|
||||||
},
|
},
|
||||||
|
"megamenu": {
|
||||||
|
"close": "",
|
||||||
|
"dock": "",
|
||||||
|
"undock": ""
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"enable-kiosk": "Activer le mode kiosque",
|
"enable-kiosk": "Activer le mode kiosque",
|
||||||
"toggle-menu": "Afficher/Masquer le menu",
|
"toggle-menu": "Afficher/Masquer le menu",
|
||||||
|
@ -722,6 +722,11 @@
|
|||||||
"kiosk": {
|
"kiosk": {
|
||||||
"tv-alert": "Přęşş ĒŜC ŧő ęχįŧ ĸįőşĸ mőđę"
|
"tv-alert": "Přęşş ĒŜC ŧő ęχįŧ ĸįőşĸ mőđę"
|
||||||
},
|
},
|
||||||
|
"megamenu": {
|
||||||
|
"close": "Cľőşę męʼnū",
|
||||||
|
"dock": "Đőčĸ męʼnū",
|
||||||
|
"undock": "Ůʼnđőčĸ męʼnū"
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"enable-kiosk": "Ēʼnäþľę ĸįőşĸ mőđę",
|
"enable-kiosk": "Ēʼnäþľę ĸįőşĸ mőđę",
|
||||||
"toggle-menu": "Ŧőģģľę męʼnū",
|
"toggle-menu": "Ŧőģģľę męʼnū",
|
||||||
|
@ -716,6 +716,11 @@
|
|||||||
"kiosk": {
|
"kiosk": {
|
||||||
"tv-alert": "按 ESC 退出 kiosk 模式"
|
"tv-alert": "按 ESC 退出 kiosk 模式"
|
||||||
},
|
},
|
||||||
|
"megamenu": {
|
||||||
|
"close": "",
|
||||||
|
"dock": "",
|
||||||
|
"undock": ""
|
||||||
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"enable-kiosk": "启用 kiosk 模式",
|
"enable-kiosk": "启用 kiosk 模式",
|
||||||
"toggle-menu": "切换菜单",
|
"toggle-menu": "切换菜单",
|
||||||
|
Loading…
Reference in New Issue
Block a user