From a59c4e2fccdc2ba8c0cd94ee2dd90571c1bc8cb1 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Fri, 14 Nov 2025 15:26:31 +0100 Subject: [PATCH] Dashboards: Wrap links and data layer controls in divs to make them behave like variables (#113927) Wrap links and data layer controls in divs to make them float together with variables --- .../panel-edit/PanelEditControls.tsx | 22 ++++++- .../scene/DashboardControls.tsx | 32 +++++----- .../scene/DashboardDataLayerControls.tsx | 19 +++++- .../scene/DashboardLinkRenderer.tsx | 20 +++++- .../SubMenu/DashboardLinksDashboard.tsx | 62 +++++++++++-------- 5 files changed, 109 insertions(+), 46 deletions(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelEditControls.tsx b/public/app/features/dashboard-scene/panel-edit/PanelEditControls.tsx index 44e1c63e510..1e71ed7f77d 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelEditControls.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelEditControls.tsx @@ -1,6 +1,9 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { t } from '@grafana/i18n'; -import { InlineSwitch } from '@grafana/ui'; +import { InlineSwitch, useStyles2 } from '@grafana/ui'; import { PanelEditor } from './PanelEditor'; @@ -10,9 +13,10 @@ export interface Props { export function PanelEditControls({ panelEditor }: Props) { const { tableView, dataPane } = panelEditor.useState(); + const styles = useStyles2(getStyles); return ( - <> +
{dataPane && ( )} - +
); } + +function getStyles(theme: GrafanaTheme2) { + return { + container: css({ + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle', + marginBottom: theme.spacing(1), + marginRight: theme.spacing(1), + }), + }; +} diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 714c1a81bb3..686a7f785e0 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -15,7 +15,7 @@ import { SceneObjectUrlValues, CancelActivationHandler, } from '@grafana/scenes'; -import { Box, Stack, useStyles2 } from '@grafana/ui'; +import { Box, useStyles2 } from '@grafana/ui'; import { PanelEditControls } from '../panel-edit/PanelEditControls'; import { getDashboardSceneFor } from '../utils/utils'; @@ -163,12 +163,15 @@ function DashboardControlsRenderer({ model }: SceneComponentProps - {!hideTimeControls && ( -
- - -
- )} +
+ {!hideTimeControls && ( +
+ + +
+ )} + {!hideDashboardControls && model.hasDashboardControls() && } +
{!hideVariableControls && ( <> @@ -177,11 +180,6 @@ function DashboardControlsRenderer({ model }: SceneComponentProps} {editPanel && } - {!hideDashboardControls && model.hasDashboardControls() && ( - - - - )} {showDebugger && } ); @@ -230,14 +228,20 @@ function getStyles(theme: GrafanaTheme2) { background: 'unset', position: 'unset', }), - timeControls: css({ + rightControls: css({ display: 'flex', justifyContent: 'flex-end', gap: theme.spacing(1), marginBottom: theme.spacing(1), float: 'right', + alignItems: 'center', }), - timeControlsWrap: css({ + timeControls: css({ + display: 'flex', + justifyContent: 'flex-end', + gap: theme.spacing(1), + }), + rightControlsWrap: css({ flexWrap: 'wrap', marginLeft: 'auto', }), diff --git a/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx b/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx index 1d5ef0f3866..24f7e2dbd79 100644 --- a/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardDataLayerControls.tsx @@ -1,4 +1,8 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; import { SceneDataLayerProvider, sceneGraph } from '@grafana/scenes'; +import { useStyles2 } from '@grafana/ui'; import { isDashboardDataLayerSetState } from './DashboardDataLayerSet'; import { DashboardScene } from './DashboardScene'; @@ -12,12 +16,15 @@ export function DashboardDataLayerControls({ dashboard }: { dashboard: Dashboard // It is possible to render the controls for the annotation data layers in separate places using the `placement` property. // In case it's not specified, we are rendering the controls here (default). const isDefaultPlacement = (layer: SceneDataLayerProvider) => layer.state.placement === undefined; + const styles = useStyles2(getStyles); if (isDashboardDataLayerSetState(state)) { return ( <> {state.annotationLayers.filter(isDefaultPlacement).map((layer) => ( - +
+ +
))} ); @@ -25,3 +32,13 @@ export function DashboardDataLayerControls({ dashboard }: { dashboard: Dashboard return null; } + +const getStyles = (theme: GrafanaTheme2) => ({ + container: css({ + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle', + marginBottom: theme.spacing(1), + marginRight: theme.spacing(1), + }), +}); diff --git a/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx index be6c5d772bc..e7bc346642b 100644 --- a/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardLinkRenderer.tsx @@ -1,7 +1,10 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; import { sanitizeUrl } from '@grafana/data/internal'; import { selectors } from '@grafana/e2e-selectors'; import { DashboardLink } from '@grafana/schema'; -import { MenuItem, Tooltip } from '@grafana/ui'; +import { MenuItem, Tooltip, useStyles2 } from '@grafana/ui'; import { DashboardLinkButton, DashboardLinksDashboard, @@ -19,6 +22,7 @@ export interface Props { export function DashboardLinkRenderer({ link, dashboardUID, inMenu }: Props) { const linkInfo = getLinkSrv().getAnchorInfo(link); + const styles = useStyles2(getStyles); if (link.type === 'dashboards') { return ; @@ -47,8 +51,20 @@ export function DashboardLinkRenderer({ link, dashboardUID, inMenu }: Props) { ); return ( -
+
{link.tooltip ? {linkElement} : linkElement}
); } + +function getStyles(theme: GrafanaTheme2) { + return { + linkContainer: css({ + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle', + marginBottom: theme.spacing(1), + marginRight: theme.spacing(1), + }), + }; +} diff --git a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx index af9f6702832..43d1dda185f 100644 --- a/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx +++ b/public/app/features/dashboard/components/SubMenu/DashboardLinksDashboard.tsx @@ -74,20 +74,22 @@ export const DashboardLinksDashboard = ({ link, linkInfo, dashboardUID }: Props) if (link.asDropdown) { return ( - }> - - - {title} - - +
+ }> + + + {title} + + +
); } @@ -96,18 +98,19 @@ export const DashboardLinksDashboard = ({ link, linkInfo, dashboardUID }: Props) {resolvedLinks.length > 0 && resolvedLinks.map((resolvedLink, index) => { return ( - - {resolvedLink.title} - +
+ + {resolvedLink.title} + +
); })} @@ -174,6 +177,13 @@ function getStyles(theme: GrafanaTheme2) { paddingLeft: theme.spacing(1), paddingRight: theme.spacing(1), }), + linkContainer: css({ + display: 'inline-flex', + alignItems: 'center', + verticalAlign: 'middle', + marginBottom: theme.spacing(1), + marginRight: theme.spacing(1), + }), }; }