Skip to content

Commit

Permalink
Sortable component (#4199)
Browse files Browse the repository at this point in the history
  • Loading branch information
kravets-levko authored Sep 30, 2019
1 parent cb14459 commit a8af968
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 67 deletions.
51 changes: 17 additions & 34 deletions client/app/components/Parameters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { size, filter, forEach, extend } from 'lodash';
import { react2angular } from 'react2angular';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';
import { SortableContainer, SortableElement, DragHandle } from '@/components/sortable';
import { $location } from '@/services/ng';
import { Parameter } from '@/services/query';
import ParameterApplyButton from '@/components/ParameterApplyButton';
Expand All @@ -12,18 +12,6 @@ import { toHuman } from '@/filters';

import './Parameters.less';

const DragHandle = sortableHandle(({ parameterName }) => (
<div className="drag-handle" data-test={`DragHandle-${parameterName}`} />
));

const SortableItem = sortableElement(({ className, parameterName, disabled, children }) => (
<div className={className} data-editable={!disabled || null}>
{!disabled && <DragHandle parameterName={parameterName} />}
{children}
</div>
));
const SortableContainer = sortableContainer(({ children }) => children);

function updateUrl(parameters) {
const params = extend({}, $location.search());
parameters.forEach((param) => {
Expand All @@ -50,12 +38,12 @@ export class Parameters extends React.Component {
onValuesChange: () => {},
onPendingValuesChange: () => {},
onParametersEdit: () => {},
}
};

constructor(props) {
super(props);
const { parameters } = props;
this.state = { parameters, dragging: false };
this.state = { parameters };
if (!props.disableUrlUpdate) {
updateUrl(parameters);
}
Expand Down Expand Up @@ -101,11 +89,6 @@ export class Parameters extends React.Component {
return { parameters };
});
}
this.setState({ dragging: false });
};

onBeforeSortStart = () => {
this.setState({ dragging: true });
};

applyChanges = () => {
Expand Down Expand Up @@ -170,32 +153,32 @@ export class Parameters extends React.Component {
}

render() {
const { parameters, dragging } = this.state;
const { parameters } = this.state;
const { editable } = this.props;
const dirtyParamCount = size(filter(parameters, 'hasPendingValue'));
return (
<SortableContainer
disabled={!editable}
axis="xy"
useDragHandle
lockToContainerEdges
helperClass="parameter-dragged"
updateBeforeSortStart={this.onBeforeSortStart}
onSortEnd={this.moveParameter}
containerProps={{
className: 'parameter-container',
onKeyDown: dirtyParamCount ? this.handleKeyDown : null,
}}
>
<div
className="parameter-container"
onKeyDown={dirtyParamCount ? this.handleKeyDown : null}
data-draggable={editable || null}
data-dragging={dragging || null}
>
{parameters.map((param, index) => (
<SortableItem className="parameter-block" key={param.name} index={index} parameterName={param.name} disabled={!editable}>
{parameters.map((param, index) => (
<SortableElement key={param.name} index={index}>
<div className="parameter-block" data-editable={editable || null}>
{editable && <DragHandle data-test={`DragHandle-${param.name}`} />}
{this.renderParameter(param, index)}
</SortableItem>
))}

<ParameterApplyButton onClick={this.applyChanges} paramCount={dirtyParamCount} />
</div>
</div>
</SortableElement>
))}
<ParameterApplyButton onClick={this.applyChanges} paramCount={dirtyParamCount} />
</SortableContainer>
);
}
Expand Down
38 changes: 12 additions & 26 deletions client/app/components/Parameters.less
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
@import '../assets/less/ant';

.drag-handle {
background: linear-gradient(90deg, transparent 0px, white 1px, white 2px)
center,
linear-gradient(transparent 0px, white 1px, white 2px) center, #111111;
background-size: 2px 2px;
display: inline-block;
width: 6px;
height: 36px;
vertical-align: bottom;
margin-right: 5px;
cursor: move;
}

.parameter-block {
display: inline-block;
background: white;
padding: 0 12px 6px 0;
vertical-align: top;
z-index: 1;

.drag-handle {
padding: 0 5px;
margin-left: -5px;
height: 36px;
}

.parameter-container[data-draggable] & {
.parameter-container.sortable-container & {
margin: 4px 0 0 4px;
padding: 3px 6px 6px;
}

&.parameter-dragged {
box-shadow: 0 4px 9px -3px rgba(102, 136, 153, 0.15);
width: auto !important;
}
}

Expand All @@ -53,20 +46,13 @@
.parameter-container {
position: relative;

&[data-draggable] {
&.sortable-container {
padding: 0 4px 4px 0;
transition: background-color 200ms ease-out;
transition-delay: 300ms; // short pause before returning to original bgcolor
}

&[data-dragging] {
transition-delay: 0s;
background-color: #f6f8f9;
}

.parameter-apply-button {
display: none; // default for mobile

// "floating" on desktop
@media (min-width: 768px) {
position: absolute;
Expand All @@ -83,7 +69,7 @@
display: block;
pointer-events: none; // so tooltip doesn't remain after button hides
}

&[data-show="true"] {
opacity: 1;
display: block;
Expand Down Expand Up @@ -118,7 +104,7 @@
line-height: 15px;
background: #f77b74;
border-radius: 7px;
box-shadow: 0px 0px 0 1px white, -1px 1px 0 1px #5d6f7d85;
box-shadow: 0 0 0 1px white, -1px 1px 0 1px #5d6f7d85;
}
}
}
80 changes: 80 additions & 0 deletions client/app/components/sortable/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { isFunction, wrap } from 'lodash';
import React, { useRef, useState } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { sortableContainer, sortableElement, sortableHandle } from 'react-sortable-hoc';

import './style.less';

export const DragHandle = sortableHandle(({ className, ...restProps }) => (
<div className={cx('drag-handle', className)} {...restProps} />
));

export const SortableContainerWrapper = sortableContainer(({ children }) => children);

export const SortableElement = sortableElement(({ children }) => children);

export function SortableContainer({ disabled, containerProps, children, ...wrapperProps }) {
const containerRef = useRef();
const [isDragging, setIsDragging] = useState(false);

wrapperProps = { ...wrapperProps };
containerProps = { ...containerProps };

if (disabled) {
// Disabled state:
// - forbid drag'n'drop (and therefore no need to hook events
// - don't override anything on container element
wrapperProps.shouldCancelStart = () => true;
} else {
// Enabled state:

// - use container element as a default helper element
wrapperProps.helperContainer = wrap(wrapperProps.helperContainer, helperContainer => (
isFunction(helperContainer) ?
helperContainer(containerRef.current) :
containerRef.current
));

// - hook drag start/end events
wrapperProps.updateBeforeSortStart = wrap(wrapperProps.updateBeforeSortStart, (updateBeforeSortStart, ...args) => {
setIsDragging(true);
if (isFunction(updateBeforeSortStart)) {
updateBeforeSortStart(...args);
}
});
wrapperProps.onSortEnd = wrap(wrapperProps.onSortEnd, (onSortEnd, ...args) => {
setIsDragging(false);
if (isFunction(onSortEnd)) {
onSortEnd(...args);
}
});

// - update container element: add classes and take a ref
containerProps.className = cx(
'sortable-container',
{ 'sortable-container-dragging': isDragging },
containerProps.className,
);
containerProps.ref = containerRef;
}

// order of props matters - we override some of them
return (
<SortableContainerWrapper {...wrapperProps}>
<div {...containerProps}>{children}</div>
</SortableContainerWrapper>
);
}

SortableContainer.propTypes = {
disabled: PropTypes.bool,
containerProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types
children: PropTypes.node,
};

SortableContainer.defaultProps = {
disabled: false,
containerProps: {},
children: null,
};
30 changes: 30 additions & 0 deletions client/app/components/sortable/style.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.drag-handle {
vertical-align: bottom;
cursor: move;

display: inline-flex;
align-items: stretch;
justify-content: center;

&:before {
content: '';
display: block;
width: 6px;

background:
linear-gradient(90deg, transparent 0px, white 1px, white 2px) center,
linear-gradient(transparent 0px, white 1px, white 2px) center,
#111111;
background-size: 2px 2px;
}
}

.sortable-container {
transition: background-color 200ms ease-out;
transition-delay: 300ms; // short pause before returning to original bgcolor

&.sortable-container-dragging {
transition-delay: 0s;
background-color: #f6f8f9;
}
}
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"react-dom": "^16.8.3",
"react-grid-layout": "git+https://github.com/getredash/react-grid-layout.git",
"react-pivottable": "^0.9.0",
"react-sortable-hoc": "^1.9.1",
"react-sortable-hoc": "^1.10.1",
"react2angular": "^3.2.1",
"tinycolor2": "^1.4.1",
"ui-select": "^0.19.8"
Expand Down

0 comments on commit a8af968

Please sign in to comment.