From 8f6dcd01d634325d8a0ea6dfa39746c68279cb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Mar 2025 17:58:05 +0100 Subject: [PATCH] Dashboard: Editing selection redesign (#101763) * Dashboard: Editing selection redesign * Reduce row padding and make it uniform * Minor tweak * Update --- .../components/PanelChrome/PanelChrome.tsx | 14 +++-- .../src/themes/GlobalStyles/dashboardGrid.ts | 11 +++- .../scene/layout-rows/RowItemMenu.tsx | 16 +++--- .../scene/layout-rows/RowItemRenderer.tsx | 51 ++++++++++++++----- .../scene/layout-tabs/TabItemMenu.tsx | 18 +++---- .../scene/layout-tabs/TabItemRenderer.tsx | 20 ++++---- .../layout-tabs/TabsLayoutManagerRenderer.tsx | 6 +-- 7 files changed, 91 insertions(+), 45 deletions(-) diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index b58a973140d..b629dbeee83 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import { CSSProperties, PointerEvent, ReactElement, ReactNode, useId, useRef } from 'react'; +import { CSSProperties, PointerEvent, ReactElement, ReactNode, useId, useRef, useState } from 'react'; import * as React from 'react'; import { useMeasure, useToggle } from 'react-use'; @@ -150,13 +150,18 @@ export function PanelChrome({ const styles = useStyles2(getStyles); const panelContentId = useId(); const panelTitleId = useId().replace(/:/g, '_'); - const { isSelected, onSelect } = useElementSelection(selectionId); + const { isSelected, onSelect, isSelectable } = useElementSelection(selectionId); const pointerDownEvt = useRef(null); const hasHeader = !hoverHeader; const [isOpen, toggleOpen] = useToggle(true); + // Highlight the full panel when hovering over header + const [selectableHighlight, setSelectableHighlight] = useState(false); + const onHeaderEnter = React.useCallback(() => setSelectableHighlight(true), []); + const onHeaderLeave = React.useCallback(() => setSelectableHighlight(false), []); + // if collapsed is not defined, then component is uncontrolled and state is managed internally if (collapsed === undefined) { collapsed = !isOpen; @@ -273,7 +278,8 @@ export function PanelChrome({ className={cx( styles.container, isPanelTransparent && styles.transparentContainer, - isSelected && 'dashboard-selected-element' + isSelected && 'dashboard-selected-element', + !isSelected && isSelectable && selectableHighlight && 'dashboard-selectable-element' )} style={containerStyles} aria-labelledby={!!title ? panelTitleId : undefined} @@ -325,6 +331,8 @@ export function PanelChrome({ pointerDownEvt.current = null; } }} + onMouseEnter={isSelectable ? onHeaderEnter : undefined} + onMouseLeave={isSelectable ? onHeaderLeave : undefined} onPointerUp={(evt) => { evt.stopPropagation(); if ( diff --git a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts index 1f6b410404d..6a87fad092c 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts @@ -69,9 +69,18 @@ export function getDashboardGridStyles(theme: GrafanaTheme2) { }, '.dashboard-selected-element': { - outline: `2px dashed ${theme.colors.primary.border}`, + outline: `1px dashed ${theme.colors.primary.border}`, outlineOffset: '0px', borderRadius: theme.shape.radius.default, }, + + '.dashboard-selectable-element': { + '&:hover': { + outline: `1px dashed ${theme.colors.border.strong}`, + outlineOffset: '0px', + borderRadius: theme.shape.radius.default, + backgroundColor: theme.colors.emphasize(theme.colors.background.canvas, 0.08), + }, + }, }); } diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemMenu.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemMenu.tsx index 496b5130515..1243c07d731 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemMenu.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemMenu.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; -import { Dropdown, Menu, ToolbarButton, ToolbarButtonRow, useStyles2 } from '@grafana/ui'; +import { Button, Dropdown, Menu, ToolbarButtonRow, useStyles2 } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { RowItem } from './RowItem'; @@ -38,16 +38,16 @@ export function RowItemMenu({ model }: RowItemMenuProps) { )} > - Add - + )} > - diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx index 80e6026c7a8..e7af3b4f570 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx @@ -1,10 +1,10 @@ import { css, cx } from '@emotion/css'; -import { useMemo, useRef } from 'react'; +import { useCallback, useMemo, useRef, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; -import { Checkbox, clearButtonStyles, Icon, useElementSelection, useStyles2 } from '@grafana/ui'; +import { clearButtonStyles, Icon, useElementSelection, useStyles2 } from '@grafana/ui'; import { t } from 'app/core/internationalization'; import { isClonedKey } from '../../utils/clone'; @@ -23,25 +23,33 @@ export function RowItemRenderer({ model }: SceneComponentProps) { const titleInterpolated = sceneGraph.interpolate(model, title, undefined, 'text'); const ref = useRef(null); const shouldGrow = !isCollapsed && height === 'expand'; - const { isSelected, onSelect } = useElementSelection(key); + const { isSelected, isSelectable, onSelect } = useElementSelection(key); + + // Highlight the full row when hovering over header + const [selectableHighlight, setSelectableHighlight] = useState(false); + const onHeaderEnter = useCallback(() => setSelectableHighlight(true), []); + const onHeaderLeave = useCallback(() => setSelectableHighlight(false), []); return (
{(!isHeaderHidden || (isEditing && showHiddenElements)) && ( -
- {!isClone && isEditing && ( -
- -
- )} +
)} > - @@ -83,7 +83,7 @@ export function TabItemMenu({ model }: Props) { function getStyles(theme: GrafanaTheme2) { return { container: css({ - gap: theme.spacing(0), + gap: theme.spacing(1), flexShrink: 0, }), }; diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx index 30f4fa4e13a..52f12ffdc7f 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabItemRenderer.tsx @@ -3,7 +3,7 @@ import { useLocation } from 'react-router'; import { GrafanaTheme2, locationUtil, textUtil } from '@grafana/data'; import { SceneComponentProps, sceneGraph } from '@grafana/scenes'; -import { Checkbox, clearButtonStyles, useElementSelection, useStyles2 } from '@grafana/ui'; +import { clearButtonStyles, useElementSelection, useStyles2 } from '@grafana/ui'; // eslint-disable-next-line no-restricted-imports import { getFocusStyles } from '@grafana/ui/src/themes/mixins'; @@ -24,18 +24,20 @@ export function TabItemRenderer({ model }: SceneComponentProps) { return ( <> -
- {isSelectable && ( -
- -
+
{titleInterpolated} @@ -58,7 +60,7 @@ function getStyles(theme: GrafanaTheme2) { }), label: css({ color: theme.colors.text.secondary, - padding: theme.spacing(1, 1.5, 0.5), + padding: theme.spacing(1, 2, 0.5), borderRadius: theme.shape.radius.default, userSelect: 'none', @@ -83,7 +85,7 @@ function getStyles(theme: GrafanaTheme2) { }, }), labelNotActive: css({ - 'a:hover, &:hover, &:focus': { + '&:hover, &:focus': { color: theme.colors.text.primary, '&::before': { diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManagerRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManagerRenderer.tsx index 5399d2496d9..bab343f3936 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManagerRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManagerRenderer.tsx @@ -51,9 +51,9 @@ const getStyles = (theme: GrafanaTheme2) => ({ tabsContainer: css({ display: 'flex', justifyContent: 'flex-start', - alignItems: 'center', - overflowX: 'scroll', - overflowY: 'visible', + alignItems: 'flex-end', + overflowX: 'auto', + overflowY: 'hidden', paddingInline: theme.spacing(0.125), }), tabContentContainer: css({