mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 02:10:45 -06:00
Navigation: Adds profile menu to top nav bar (#54720)
This commit is contained in:
parent
9cc828a00e
commit
ab79976a8d
@ -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,
|
||||
}),
|
||||
};
|
||||
};
|
@ -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({
|
||||
|
Loading…
Reference in New Issue
Block a user