diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx
index 0aa35fca698..9b3ca4c3a81 100644
--- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx
+++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx
@@ -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);
return (
diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelDescription.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelDescription.tsx
index be82080da43..33b7d876bfe 100644
--- a/packages/grafana-ui/src/components/PanelChrome/PanelDescription.tsx
+++ b/packages/grafana-ui/src/components/PanelChrome/PanelDescription.tsx
@@ -1,20 +1,17 @@
import { css } from '@emotion/css';
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 { Tooltip } from '../Tooltip';
+import { TitleItem } from './TitleItem';
+
interface Props {
description: string | (() => string);
}
export function PanelDescription({ description }: Props) {
- const theme = useTheme2();
- const styles = getStyles(theme);
+ const styles = getStyles();
const getDescriptionContent = (): JSX.Element => {
// description
@@ -29,39 +26,16 @@ export function PanelDescription({ description }: Props) {
return description !== '' ? (
-
-
-
+
+
+
) : null;
}
-const getStyles = (theme: GrafanaTheme2) => {
+const getStyles = () => {
return {
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: {
whiteSpace: 'normal',
wordWrap: 'break-word',
diff --git a/packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx b/packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx
new file mode 100644
index 00000000000..b6d1d4fd8cd
--- /dev/null
+++ b/packages/grafana-ui/src/components/PanelChrome/TitleItem.tsx
@@ -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(
+ ({ className, children, href, onClick, target, title, ...rest }, ref) => {
+ const styles = useStyles2(getStyles);
+
+ if (href) {
+ return (
+
+ {children}
+
+ );
+ } else {
+ return (
+
+ {children}
+
+ );
+ }
+ }
+);
+
+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}`,
+ },
+ }),
+ };
+};
diff --git a/packages/grafana-ui/src/components/PanelChrome/index.ts b/packages/grafana-ui/src/components/PanelChrome/index.ts
index 22cf206bfbd..3f7e8845414 100644
--- a/packages/grafana-ui/src/components/PanelChrome/index.ts
+++ b/packages/grafana-ui/src/components/PanelChrome/index.ts
@@ -3,6 +3,7 @@ import React from 'react';
import { ErrorIndicator } from './ErrorIndicator';
import { LoadingIndicator } from './LoadingIndicator';
import { PanelChrome as PanelChromeComponent, PanelChromeProps } from './PanelChrome';
+import { TitleItem } from './TitleItem';
/**
* @internal
@@ -15,6 +16,7 @@ export type { PanelChromeProps, PanelPadding } from './PanelChrome';
export interface PanelChromeType extends React.FC {
LoadingIndicator: typeof LoadingIndicator;
ErrorIndicator: typeof ErrorIndicator;
+ TitleItem: typeof TitleItem;
}
/**
@@ -23,6 +25,7 @@ export interface PanelChromeType extends React.FC {
export const PanelChrome = PanelChromeComponent as PanelChromeType;
PanelChrome.LoadingIndicator = LoadingIndicator;
PanelChrome.ErrorIndicator = ErrorIndicator;
+PanelChrome.TitleItem = TitleItem;
/**
* Exporting the components for extensibility and since it is a good practice
diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts
index 6adfe0fd6c4..f5e93ebaaa8 100644
--- a/packages/grafana-ui/src/components/index.ts
+++ b/packages/grafana-ui/src/components/index.ts
@@ -33,6 +33,7 @@ export { UnitPicker } from './UnitPicker/UnitPicker';
export { StatsPicker } from './StatsPicker/StatsPicker';
export { RefreshPicker, defaultIntervals } from './RefreshPicker/RefreshPicker';
export { TimeRangePicker, type TimeRangePickerProps } from './DateTimePickers/TimeRangePicker';
+export { TimePickerTooltip } from './DateTimePickers/TimeRangePicker';
export { TimeOfDayPicker } from './DateTimePickers/TimeOfDayPicker';
export { TimeZonePicker } from './DateTimePickers/TimeZonePicker';
export { WeekStartPicker } from './DateTimePickers/WeekStartPicker';
diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderTitleItems.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderTitleItems.tsx
index 91967b2ab52..de802fc6fad 100644
--- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderTitleItems.tsx
+++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderTitleItems.tsx
@@ -2,8 +2,7 @@ import { css, cx } from '@emotion/css';
import React from 'react';
import { PanelData, GrafanaTheme2, PanelModel, LinkModel, AlertState, DataLink } from '@grafana/data';
-import { Icon, Tooltip, useStyles2 } from '@grafana/ui';
-import { getFocusStyles, getMouseFocusStyles } from '@grafana/ui/src/themes/mixins';
+import { Icon, PanelChrome, Tooltip, useStyles2, TimePickerTooltip } from '@grafana/ui';
import { PanelLinks } from '../PanelLinks';
@@ -24,28 +23,28 @@ export function PanelHeaderTitleItems(props: Props) {
// panel health
const alertStateItem = (
-
-
+
);
const timeshift = (
<>
-
-
-
- {data.request?.timeInfo}
-
-
+ {data.request && data.request.timeInfo && (
+ }>
+
+
+ {data.request?.timeInfo}
+
+
+ )}
>
);
@@ -56,7 +55,7 @@ export function PanelHeaderTitleItems(props: Props) {
)}
{}
- {data.request && data.request.timeInfo && timeshift}
+ {timeshift}
{alertState && alertStateItem}
>
);
@@ -64,29 +63,6 @@ export function PanelHeaderTitleItems(props: Props) {
const getStyles = (theme: GrafanaTheme2) => {
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({
color: theme.colors.success.text,
}),
diff --git a/public/app/features/dashboard/dashgrid/PanelLinks.tsx b/public/app/features/dashboard/dashgrid/PanelLinks.tsx
index 706d29fa0ba..ae1e4444b5e 100644
--- a/public/app/features/dashboard/dashgrid/PanelLinks.tsx
+++ b/public/app/features/dashboard/dashgrid/PanelLinks.tsx
@@ -2,8 +2,7 @@ import { css } from '@emotion/css';
import React from 'react';
import { DataLink, GrafanaTheme2, LinkModel } from '@grafana/data';
-import { Dropdown, Icon, Menu, ToolbarButton, useStyles2 } from '@grafana/ui';
-import { getFocusStyles, getMouseFocusStyles } from '@grafana/ui/src/themes/mixins';
+import { Dropdown, Icon, Menu, ToolbarButton, useStyles2, PanelChrome } from '@grafana/ui';
interface Props {
panelLinks: DataLink[];
@@ -27,15 +26,14 @@ export function PanelLinks({ panelLinks, onShowPanelLinks }: Props) {
if (panelLinks.length === 1) {
const linkModel = onShowPanelLinks()[0];
return (
-
-
+
);
} else {
return (
@@ -53,25 +51,5 @@ const getStyles = (theme: GrafanaTheme2) => {
borderRadius: `${theme.shape.borderRadius()}`,
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}`,
- },
- }),
};
};