mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Navigation: Add help menu to top search bar (#55062)
* add help menu to top search bar * fixes * handle preventDefault in node graph specifically * use icon prop of MenuItem * undo changes to ContextMenuPlugin/DataLinksContextMenu * remove unused component * revert storybook changes * Tweaks * remove unused style * stop propagation on the header so version can be highlighted * make sure useContextMenu has the exact same logic as before Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
@@ -16,59 +16,47 @@ export function TopNavBarMenu({ node }: TopNavBarMenuProps) {
|
||||
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} />
|
||||
<Menu
|
||||
header={
|
||||
<div onClick={(e) => e.stopPropagation()} className={styles.header}>
|
||||
<div>{node.text}</div>
|
||||
{node.subTitle && <div className={styles.subTitle}>{node.subTitle}</div>}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{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} />
|
||||
const showExternalLinkIcon = /^https?:\/\//.test(item.url || '');
|
||||
return item.url ? (
|
||||
<MenuItem
|
||||
url={item.url}
|
||||
label={itemText}
|
||||
icon={showExternalLinkIcon ? 'external-link-alt' : undefined}
|
||||
target={item.target}
|
||||
key={item.id}
|
||||
/>
|
||||
) : (
|
||||
<MenuItem onClick={() => onNavigate(item)} label={itemText} key={item.id} />
|
||||
<MenuItem icon={item.icon} onClick={item.onClick} 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)}`,
|
||||
fontSize: theme.typography.h5.fontSize,
|
||||
fontWeight: theme.typography.h5.fontWeight,
|
||||
padding: theme.spacing(0.5, 1),
|
||||
whiteSpace: 'nowrap',
|
||||
width: '100%',
|
||||
background: theme.colors.background.secondary,
|
||||
}),
|
||||
subTitle: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
@@ -44,6 +44,7 @@ export function TopSearchBar() {
|
||||
toggleSwitcherModal
|
||||
).map((item) => enrichWithInteractionTracking(item, false));
|
||||
|
||||
const helpNode = configItems.find((item) => item.id === 'help');
|
||||
const profileNode = configItems.find((item) => item.id === 'profile');
|
||||
const signInNode = configItems.find((item) => item.id === 'signin');
|
||||
|
||||
@@ -64,11 +65,13 @@ export function TopSearchBar() {
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
<Tooltip placement="bottom" content="Help menu (todo)">
|
||||
<button className={styles.actionItem}>
|
||||
<Icon name="question-circle" size="lg" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{helpNode && (
|
||||
<Dropdown overlay={<TopNavBarMenu node={helpNode} />}>
|
||||
<button className={styles.actionItem}>
|
||||
<Icon name="question-circle" size="lg" />
|
||||
</button>
|
||||
</Dropdown>
|
||||
)}
|
||||
<Tooltip placement="bottom" content="Grafana news (todo)">
|
||||
<button className={styles.actionItem}>
|
||||
<Icon name="rss" size="lg" />
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import { LinkTarget } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Icon, IconName } from '@grafana/ui';
|
||||
|
||||
export interface FooterLink {
|
||||
target: LinkTarget;
|
||||
text: string;
|
||||
id?: string;
|
||||
id: string;
|
||||
icon?: IconName;
|
||||
url?: string;
|
||||
}
|
||||
@@ -13,16 +15,22 @@ export interface FooterLink {
|
||||
export let getFooterLinks = (): FooterLink[] => {
|
||||
return [
|
||||
{
|
||||
target: '_blank',
|
||||
id: 'documentation',
|
||||
text: 'Documentation',
|
||||
icon: 'document-info',
|
||||
url: 'https://grafana.com/docs/grafana/latest/?utm_source=grafana_footer',
|
||||
},
|
||||
{
|
||||
target: '_blank',
|
||||
id: 'support',
|
||||
text: 'Support',
|
||||
icon: 'question-circle',
|
||||
url: 'https://grafana.com/products/enterprise/?utm_source=grafana_footer',
|
||||
},
|
||||
{
|
||||
target: '_blank',
|
||||
id: 'community',
|
||||
text: 'Community',
|
||||
icon: 'comments-alt',
|
||||
url: 'https://community.grafana.com/?utm_source=grafana_footer',
|
||||
@@ -45,7 +53,12 @@ export let getVersionLinks = (): FooterLink[] => {
|
||||
const links: FooterLink[] = [];
|
||||
const stateInfo = licenseInfo.stateInfo ? ` (${licenseInfo.stateInfo})` : '';
|
||||
|
||||
links.push({ text: `${buildInfo.edition}${stateInfo}`, url: licenseInfo.licenseUrl });
|
||||
links.push({
|
||||
target: '_blank',
|
||||
id: 'version',
|
||||
text: `${buildInfo.edition}${stateInfo}`,
|
||||
url: licenseInfo.licenseUrl,
|
||||
});
|
||||
|
||||
if (buildInfo.hideVersion) {
|
||||
return links;
|
||||
@@ -56,6 +69,8 @@ export let getVersionLinks = (): FooterLink[] => {
|
||||
const docsVersion = isBeta ? 'next' : 'latest';
|
||||
|
||||
links.push({
|
||||
target: '_blank',
|
||||
id: 'version',
|
||||
text: `v${buildInfo.version} (${buildInfo.commit})`,
|
||||
url: hasReleaseNotes
|
||||
? `https://grafana.com/docs/grafana/${docsVersion}/release-notes/release-notes-${versionSlug}/`
|
||||
@@ -64,6 +79,7 @@ export let getVersionLinks = (): FooterLink[] => {
|
||||
|
||||
if (buildInfo.hasUpdate) {
|
||||
links.push({
|
||||
target: '_blank',
|
||||
id: 'updateVersion',
|
||||
text: `New version available!`,
|
||||
icon: 'download-alt',
|
||||
@@ -109,7 +125,7 @@ Footer.displayName = 'Footer';
|
||||
|
||||
function FooterItem({ item }: { item: FooterLink }) {
|
||||
const content = item.url ? (
|
||||
<a href={item.url} target="_blank" rel="noopener noreferrer" id={item.id}>
|
||||
<a href={item.url} target={item.target} rel="noopener noreferrer" id={item.id}>
|
||||
{item.text}
|
||||
</a>
|
||||
) : (
|
||||
|
Reference in New Issue
Block a user