diff --git a/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx b/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx index 52cbaa67caa..41259887f5a 100644 --- a/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx +++ b/public/app/features/dashboard/components/DashboardRow/DashboardRow.test.tsx @@ -2,11 +2,13 @@ import { screen, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { createTheme } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/types'; import { PanelModel } from '../../state/PanelModel'; -import { DashboardRow } from './DashboardRow'; +import { DashboardRow, UnthemedDashboardRow } from './DashboardRow'; describe('DashboardRow', () => { let panel: PanelModel, dashboardMock: any; @@ -25,18 +27,18 @@ describe('DashboardRow', () => { panel = new PanelModel({ collapsed: false }); }); - it('Should not have collapsed class when collaped is false', () => { + it('Should correctly show expanded state when the panel is expanded', () => { render(); - const row = screen.getByTestId('dashboard-row-container'); + const row = screen.getByTestId(selectors.components.DashboardRow.title('')); expect(row).toBeInTheDocument(); - expect(row).not.toHaveClass('dashboard-row--collapsed'); + expect(row).toHaveAttribute('aria-expanded', 'true'); }); - it('Should collapse when the panel is collapsed', async () => { + it('Should correctly show expanded state when the panel is collapsed', async () => { const panel = new PanelModel({ collapsed: true }); render(); - const row = screen.getByTestId('dashboard-row-container'); - expect(row).toHaveClass('dashboard-row--collapsed'); + const row = screen.getByTestId(selectors.components.DashboardRow.title('')); + expect(row).toHaveAttribute('aria-expanded', 'false'); }); it('Should collapse after clicking title', async () => { @@ -79,7 +81,7 @@ describe('DashboardRow', () => { }, }); const rowPanel = new PanelModel({ collapsed: true, panels: [panel] }); - const dashboardRow = new DashboardRow({ panel: rowPanel, dashboard: dashboardMock }); + const dashboardRow = new UnthemedDashboardRow({ panel: rowPanel, dashboard: dashboardMock, theme: createTheme() }); expect(dashboardRow.getWarning()).toBeDefined(); }); @@ -91,7 +93,7 @@ describe('DashboardRow', () => { }, }); const rowPanel = new PanelModel({ collapsed: true, panels: [panel] }); - const dashboardRow = new DashboardRow({ panel: rowPanel, dashboard: dashboardMock }); + const dashboardRow = new UnthemedDashboardRow({ panel: rowPanel, dashboard: dashboardMock, theme: createTheme() }); expect(dashboardRow.getWarning()).not.toBeDefined(); }); }); diff --git a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx index 8b496e5962b..0c6852f7b6b 100644 --- a/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx +++ b/public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx @@ -1,11 +1,12 @@ -import classNames from 'classnames'; +import { css, cx } from '@emotion/css'; import { indexOf } from 'lodash'; import React from 'react'; import { Unsubscribable } from 'rxjs'; +import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { getTemplateSrv, RefreshEvent } from '@grafana/runtime'; -import { Icon, TextLink } from '@grafana/ui'; +import { Icon, TextLink, Themeable2, withTheme2 } from '@grafana/ui'; import appEvents from 'app/core/app_events'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/types'; @@ -14,12 +15,12 @@ import { DashboardModel } from '../../state/DashboardModel'; import { PanelModel } from '../../state/PanelModel'; import { RowOptionsButton } from '../RowOptions/RowOptionsButton'; -export interface DashboardRowProps { +export interface DashboardRowProps extends Themeable2 { panel: PanelModel; dashboard: DashboardModel; } -export class DashboardRow extends React.Component { +export class UnthemedDashboardRow extends React.Component { sub?: Unsubscribable; componentDidMount() { @@ -93,32 +94,39 @@ export class DashboardRow extends React.Component { }; render() { - const classes = classNames({ - 'dashboard-row': true, - 'dashboard-row--collapsed': this.props.panel.collapsed, - }); - const title = getTemplateSrv().replace(this.props.panel.title, this.props.panel.scopedVars, 'text'); const count = this.props.panel.panels ? this.props.panel.panels.length : 0; const panels = count === 1 ? 'panel' : 'panels'; const canEdit = this.props.dashboard.meta.canEdit === true; + const collapsed = this.props.panel.collapsed; + const styles = getStyles(this.props.theme); return ( -
+
{canEdit && ( -
+
{
)} - {this.props.panel.collapsed === true && ( + {collapsed === true && ( /* disabling the a11y rules here as the button handles keyboard interactions */ /* this is just to provide a better experience for mouse users */ /* eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ -
+
 
)} - {canEdit &&
} + {canEdit && ( +
+ )}
); } } + +export const DashboardRow = withTheme2(UnthemedDashboardRow); + +const getStyles = (theme: GrafanaTheme2) => { + const actions = css({ + color: theme.colors.text.secondary, + opacity: 0, + [theme.transitions.handleMotion('no-preference', 'reduce')]: { + transition: '200ms opacity ease-in 200ms', + }, + + button: { + color: theme.colors.text.secondary, + paddingLeft: theme.spacing(2), + background: 'transparent', + border: 'none', + + '&:hover': { + color: theme.colors.text.maxContrast, + }, + }, + }); + + return { + dashboardRow: css({ + display: 'flex', + alignItems: 'center', + height: '100%', + + '&:hover, &:focus-within': { + [`.${actions}`]: { + opacity: 1, + }, + }, + }), + dashboardRowCollapsed: css({ + background: theme.components.panel.background, + }), + toggleTargetCollapsed: css({ + flex: 1, + cursor: 'pointer', + marginRight: '15px', + }), + title: css({ + flexGrow: 0, + fontSize: theme.typography.h5.fontSize, + fontWeight: theme.typography.fontWeightMedium, + color: theme.colors.text.primary, + background: 'transparent', + border: 'none', + + '.fa': { + color: theme.colors.text.secondary, + fontSize: theme.typography.size.xs, + padding: theme.spacing(0, 1), + }, + }), + actions, + count: css({ + paddingLeft: theme.spacing(2), + color: theme.colors.text.secondary, + fontStyle: 'italic', + fontSize: theme.typography.size.sm, + fontWeight: 'normal', + display: 'none', + }), + countCollapsed: css({ + display: 'inline-block', + }), + dragHandle: css({ + cursor: 'move', + width: '16px', + height: '100%', + background: 'url("public/img/grab_dark.svg") no-repeat 50% 50%', + backgroundSize: '8px', + visibility: 'hidden', + position: 'absolute', + top: 0, + right: 0, + }), + dragHandleCollapsed: css({ + visibility: 'visible', + opacity: 1, + }), + }; +}; diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 820c531beba..8d7228cf46f 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -39,7 +39,6 @@ @import 'components/query_editor'; @import 'components/tabbed_view'; @import 'components/query_part'; -@import 'components/row'; @import 'components/json_explorer'; @import 'components/dashboard_grid'; @import 'components/add_data_source'; diff --git a/public/sass/components/_row.scss b/public/sass/components/_row.scss deleted file mode 100644 index b955988c7e9..00000000000 --- a/public/sass/components/_row.scss +++ /dev/null @@ -1,85 +0,0 @@ -.dashboard-row { - display: flex; - align-items: center; - - height: 100%; - - &--collapsed { - background: $panel-bg; - - .dashboard-row__panel_count { - display: inline-block; - } - - .dashboard-row__drag { - visibility: visible; - opacity: 1; - } - - .dashboard-row__toggle-target { - flex: 1; - cursor: pointer; - margin-right: 15px; - } - } - - &:hover, - &:focus-within { - .dashboard-row__actions { - opacity: 1; - } - } -} - -.dashboard-row__title { - flex-grow: 0; - font-size: $font-size-h5; - font-weight: $font-weight-semi-bold; - color: $text-color; - background: transparent; - border: none; - - .fa { - color: $text-muted; - font-size: $font-size-xs; - padding: 0 $space-sm; - } -} - -.dashboard-row__actions { - color: $text-muted; - opacity: 0; - transition: 200ms opacity ease-in 200ms; - - button { - color: $text-color-weak; - padding-left: $spacer; - background: transparent; - border: none; - - &:hover { - color: $link-hover-color; - } - } -} - -.dashboard-row__panel_count { - padding-left: $spacer; - color: $text-color-weak; - font-style: italic; - font-size: $font-size-sm; - font-weight: normal; - display: none; -} - -.dashboard-row__drag { - cursor: move; - width: 16px; - height: 100%; - background: url('../img/grab_dark.svg') no-repeat 50% 50%; - background-size: 8px; - visibility: hidden; - position: absolute; - top: 0; - right: 0; -}