Navigation: Adds profile menu to top nav bar (#54720)

This commit is contained in:
Joao Silva 2022-09-06 17:32:05 +01:00 committed by GitHub
parent 9cc828a00e
commit ab79976a8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 112 additions and 19 deletions

View File

@ -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 (
<Menu>
<MenuItem url={node.url} label={node.text} className={styles.header} />
{node.children?.map((item) => {
const translationKey = item.id && menuItemTranslations[item.id];
const itemText = translationKey ? i18n._(translationKey) : item.text;
return !item.target && item.url?.startsWith('/') ? (
<MenuItem url={item.url} label={itemText} key={item.id} />
) : (
<MenuItem onClick={() => 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
<div onClick={(e) => e.stopPropagation()} className={styles.subtitle}>
{node.subTitle}
</div>
)}
</Menu>
);
}
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,
}),
};
};

View File

@ -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 (
<div className={styles.container}>
@ -31,31 +55,26 @@ export function TopSearchBar() {
<Icon name="rss" size="lg" />
</button>
</Tooltip>
<Tooltip placement="bottom" content="User profile (todo)">
<Dropdown overlay={ProfileMenu}>
{signInNode && (
<Tooltip placement="bottom" content="Sign in">
<a className={styles.actionItem} href={signInNode.url} target={signInNode.target}>
{signInIconName && <Icon name={signInIconName} size="lg" />}
</a>
</Tooltip>
)}
{profileNode && (
<Dropdown overlay={<TopNavBarMenu node={profileNode} />}>
<button className={styles.actionItem}>
<img src={contextSrv.user.gravatarUrl} />
</button>
</Dropdown>
</Tooltip>
)}
</div>
{showSwitcherModal && <OrgSwitcher onDismiss={toggleSwitcherModal} />}
</div>
);
}
/**
* This is just temporary, needs syncing with the backend option like DisableSignoutMenu
*/
export function ProfileMenu() {
return (
<Menu>
<MenuItem url="profile" label="Your profile" />
<MenuItem url="profile/notifications" label="Your notifications" />
<MenuItem url="logout" label="Sign out" />
</Menu>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
container: css({