feat(explore): Drag and drop UX improvements (#13598)

* Create a metric immediately when saved metric is dropped

* Display borders around control boxes when metric or column is dragged

* Fix imports

* Display ghost button

* Rename placeholder to ghostButton
This commit is contained in:
Kamil Gabryjelski
2021-03-15 11:47:13 +01:00
committed by GitHub
parent 6b30f55cfd
commit ae66f5fa78
6 changed files with 36 additions and 42 deletions

View File

@@ -72,7 +72,6 @@ export const DndColumnSelect = (props: LabelProps) => {
return (
<DndSelectLabel<string | string[], ColumnMeta[]>
values={values}
onDrop={onDrop}
canDrop={canDrop}
valuesRenderer={valuesRenderer}

View File

@@ -17,8 +17,8 @@
* under the License.
*/
import React, { useEffect, useMemo, useState } from 'react';
import { logging, SupersetClient, t } from '@superset-ui/core';
import { ColumnMeta, Metric } from '@superset-ui/chart-controls';
import { logging, SupersetClient, t, Metric } from '@superset-ui/core';
import { ColumnMeta } from '@superset-ui/chart-controls';
import { Tooltip } from 'src/common/components/Tooltip';
import { OPERATORS } from 'src/explore/constants';
import { OptionSortType } from 'src/explore/types';
@@ -300,7 +300,6 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
return (
<>
<DndSelectLabel<OptionValueType, OptionValueType[]>
values={values}
onDrop={(item: DatasourcePanelDndItem) => {
setDroppedItem(item.value);
togglePopover(true);
@@ -313,7 +312,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
DndItemType.MetricOption,
DndItemType.AdhocMetricOption,
]}
placeholderText={t('Drop columns or metrics')}
ghostButtonText={t('Drop columns or metrics')}
{...props}
/>
<AdhocFilterPopoverTrigger

View File

@@ -241,41 +241,35 @@ export const DndMetricSelect = (props: any) => {
togglePopover(false);
};
const { savedMetric, adhocMetric } = useMemo(() => {
if (droppedItem?.type === 'column') {
const handleDrop = (item: DatasourcePanelDndItem) => {
if (item.type === DndItemType.Metric) {
onNewMetric(item.value as Metric);
}
if (item.type === DndItemType.Column) {
setDroppedItem(item);
togglePopover(true);
}
};
const adhocMetric = useMemo(() => {
if (droppedItem?.type === DndItemType.Column) {
const itemValue = droppedItem?.value as ColumnMeta;
return {
savedMetric: {} as savedMetricType,
adhocMetric: new AdhocMetric({
column: { column_name: itemValue?.column_name },
}),
};
return new AdhocMetric({
column: { column_name: itemValue?.column_name },
});
}
if (droppedItem?.type === 'metric') {
const itemValue = droppedItem?.value as savedMetricType;
return {
savedMetric: itemValue,
adhocMetric: new AdhocMetric({ isNew: true }),
};
}
return {
savedMetric: {} as savedMetricType,
adhocMetric: new AdhocMetric({ isNew: true }),
};
return new AdhocMetric({ isNew: true });
}, [droppedItem?.type, droppedItem?.value]);
return (
<div className="metrics-select">
<DndSelectLabel<OptionValueType, OptionValueType[]>
values={value}
onDrop={(item: DatasourcePanelDndItem) => {
setDroppedItem(item);
togglePopover(true);
}}
onDrop={handleDrop}
canDrop={canDrop}
valuesRenderer={valuesRenderer}
accept={[DndItemType.Column, DndItemType.Metric]}
placeholderText={t('Drop columns or metrics')}
ghostButtonText={t('Drop columns or metrics')}
displayGhostButton={multi || value.length === 0}
{...props}
/>
<AdhocMetricPopoverTrigger
@@ -286,7 +280,7 @@ export const DndMetricSelect = (props: any) => {
props.savedMetrics,
props.value,
)}
savedMetric={savedMetric}
savedMetric={{} as savedMetricType}
datasourceType={props.datasourceType}
isControlledComponent
visible={newMetricPopoverVisible}

View File

@@ -18,7 +18,6 @@
*/
import React from 'react';
import { useDrop } from 'react-dnd';
import { isEmpty } from 'lodash';
import { t, useTheme } from '@superset-ui/core';
import ControlHeader from 'src/explore/components/ControlHeader';
import {
@@ -30,9 +29,10 @@ import { DatasourcePanelDndItem } from 'src/explore/components/DatasourcePanel/t
import Icon from 'src/components/Icon';
import { DndColumnSelectProps } from './types';
export default function DndSelectLabel<T, O>(
props: DndColumnSelectProps<T, O>,
) {
export default function DndSelectLabel<T, O>({
displayGhostButton = true,
...props
}: DndColumnSelectProps<T, O>) {
const theme = useTheme();
const [{ isOver, canDrop }, datasourcePanelDrop] = useDrop({
@@ -51,11 +51,11 @@ export default function DndSelectLabel<T, O>(
}),
});
function renderPlaceHolder() {
function renderGhostButton() {
return (
<AddControlLabel cancelHover>
<Icon name="plus-small" color={theme.colors.grayscale.light1} />
{t(props.placeholderText || 'Drop columns')}
{t(props.ghostButtonText || 'Drop columns')}
</AddControlLabel>
);
}
@@ -66,7 +66,8 @@ export default function DndSelectLabel<T, O>(
<ControlHeader {...props} />
</HeaderContainer>
<DndLabelsContainer canDrop={canDrop} isOver={isOver}>
{isEmpty(props.values) ? renderPlaceHolder() : props.valuesRenderer()}
{props.valuesRenderer()}
{displayGhostButton && renderGhostButton()}
</DndLabelsContainer>
</div>
);

View File

@@ -17,7 +17,8 @@
* under the License.
*/
import { ReactNode } from 'react';
import { ColumnMeta, Metric } from '@superset-ui/chart-controls';
import { Metric } from '@superset-ui/core';
import { ColumnMeta } from '@superset-ui/chart-controls';
import { DatasourcePanelDndItem } from '../../DatasourcePanel/types';
import { DndItemType } from '../../DndItemType';
@@ -45,12 +46,12 @@ export interface DndColumnSelectProps<
T = string[] | string,
O = string[] | string
> extends LabelProps<T> {
values?: O;
onDrop: (item: DatasourcePanelDndItem) => void;
canDrop: (item: DatasourcePanelDndItem) => boolean;
valuesRenderer: () => ReactNode;
accept: DndItemType | DndItemType[];
placeholderText?: string;
ghostButtonText?: string;
displayGhostButton?: boolean;
}
export type OptionValueType = Record<string, any>;