mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NavModel: Enable adding suffix elements to tabs (#44155)
* Trigger extra events * Extend html attributes * Add suffix to tabs * Add upgrade routes * suffix => highlightText * suffix => suffixText * Add size prop * Update prop name * Convert tabSuffix to a component
This commit is contained in:
parent
a20894c261
commit
55e1c53e36
@ -1,3 +1,5 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
export interface NavModelItem {
|
||||
text: string;
|
||||
url?: string;
|
||||
@ -18,6 +20,7 @@ export interface NavModelItem {
|
||||
onClick?: () => void;
|
||||
menuItemType?: NavMenuItemType;
|
||||
highlightText?: string;
|
||||
tabSuffix?: ComponentType<{ className?: string }>;
|
||||
}
|
||||
|
||||
export enum NavSection {
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { Meta, Props } from '@storybook/addon-docs/blocks';
|
||||
import { Badge } from './Badge';
|
||||
|
||||
<Meta title="MDX|Badge" component={Badge} />
|
||||
|
||||
# Badge
|
||||
## Badge
|
||||
|
||||
The badge component adds meta information to other content, for example about release status or new elements. You can add any `Icon` component or use the badge without an icon.
|
||||
|
||||
<Props of={Badge} />
|
||||
|
@ -3,13 +3,14 @@ import { Story } from '@storybook/react';
|
||||
import { Badge, BadgeProps } from '@grafana/ui';
|
||||
import { iconOptions } from '../../utils/storybook/knobs';
|
||||
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
|
||||
import mdx from './Badge.mdx';
|
||||
|
||||
export default {
|
||||
title: 'Data Display/Badge',
|
||||
component: Badge,
|
||||
decorators: [withCenteredStory],
|
||||
parameters: {
|
||||
docs: {},
|
||||
docs: { page: mdx },
|
||||
},
|
||||
argTypes: {
|
||||
icon: { options: iconOptions, control: { type: 'select' } },
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { NavModelItem } from '@grafana/data';
|
||||
import { IconName } from '../../types';
|
||||
import { TabsBar } from '../Tabs/TabsBar';
|
||||
import { Tab } from '../Tabs/Tab';
|
||||
@ -8,7 +9,7 @@ interface ModalTab {
|
||||
value: string;
|
||||
label: string;
|
||||
icon?: IconName;
|
||||
labelSuffix?: () => JSX.Element;
|
||||
tabSuffix?: NavModelItem['tabSuffix'];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@ -29,7 +30,7 @@ export const ModalTabsHeader: React.FC<Props> = ({ icon, title, tabs, activeTab,
|
||||
key={`${t.value}-${index}`}
|
||||
label={t.label}
|
||||
icon={t.icon}
|
||||
suffix={t.labelSuffix}
|
||||
suffix={t.tabSuffix}
|
||||
active={t.value === activeTab}
|
||||
onChangeTab={() => onChangeTab(t)}
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { HTMLProps } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { GrafanaTheme2, NavModelItem } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { Icon } from '../Icon/Icon';
|
||||
@ -18,11 +18,12 @@ export interface TabProps extends HTMLProps<HTMLAnchorElement> {
|
||||
onChangeTab?: (event?: React.MouseEvent<HTMLAnchorElement>) => void;
|
||||
/** A number rendered next to the text. Usually used to display the number of items in a tab's view. */
|
||||
counter?: number | null;
|
||||
suffix?: () => JSX.Element;
|
||||
/** Extra content, displayed after the tab label and counter */
|
||||
suffix?: NavModelItem['tabSuffix'];
|
||||
}
|
||||
|
||||
export const Tab = React.forwardRef<HTMLAnchorElement, TabProps>(
|
||||
({ label, active, icon, onChangeTab, counter, suffix, className, href, ...otherProps }, ref) => {
|
||||
({ label, active, icon, onChangeTab, counter, suffix: Suffix, className, href, ...otherProps }, ref) => {
|
||||
const theme = useTheme2();
|
||||
const tabsStyles = getTabStyles(theme);
|
||||
const content = () => (
|
||||
@ -30,7 +31,7 @@ export const Tab = React.forwardRef<HTMLAnchorElement, TabProps>(
|
||||
{icon && <Icon name={icon} />}
|
||||
{label}
|
||||
{typeof counter === 'number' && <Counter value={counter} />}
|
||||
{suffix && suffix()}
|
||||
{Suffix && <Suffix className={tabsStyles.suffix} />}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -116,5 +117,8 @@ const getTabStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
background-image: ${theme.colors.gradients.brandHorizontal} !important;
|
||||
}
|
||||
`,
|
||||
suffix: css`
|
||||
margin-left: ${theme.spacing(1)};
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
@ -63,7 +63,7 @@ export function NavBarItemMenuItem({ item, state, onNavigate }: NavBarItemMenuIt
|
||||
</li>
|
||||
{item.value.highlightText && (
|
||||
<li className={styles.upgradeBoxContainer}>
|
||||
<UpgradeBox text={item.value.highlightText} className={styles.upgradeBox} />
|
||||
<UpgradeBox text={item.value.highlightText} className={styles.upgradeBox} size={'sm'} />
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
|
@ -62,6 +62,7 @@ const Navigation = ({ children }: { children: NavModelItem[] }) => {
|
||||
key={`${child.url}-${index}`}
|
||||
icon={child.icon as IconName}
|
||||
href={child.url}
|
||||
suffix={child.tabSuffix}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
40
public/app/core/components/Upgrade/ProBadge.tsx
Normal file
40
public/app/core/components/Upgrade/ProBadge.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { HTMLAttributes, useEffect } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export interface Props extends HTMLAttributes<HTMLSpanElement> {
|
||||
text?: string;
|
||||
/** Function to call when component initializes, e.g. event trackers */
|
||||
onLoad?: (...args: any[]) => void;
|
||||
}
|
||||
|
||||
export const ProBadge = ({ text = 'PRO', className, onLoad, ...htmlProps }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
useEffect(() => {
|
||||
if (onLoad) {
|
||||
onLoad();
|
||||
}
|
||||
}, [onLoad]);
|
||||
|
||||
return (
|
||||
<span className={cx(styles.badge, className)} {...htmlProps}>
|
||||
{text}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
badge: css`
|
||||
margin-left: ${theme.spacing(1.25)};
|
||||
border-radius: ${theme.shape.borderRadius(5)};
|
||||
background-color: ${theme.colors.success.main};
|
||||
padding: ${theme.spacing(0.25, 0.75)};
|
||||
color: ${theme.colors.text.maxContrast};
|
||||
font-weight: ${theme.typography.fontWeightMedium};
|
||||
font-size: ${theme.typography.pxToRem(10)};
|
||||
`,
|
||||
};
|
||||
};
|
@ -3,12 +3,15 @@ import { css, cx } from '@emotion/css';
|
||||
import { Icon, LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
type ComponentSize = 'sm' | 'md';
|
||||
|
||||
export interface Props extends HTMLAttributes<HTMLOrSVGElement> {
|
||||
text: string;
|
||||
size?: ComponentSize;
|
||||
}
|
||||
|
||||
export const UpgradeBox = ({ text, className, ...htmlProps }: Props) => {
|
||||
const styles = useStyles2(getUpgradeBoxStyles);
|
||||
export const UpgradeBox = ({ text, className, size = 'md', ...htmlProps }: Props) => {
|
||||
const styles = useStyles2((theme) => getUpgradeBoxStyles(theme, size));
|
||||
|
||||
return (
|
||||
<div className={cx(styles.box, className)} {...htmlProps}>
|
||||
@ -18,7 +21,7 @@ export const UpgradeBox = ({ text, className, ...htmlProps }: Props) => {
|
||||
<p className={styles.text}>{text}</p>
|
||||
<LinkButton
|
||||
variant="primary"
|
||||
size={'sm'}
|
||||
size={size}
|
||||
className={styles.button}
|
||||
href="https://grafana.com/profile/org/subscription"
|
||||
target="__blank"
|
||||
@ -31,8 +34,9 @@ export const UpgradeBox = ({ text, className, ...htmlProps }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const getUpgradeBoxStyles = (theme: GrafanaTheme2) => {
|
||||
const getUpgradeBoxStyles = (theme: GrafanaTheme2, size: ComponentSize) => {
|
||||
const borderRadius = theme.shape.borderRadius(2);
|
||||
const fontBase = size === 'md' ? 'body' : 'bodySmall';
|
||||
|
||||
return {
|
||||
box: css`
|
||||
@ -43,7 +47,7 @@ const getUpgradeBoxStyles = (theme: GrafanaTheme2) => {
|
||||
border: 1px solid ${theme.colors.primary.shade};
|
||||
padding: ${theme.spacing(2)};
|
||||
color: ${theme.colors.primary.text};
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
font-size: ${theme.typography[fontBase].fontSize};
|
||||
text-align: left;
|
||||
line-height: 16px;
|
||||
`,
|
||||
@ -52,6 +56,12 @@ const getUpgradeBoxStyles = (theme: GrafanaTheme2) => {
|
||||
`,
|
||||
button: css`
|
||||
margin-top: ${theme.spacing(2)};
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: none;
|
||||
color: ${theme.colors.text.primary};
|
||||
outline: 2px solid ${theme.colors.primary.main};
|
||||
}
|
||||
`,
|
||||
icon: css`
|
||||
border: 1px solid ${theme.colors.primary.shade};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { NavModelItem } from '@grafana/data';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
export interface ShareModalTabProps {
|
||||
@ -10,6 +11,6 @@ export interface ShareModalTabProps {
|
||||
export interface ShareModalTabModel {
|
||||
label: string;
|
||||
value: string;
|
||||
labelSuffix?: () => JSX.Element;
|
||||
tabSuffix?: NavModelItem['tabSuffix'];
|
||||
component: React.ComponentType<ShareModalTabProps>;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { featureEnabled } from '@grafana/runtime';
|
||||
import config from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
import { ProBadge } from 'app/core/components/Upgrade/ProBadge';
|
||||
import { GenericDataSourcePlugin } from '../settings/PluginSettings';
|
||||
|
||||
export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDataSourcePlugin): NavModelItem {
|
||||
@ -48,36 +49,60 @@ export function buildNavModel(dataSource: DataSourceSettings, plugin: GenericDat
|
||||
});
|
||||
}
|
||||
|
||||
const dsPermissions = {
|
||||
active: false,
|
||||
icon: 'lock',
|
||||
id: `datasource-permissions-${dataSource.id}`,
|
||||
text: 'Permissions',
|
||||
url: `datasources/edit/${dataSource.id}/permissions`,
|
||||
};
|
||||
|
||||
if (featureEnabled('dspermissions')) {
|
||||
if (contextSrv.hasPermission(AccessControlAction.DataSourcesPermissionsRead)) {
|
||||
navModel.children!.push({
|
||||
active: false,
|
||||
icon: 'lock',
|
||||
id: `datasource-permissions-${dataSource.id}`,
|
||||
text: 'Permissions',
|
||||
url: `datasources/edit/${dataSource.id}/permissions`,
|
||||
});
|
||||
navModel.children!.push(dsPermissions);
|
||||
}
|
||||
}
|
||||
|
||||
if (featureEnabled('analytics')) {
|
||||
} else if (config.featureHighlights.enabled) {
|
||||
navModel.children!.push({
|
||||
active: false,
|
||||
icon: 'info-circle',
|
||||
id: `datasource-insights-${dataSource.id}`,
|
||||
text: 'Insights',
|
||||
url: `datasources/edit/${dataSource.id}/insights`,
|
||||
...dsPermissions,
|
||||
url: dsPermissions.url + '/upgrade',
|
||||
tabSuffix: ProBadge,
|
||||
});
|
||||
}
|
||||
|
||||
if (featureEnabled('caching')) {
|
||||
const analytics = {
|
||||
active: false,
|
||||
icon: 'info-circle',
|
||||
id: `datasource-insights-${dataSource.id}`,
|
||||
text: 'Insights',
|
||||
url: `datasources/edit/${dataSource.id}/insights`,
|
||||
};
|
||||
|
||||
if (featureEnabled('analytics')) {
|
||||
navModel.children!.push(analytics);
|
||||
} else if (config.featureHighlights.enabled) {
|
||||
navModel.children!.push({
|
||||
active: false,
|
||||
icon: 'database',
|
||||
id: `datasource-cache-${dataSource.uid}`,
|
||||
text: 'Cache',
|
||||
url: `datasources/edit/${dataSource.uid}/cache`,
|
||||
hideFromTabs: !pluginMeta.isBackend || !config.caching.enabled,
|
||||
...analytics,
|
||||
url: analytics.url + '/upgrade',
|
||||
tabSuffix: ProBadge,
|
||||
});
|
||||
}
|
||||
|
||||
const caching = {
|
||||
active: false,
|
||||
icon: 'database',
|
||||
id: `datasource-cache-${dataSource.uid}`,
|
||||
text: 'Cache',
|
||||
url: `datasources/edit/${dataSource.uid}/cache`,
|
||||
hideFromTabs: !pluginMeta.isBackend || !config.caching.enabled,
|
||||
};
|
||||
|
||||
if (featureEnabled('caching')) {
|
||||
navModel.children!.push(caching);
|
||||
} else if (config.featureHighlights.enabled) {
|
||||
navModel.children!.push({
|
||||
...caching,
|
||||
url: caching.url + '/upgrade',
|
||||
tabSuffix: ProBadge,
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user