mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 14:29:25 +08:00
[Panel] Extract styling duplication in new TitleItem component (#61625)
This commit is contained in:
@ -181,7 +181,7 @@ const ZoomOutTooltip = () => (
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const TimePickerTooltip = ({ timeRange, timeZone }: { timeRange: TimeRange; timeZone?: TimeZone }) => {
|
export const TimePickerTooltip = ({ timeRange, timeZone }: { timeRange: TimeRange; timeZone?: TimeZone }) => {
|
||||||
const styles = useStyles2(getLabelStyles);
|
const styles = useStyles2(getLabelStyles);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
|
||||||
|
|
||||||
import { useTheme2 } from '../../themes';
|
|
||||||
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
|
|
||||||
import { Icon } from '../Icon/Icon';
|
import { Icon } from '../Icon/Icon';
|
||||||
import { Tooltip } from '../Tooltip';
|
import { Tooltip } from '../Tooltip';
|
||||||
|
|
||||||
|
import { TitleItem } from './TitleItem';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
description: string | (() => string);
|
description: string | (() => string);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PanelDescription({ description }: Props) {
|
export function PanelDescription({ description }: Props) {
|
||||||
const theme = useTheme2();
|
const styles = getStyles();
|
||||||
const styles = getStyles(theme);
|
|
||||||
|
|
||||||
const getDescriptionContent = (): JSX.Element => {
|
const getDescriptionContent = (): JSX.Element => {
|
||||||
// description
|
// description
|
||||||
@ -29,39 +26,16 @@ export function PanelDescription({ description }: Props) {
|
|||||||
|
|
||||||
return description !== '' ? (
|
return description !== '' ? (
|
||||||
<Tooltip interactive content={getDescriptionContent}>
|
<Tooltip interactive content={getDescriptionContent}>
|
||||||
<span className={styles.description}>
|
<TitleItem className={styles.description}>
|
||||||
<Icon name="info-circle" size="lg" aria-label="description" />
|
<Icon name="info-circle" size="lg" title="description" />
|
||||||
</span>
|
</TitleItem>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = () => {
|
||||||
return {
|
return {
|
||||||
description: css({
|
description: css({
|
||||||
color: `${theme.colors.text.secondary}`,
|
|
||||||
backgroundColor: `${theme.colors.background.primary}`,
|
|
||||||
cursor: 'auto',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: `${theme.shape.borderRadius()}`,
|
|
||||||
padding: `${theme.spacing(0, 1)}`,
|
|
||||||
height: ` ${theme.spacing(theme.components.height.md)}`,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
|
|
||||||
'&:focus, &:focus-visible': {
|
|
||||||
...getFocusStyles(theme),
|
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
'&: focus:not(:focus-visible)': getMouseFocusStyles(theme),
|
|
||||||
|
|
||||||
'&:hover ': {
|
|
||||||
boxShadow: `${theme.shadows.z1}`,
|
|
||||||
color: `${theme.colors.text.primary}`,
|
|
||||||
background: `${theme.colors.background.secondary}`,
|
|
||||||
},
|
|
||||||
|
|
||||||
code: {
|
code: {
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
wordWrap: 'break-word',
|
wordWrap: 'break-word',
|
||||||
|
75
packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx
Normal file
75
packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { cx, css } from '@emotion/css';
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
|
||||||
|
import { GrafanaTheme2, LinkModel, LinkTarget } from '@grafana/data';
|
||||||
|
|
||||||
|
import { useStyles2 } from '../../themes';
|
||||||
|
import { getFocusStyles, getMouseFocusStyles } from '../../themes/mixins';
|
||||||
|
|
||||||
|
type TitleItemProps = {
|
||||||
|
className?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: LinkModel['onClick'];
|
||||||
|
href?: string;
|
||||||
|
target?: LinkTarget;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TitleItem = forwardRef<HTMLAnchorElement, TitleItemProps>(
|
||||||
|
({ className, children, href, onClick, target, title, ...rest }, ref) => {
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
ref={ref}
|
||||||
|
href={href}
|
||||||
|
onClick={onClick}
|
||||||
|
target={target}
|
||||||
|
title={title}
|
||||||
|
className={cx(styles.item, className)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<span ref={ref} className={cx(styles.item, className)} {...rest}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
TitleItem.displayName = 'TitleItem';
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
|
return {
|
||||||
|
item: css({
|
||||||
|
color: `${theme.colors.text.secondary}`,
|
||||||
|
label: 'panel-header-item',
|
||||||
|
backgroundColor: `${theme.colors.background.primary}`,
|
||||||
|
cursor: 'auto',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: `${theme.shape.borderRadius()}`,
|
||||||
|
padding: `${theme.spacing(0, 1)}`,
|
||||||
|
height: `${theme.spacing(theme.components.height.md)}`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
|
||||||
|
'&:focus, &:focus-visible': {
|
||||||
|
...getFocusStyles(theme),
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
|
'&: focus:not(:focus-visible)': getMouseFocusStyles(theme),
|
||||||
|
|
||||||
|
'&:hover ': {
|
||||||
|
boxShadow: `${theme.shadows.z1}`,
|
||||||
|
background: `${theme.colors.background.secondary}`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||||||
import { ErrorIndicator } from './ErrorIndicator';
|
import { ErrorIndicator } from './ErrorIndicator';
|
||||||
import { LoadingIndicator } from './LoadingIndicator';
|
import { LoadingIndicator } from './LoadingIndicator';
|
||||||
import { PanelChrome as PanelChromeComponent, PanelChromeProps } from './PanelChrome';
|
import { PanelChrome as PanelChromeComponent, PanelChromeProps } from './PanelChrome';
|
||||||
|
import { TitleItem } from './TitleItem';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -15,6 +16,7 @@ export type { PanelChromeProps, PanelPadding } from './PanelChrome';
|
|||||||
export interface PanelChromeType extends React.FC<PanelChromeProps> {
|
export interface PanelChromeType extends React.FC<PanelChromeProps> {
|
||||||
LoadingIndicator: typeof LoadingIndicator;
|
LoadingIndicator: typeof LoadingIndicator;
|
||||||
ErrorIndicator: typeof ErrorIndicator;
|
ErrorIndicator: typeof ErrorIndicator;
|
||||||
|
TitleItem: typeof TitleItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,6 +25,7 @@ export interface PanelChromeType extends React.FC<PanelChromeProps> {
|
|||||||
export const PanelChrome = PanelChromeComponent as PanelChromeType;
|
export const PanelChrome = PanelChromeComponent as PanelChromeType;
|
||||||
PanelChrome.LoadingIndicator = LoadingIndicator;
|
PanelChrome.LoadingIndicator = LoadingIndicator;
|
||||||
PanelChrome.ErrorIndicator = ErrorIndicator;
|
PanelChrome.ErrorIndicator = ErrorIndicator;
|
||||||
|
PanelChrome.TitleItem = TitleItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exporting the components for extensibility and since it is a good practice
|
* Exporting the components for extensibility and since it is a good practice
|
||||||
|
@ -33,6 +33,7 @@ export { UnitPicker } from './UnitPicker/UnitPicker';
|
|||||||
export { StatsPicker } from './StatsPicker/StatsPicker';
|
export { StatsPicker } from './StatsPicker/StatsPicker';
|
||||||
export { RefreshPicker, defaultIntervals } from './RefreshPicker/RefreshPicker';
|
export { RefreshPicker, defaultIntervals } from './RefreshPicker/RefreshPicker';
|
||||||
export { TimeRangePicker, type TimeRangePickerProps } from './DateTimePickers/TimeRangePicker';
|
export { TimeRangePicker, type TimeRangePickerProps } from './DateTimePickers/TimeRangePicker';
|
||||||
|
export { TimePickerTooltip } from './DateTimePickers/TimeRangePicker';
|
||||||
export { TimeOfDayPicker } from './DateTimePickers/TimeOfDayPicker';
|
export { TimeOfDayPicker } from './DateTimePickers/TimeOfDayPicker';
|
||||||
export { TimeZonePicker } from './DateTimePickers/TimeZonePicker';
|
export { TimeZonePicker } from './DateTimePickers/TimeZonePicker';
|
||||||
export { WeekStartPicker } from './DateTimePickers/WeekStartPicker';
|
export { WeekStartPicker } from './DateTimePickers/WeekStartPicker';
|
||||||
|
@ -2,8 +2,7 @@ import { css, cx } from '@emotion/css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { PanelData, GrafanaTheme2, PanelModel, LinkModel, AlertState, DataLink } from '@grafana/data';
|
import { PanelData, GrafanaTheme2, PanelModel, LinkModel, AlertState, DataLink } from '@grafana/data';
|
||||||
import { Icon, Tooltip, useStyles2 } from '@grafana/ui';
|
import { Icon, PanelChrome, Tooltip, useStyles2, TimePickerTooltip } from '@grafana/ui';
|
||||||
import { getFocusStyles, getMouseFocusStyles } from '@grafana/ui/src/themes/mixins';
|
|
||||||
|
|
||||||
import { PanelLinks } from '../PanelLinks';
|
import { PanelLinks } from '../PanelLinks';
|
||||||
|
|
||||||
@ -24,28 +23,28 @@ export function PanelHeaderTitleItems(props: Props) {
|
|||||||
// panel health
|
// panel health
|
||||||
const alertStateItem = (
|
const alertStateItem = (
|
||||||
<Tooltip content={`alerting is ${alertState}`}>
|
<Tooltip content={`alerting is ${alertState}`}>
|
||||||
<span
|
<PanelChrome.TitleItem
|
||||||
className={cx(styles.item, {
|
className={cx({
|
||||||
[styles.ok]: alertState === AlertState.OK,
|
[styles.ok]: alertState === AlertState.OK,
|
||||||
[styles.pending]: alertState === AlertState.Pending,
|
[styles.pending]: alertState === AlertState.Pending,
|
||||||
[styles.alerting]: alertState === AlertState.Alerting,
|
[styles.alerting]: alertState === AlertState.Alerting,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Icon name={alertState === 'alerting' ? 'heart-break' : 'heart'} className="panel-alert-icon" />
|
<Icon name={alertState === 'alerting' ? 'heart-break' : 'heart'} className="panel-alert-icon" />
|
||||||
</span>
|
</PanelChrome.TitleItem>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
const timeshift = (
|
const timeshift = (
|
||||||
<>
|
<>
|
||||||
<Tooltip
|
{data.request && data.request.timeInfo && (
|
||||||
content={data.request?.range ? `Time Range: ${data.request.range.from} to ${data.request.range.to}` : ''}
|
<Tooltip content={<TimePickerTooltip timeRange={data.request?.range} timeZone={data.request?.timezone} />}>
|
||||||
>
|
<PanelChrome.TitleItem className={styles.timeshift}>
|
||||||
<span className={cx(styles.item, styles.timeshift)}>
|
<Icon name="clock-nine" />
|
||||||
<Icon name="clock-nine" />
|
{data.request?.timeInfo}
|
||||||
{data.request?.timeInfo}
|
</PanelChrome.TitleItem>
|
||||||
</span>
|
</Tooltip>
|
||||||
</Tooltip>
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -56,7 +55,7 @@ export function PanelHeaderTitleItems(props: Props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{<PanelHeaderNotices panelId={panelId} frames={data.series} />}
|
{<PanelHeaderNotices panelId={panelId} frames={data.series} />}
|
||||||
{data.request && data.request.timeInfo && timeshift}
|
{timeshift}
|
||||||
{alertState && alertStateItem}
|
{alertState && alertStateItem}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -64,29 +63,6 @@ export function PanelHeaderTitleItems(props: Props) {
|
|||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
return {
|
return {
|
||||||
item: css({
|
|
||||||
label: 'panel-header-item',
|
|
||||||
backgroundColor: `${theme.colors.background.primary}`,
|
|
||||||
cursor: 'auto',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: `${theme.shape.borderRadius()}`,
|
|
||||||
padding: `${theme.spacing(0, 1)}`,
|
|
||||||
height: `${theme.spacing(theme.components.height.md)}`,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
|
|
||||||
'&:focus, &:focus-visible': {
|
|
||||||
...getFocusStyles(theme),
|
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
'&: focus:not(:focus-visible)': getMouseFocusStyles(theme),
|
|
||||||
|
|
||||||
'&:hover ': {
|
|
||||||
boxShadow: `${theme.shadows.z1}`,
|
|
||||||
background: `${theme.colors.background.secondary}`,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ok: css({
|
ok: css({
|
||||||
color: theme.colors.success.text,
|
color: theme.colors.success.text,
|
||||||
}),
|
}),
|
||||||
|
@ -2,8 +2,7 @@ import { css } from '@emotion/css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DataLink, GrafanaTheme2, LinkModel } from '@grafana/data';
|
import { DataLink, GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||||
import { Dropdown, Icon, Menu, ToolbarButton, useStyles2 } from '@grafana/ui';
|
import { Dropdown, Icon, Menu, ToolbarButton, useStyles2, PanelChrome } from '@grafana/ui';
|
||||||
import { getFocusStyles, getMouseFocusStyles } from '@grafana/ui/src/themes/mixins';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
panelLinks: DataLink[];
|
panelLinks: DataLink[];
|
||||||
@ -27,15 +26,14 @@ export function PanelLinks({ panelLinks, onShowPanelLinks }: Props) {
|
|||||||
if (panelLinks.length === 1) {
|
if (panelLinks.length === 1) {
|
||||||
const linkModel = onShowPanelLinks()[0];
|
const linkModel = onShowPanelLinks()[0];
|
||||||
return (
|
return (
|
||||||
<a
|
<PanelChrome.TitleItem
|
||||||
href={linkModel.href}
|
href={linkModel.href}
|
||||||
onClick={linkModel.onClick}
|
onClick={linkModel.onClick}
|
||||||
target={linkModel.target}
|
target={linkModel.target}
|
||||||
title={linkModel.title}
|
title={linkModel.title}
|
||||||
className={styles.singleLink}
|
|
||||||
>
|
>
|
||||||
<Icon name="external-link-alt" size="lg" />
|
<Icon name="external-link-alt" size="lg" />
|
||||||
</a>
|
</PanelChrome.TitleItem>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
@ -53,25 +51,5 @@ const getStyles = (theme: GrafanaTheme2) => {
|
|||||||
borderRadius: `${theme.shape.borderRadius()}`,
|
borderRadius: `${theme.shape.borderRadius()}`,
|
||||||
cursor: 'context-menu',
|
cursor: 'context-menu',
|
||||||
}),
|
}),
|
||||||
singleLink: css({
|
|
||||||
color: theme.colors.text.secondary,
|
|
||||||
padding: `${theme.spacing(0, 1)}`,
|
|
||||||
height: ` ${theme.spacing(theme.components.height.md)}`,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
|
|
||||||
'&:focus, &:focus-visible': {
|
|
||||||
...getFocusStyles(theme),
|
|
||||||
zIndex: 1,
|
|
||||||
},
|
|
||||||
'&: focus:not(:focus-visible)': getMouseFocusStyles(theme),
|
|
||||||
|
|
||||||
'&:hover ': {
|
|
||||||
boxShadow: `${theme.shadows.z1}`,
|
|
||||||
color: `${theme.colors.text.primary}`,
|
|
||||||
background: `${theme.colors.background.secondary}`,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user