diff --git a/public/app/core/components/AppChrome/TopBar/TopNavBarMenu.tsx b/public/app/core/components/AppChrome/TopBar/TopNavBarMenu.tsx new file mode 100644 index 00000000000..4793edae3aa --- /dev/null +++ b/public/app/core/components/AppChrome/TopBar/TopNavBarMenu.tsx @@ -0,0 +1,74 @@ +import { css } from '@emotion/css'; +import { i18n } from '@lingui/core'; +import React from 'react'; + +import { GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { Menu, MenuItem, useStyles2 } from '@grafana/ui'; + +import menuItemTranslations from '../../NavBar/navBarItem-translations'; + +export interface TopNavBarMenuProps { + node: NavModelItem; +} + +export function TopNavBarMenu({ node }: TopNavBarMenuProps) { + const styles = useStyles2(getStyles); + if (!node) { + return null; + } + const onNavigate = (item: NavModelItem) => { + const { url, target, onClick } = item; + onClick?.(); + + if (url) { + window.open(url, target); + } + }; + + return ( + + + {node.children?.map((item) => { + const translationKey = item.id && menuItemTranslations[item.id]; + const itemText = translationKey ? i18n._(translationKey) : item.text; + + return !item.target && item.url?.startsWith('/') ? ( + + ) : ( + onNavigate(item)} label={itemText} key={item.id} /> + ); + })} + {node.subTitle && ( + // Stopping the propagation of the event when clicking the subTitle so the menu + // does not close +
e.stopPropagation()} className={styles.subtitle}> + {node.subTitle} +
+ )} +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + subtitle: css` + background-color: transparent; + border-top: 1px solid ${theme.colors.border.weak}; + color: ${theme.colors.text.secondary}; + font-size: ${theme.typography.bodySmall.fontSize}; + font-weight: ${theme.typography.bodySmall.fontWeight}; + padding: ${theme.spacing(1)} ${theme.spacing(2)} ${theme.spacing(1)}; + text-align: left; + white-space: nowrap; + `, + header: css({ + height: `calc(${theme.spacing(6)} - 1px)`, + fontSize: theme.typography.h4.fontSize, + fontWeight: theme.typography.h4.fontWeight, + padding: `${theme.spacing(1)} ${theme.spacing(2)}`, + whiteSpace: 'nowrap', + width: '100%', + background: theme.colors.background.secondary, + }), + }; +}; diff --git a/public/app/core/components/AppChrome/TopSearchBar.tsx b/public/app/core/components/AppChrome/TopSearchBar.tsx index 54cae1fc30e..b056dbf3ad0 100644 --- a/public/app/core/components/AppChrome/TopSearchBar.tsx +++ b/public/app/core/components/AppChrome/TopSearchBar.tsx @@ -1,14 +1,38 @@ import { css } from '@emotion/css'; -import React from 'react'; +import { cloneDeep } from 'lodash'; +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; -import { GrafanaTheme2 } from '@grafana/data'; -import { Dropdown, FilterInput, Icon, Menu, MenuItem, Tooltip, useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2, NavSection } from '@grafana/data'; +import { Dropdown, FilterInput, Icon, Tooltip, useStyles2, toIconName } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; +import { StoreState } from 'app/types'; +import { enrichConfigItems, enrichWithInteractionTracking } from '../NavBar/utils'; +import { OrgSwitcher } from '../OrgSwitcher'; + +import { TopNavBarMenu } from './TopBar/TopNavBarMenu'; import { TOP_BAR_LEVEL_HEIGHT } from './types'; export function TopSearchBar() { const styles = useStyles2(getStyles); + const location = useLocation(); + const navBarTree = useSelector((state: StoreState) => state.navBarTree); + const navTree = cloneDeep(navBarTree); + const [showSwitcherModal, setShowSwitcherModal] = useState(false); + const toggleSwitcherModal = () => { + setShowSwitcherModal(!showSwitcherModal); + }; + const configItems = enrichConfigItems( + navTree.filter((item) => item.section === NavSection.Config), + location, + toggleSwitcherModal + ).map((item) => enrichWithInteractionTracking(item, false)); + + const profileNode = configItems.find((item) => item.id === 'profile'); + const signInNode = configItems.find((item) => item.id === 'signin'); + const signInIconName = signInNode?.icon && toIconName(signInNode.icon); return (
@@ -31,31 +55,26 @@ export function TopSearchBar() { - - + {signInNode && ( + + + {signInIconName && } + + + )} + {profileNode && ( + }> - + )}
+ {showSwitcherModal && } ); } -/** - * This is just temporary, needs syncing with the backend option like DisableSignoutMenu - */ -export function ProfileMenu() { - return ( - - - - - - ); -} - const getStyles = (theme: GrafanaTheme2) => { return { container: css({