Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draggable: only set drag handle props on the drag handle itself #50025

Merged
merged 3 commits into from Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .betterer.results
Expand Up @@ -110,7 +110,7 @@ exports[`no enzyme tests`] = {
"public/app/core/components/QueryOperationRow/QueryOperationAction.test.tsx:3032694716": [
[0, 19, 13, "RegExp match", "2409514259"]
],
"public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx:2026575657": [
"public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx:3743889097": [
[0, 26, 13, "RegExp match", "2409514259"]
],
"public/app/core/components/Select/MetricSelect.test.tsx:3351544014": [
Expand Down
Expand Up @@ -60,7 +60,7 @@ describe('QueryOperationRow', () => {
describe('headerElement rendering', () => {
it('should render headerElement provided as element', () => {
const title = <div aria-label="test title">Test</div>;
const wrapper = shallow(
const wrapper = mount(
<QueryOperationRow headerElement={title} id="test-id" index={0}>
<div>Test</div>
</QueryOperationRow>
Expand All @@ -72,7 +72,7 @@ describe('QueryOperationRow', () => {

it('should render headerElement provided as function', () => {
const title = () => <div aria-label="test title">Test</div>;
const wrapper = shallow(
const wrapper = mount(
<QueryOperationRow headerElement={title} id="test-id" index={0}>
<div>Test</div>
</QueryOperationRow>
Expand Down Expand Up @@ -101,7 +101,7 @@ describe('QueryOperationRow', () => {
describe('actions rendering', () => {
it('should render actions provided as element', () => {
const actions = <div aria-label="test actions">Test</div>;
const wrapper = shallow(
const wrapper = mount(
<QueryOperationRow actions={actions} id="test-id" index={0}>
<div>Test</div>
</QueryOperationRow>
Expand All @@ -112,7 +112,7 @@ describe('QueryOperationRow', () => {
});
it('should render actions provided as function', () => {
const actions = () => <div aria-label="test actions">Test</div>;
const wrapper = shallow(
const wrapper = mount(
<QueryOperationRow actions={actions} id="test-id" index={0}>
<div>Test</div>
</QueryOperationRow>
Expand Down
110 changes: 26 additions & 84 deletions public/app/core/components/QueryOperationRow/QueryOperationRow.tsx
@@ -1,11 +1,13 @@
import { css, cx } from '@emotion/css';
import { css } from '@emotion/css';
import React, { useCallback, useState } from 'react';
import { Draggable } from 'react-beautiful-dnd';
import { useUpdateEffect } from 'react-use';

import { GrafanaTheme } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { Icon, ReactUtils, stylesFactory, useTheme } from '@grafana/ui';
import { ReactUtils, stylesFactory, useTheme } from '@grafana/ui';

import { QueryOperationRowHeader } from './QueryOperationRowHeader';

interface QueryOperationRowProps {
index: number;
Expand Down Expand Up @@ -93,41 +95,25 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
const actionsElement = actions && ReactUtils.renderOrCallToRender(actions, renderPropArgs);
const headerElementRendered = headerElement && ReactUtils.renderOrCallToRender(headerElement, renderPropArgs);

const rowHeader = (
<div className={styles.header}>
<div className={styles.column}>
<Icon
name={isContentVisible ? 'angle-down' : 'angle-right'}
className={styles.collapseIcon}
onClick={onRowToggle}
/>
{title && (
<div className={styles.titleWrapper} onClick={onRowToggle} aria-label="Query operation row title">
<div className={cx(styles.title, disabled && styles.disabled)}>{titleElement}</div>
</div>
)}
{headerElementRendered}
</div>

<div className={styles.column}>
{actionsElement}
{draggable && (
<Icon title="Drag and drop to reorder" name="draggabledots" size="lg" className={styles.dragIcon} />
)}
</div>
</div>
);

if (draggable) {
return (
<Draggable draggableId={id} index={index}>
{(provided) => {
const dragHandleProps = { ...provided.dragHandleProps, role: 'group' }; // replace the role="button" because it causes https://dequeuniversity.com/rules/axe/4.3/nested-interactive?application=msftAI
return (
<>
<div ref={provided.innerRef} className={styles.wrapper} {...provided.draggableProps}>
<div {...dragHandleProps} onMouseMove={reportDragMousePosition}>
{rowHeader}
<div>
<QueryOperationRowHeader
actionsElement={actionsElement}
disabled={disabled}
draggable
dragHandleProps={provided.dragHandleProps}
headerElement={headerElementRendered}
isContentVisible={isContentVisible}
onRowToggle={onRowToggle}
reportDragMousePosition={reportDragMousePosition}
titleElement={titleElement}
/>
</div>
{isContentVisible && <div className={styles.content}>{children}</div>}
</div>
Expand All @@ -140,7 +126,16 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({

return (
<div className={styles.wrapper}>
{rowHeader}
<QueryOperationRowHeader
actionsElement={actionsElement}
disabled={disabled}
draggable={false}
headerElement={headerElementRendered}
isContentVisible={isContentVisible}
onRowToggle={onRowToggle}
reportDragMousePosition={reportDragMousePosition}
titleElement={titleElement}
/>
{isContentVisible && <div className={styles.content}>{children}</div>}
</div>
);
Expand All @@ -151,63 +146,10 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
wrapper: css`
margin-bottom: ${theme.spacing.md};
`,
header: css`
label: Header;
padding: ${theme.spacing.xs} ${theme.spacing.sm};
border-radius: ${theme.border.radius.sm};
background: ${theme.colors.bg2};
min-height: ${theme.spacing.formInputHeight}px;
display: grid;
grid-template-columns: minmax(100px, max-content) min-content;
align-items: center;
justify-content: space-between;
white-space: nowrap;

&:focus {
outline: none;
}
`,
column: css`
label: Column;
display: flex;
align-items: center;
`,
dragIcon: css`
cursor: grab;
color: ${theme.colors.textWeak};
&:hover {
color: ${theme.colors.text};
}
`,
collapseIcon: css`
color: ${theme.colors.textWeak};
cursor: pointer;
&:hover {
color: ${theme.colors.text};
}
`,
titleWrapper: css`
display: flex;
align-items: center;
flex-grow: 1;
cursor: pointer;
overflow: hidden;
margin-right: ${theme.spacing.sm};
`,
title: css`
font-weight: ${theme.typography.weight.semibold};
color: ${theme.colors.textBlue};
margin-left: ${theme.spacing.sm};
overflow: hidden;
text-overflow: ellipsis;
`,
content: css`
margin-top: ${theme.spacing.inlineFormMargin};
margin-left: ${theme.spacing.lg};
`,
disabled: css`
color: ${theme.colors.textWeak};
`,
};
});

Expand Down
@@ -0,0 +1,123 @@
import { css, cx } from '@emotion/css';
import React, { MouseEventHandler } from 'react';
import { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd';

import { GrafanaTheme2 } from '@grafana/data';
import { Icon, useStyles2 } from '@grafana/ui';

interface QueryOperationRowHeaderProps {
actionsElement?: React.ReactNode;
disabled?: boolean;
draggable: boolean;
dragHandleProps?: DraggableProvidedDragHandleProps;
headerElement?: React.ReactNode;
isContentVisible: boolean;
onRowToggle: () => void;
reportDragMousePosition: MouseEventHandler<HTMLDivElement>;
titleElement?: React.ReactNode;
}

export const QueryOperationRowHeader: React.FC<QueryOperationRowHeaderProps> = ({
actionsElement,
disabled,
draggable,
dragHandleProps,
headerElement,
isContentVisible,
onRowToggle,
reportDragMousePosition,
titleElement,
}: QueryOperationRowHeaderProps) => {
const styles = useStyles2(getStyles);

return (
<div className={styles.header}>
<div className={styles.column}>
<Icon
name={isContentVisible ? 'angle-down' : 'angle-right'}
className={styles.collapseIcon}
onClick={onRowToggle}
/>
{titleElement && (
<div className={styles.titleWrapper} onClick={onRowToggle} aria-label="Query operation row title">
<div className={cx(styles.title, disabled && styles.disabled)}>{titleElement}</div>
</div>
)}
{headerElement}
</div>

<div className={styles.column}>
{actionsElement}
{draggable && (
<Icon
title="Drag and drop to reorder"
name="draggabledots"
size="lg"
className={styles.dragIcon}
onMouseMove={reportDragMousePosition}
{...dragHandleProps}
/>
)}
</div>
</div>
);
};

const getStyles = (theme: GrafanaTheme2) => ({
header: css`
label: Header;
padding: ${theme.spacing(0.5, 0.5)};
border-radius: ${theme.shape.borderRadius(1)};
background: ${theme.colors.background.secondary};
min-height: ${theme.spacing(4)};
display: grid;
grid-template-columns: minmax(100px, max-content) min-content;
align-items: center;
justify-content: space-between;
white-space: nowrap;

&:focus {
outline: none;
}
`,
column: css`
label: Column;
display: flex;
align-items: center;
`,
dragIcon: css`
cursor: grab;
color: ${theme.colors.text.disabled};
margin: ${theme.spacing(0, 0.5)};
&:hover {
color: ${theme.colors.text};
}
`,
collapseIcon: css`
color: ${theme.colors.text.disabled};
cursor: pointer;
&:hover {
color: ${theme.colors.text};
}
`,
titleWrapper: css`
display: flex;
align-items: center;
flex-grow: 1;
cursor: pointer;
overflow: hidden;
margin-right: ${theme.spacing(0.5)};
`,
title: css`
font-weight: ${theme.typography.fontWeightBold};
color: ${theme.colors.text.link};
margin-left: ${theme.spacing(0.5)};
overflow: hidden;
text-overflow: ellipsis;
`,
disabled: css`
color: ${theme.colors.text.disabled};
`,
});

QueryOperationRowHeader.displayName = 'QueryOperationRowHeader';
Expand Up @@ -131,15 +131,16 @@ const DraggableFieldName: React.FC<DraggableFieldProps> = ({
return (
<Draggable draggableId={fieldName} index={index}>
{(provided) => (
<div
className="gf-form-inline"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div className="gf-form-inline" ref={provided.innerRef} {...provided.draggableProps}>
<div className="gf-form gf-form--grow">
<div className="gf-form-label gf-form-label--justify-left width-30">
<Icon name="draggabledots" title="Drag and drop to reorder" size="lg" className={styles.draggable} />
<Icon
name="draggabledots"
title="Drag and drop to reorder"
size="lg"
className={styles.draggable}
{...provided.dragHandleProps}
/>
<IconButton
className={styles.toggle}
size="md"
Expand Down