Skip to content

Commit

Permalink
[Lens] Drag within dimension group to reorder (#80547) (#82500)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbondyra authored Nov 4, 2020
1 parent 7490e8a commit 2103911
Show file tree
Hide file tree
Showing 24 changed files with 1,234 additions and 347 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/lens/public/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ $lnsPanelMinWidth: $euiSize * 18;
// These sizes also match canvas' page thumbnails for consistency
$lnsSuggestionHeight: 100px;
$lnsSuggestionWidth: 150px;
$lnsLayerPanelDimensionMargin: 8px;
44 changes: 39 additions & 5 deletions x-pack/plugins/lens/public/drag_drop/drag_drop.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@
}
}

// Draggable item when it is moving
.lnsDragDrop-isHidden {
opacity: 0;
}

// Drop area
.lnsDragDrop-isDroppable {
@include lnsDroppable;
Expand All @@ -35,6 +30,10 @@
@include lnsDroppableActive;
}

.lnsDragDrop-isActiveGroup {
background-color: transparentize($euiColorVis0, .75);
}

// Drop area while hovering with item
.lnsDragDrop-isActiveDropTarget {
@include lnsDroppableActiveHover;
Expand All @@ -52,3 +51,38 @@
text-decoration: line-through;
}
}

.lnsDragDrop__reorderableContainer {
position: relative;
}

.lnsDragDrop__reorderableDrop {
position: absolute;
width: 100%;
top: 0;
height: calc(100% + #{$lnsLayerPanelDimensionMargin});
}

.lnsDragDrop-isReorderable {
transition: transform $euiAnimSpeedFast ease-in-out;
pointer-events: none;
}

// Draggable item when it is moving
.lnsDragDrop-isHidden {
opacity: 0;
}

.lnsDragDrop__keyboardHandler {
top: 0;
position: absolute;
width: 100%;
height: 100%;
border-radius: $euiBorderRadius;

&:focus,
&:focus-within {
@include euiFocusRing;
pointer-events: none;
}
}
194 changes: 180 additions & 14 deletions x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@

import React from 'react';
import { render, mount } from 'enzyme';
import { DragDrop } from './drag_drop';
import { ChildDragDropProvider } from './providers';
import { DragDrop, ReorderableDragDrop, DropToHandler, DropHandler } from './drag_drop';
import { ChildDragDropProvider, ReorderProvider } from './providers';

jest.useFakeTimers();

describe('DragDrop', () => {
const value = { id: '1', label: 'hello' };
test('renders if nothing is being dragged', () => {
const component = render(
<DragDrop value="hello" draggable label="dragging">
<DragDrop value={value} draggable label="dragging">
<button>Hello!</button>
</DragDrop>
);
Expand Down Expand Up @@ -54,7 +55,6 @@ describe('DragDrop', () => {
setData: jest.fn(),
getData: jest.fn(),
};
const value = {};

const component = mount(
<ChildDragDropProvider dragging={value} setDragging={setDragging}>
Expand All @@ -77,10 +77,9 @@ describe('DragDrop', () => {
const stopPropagation = jest.fn();
const setDragging = jest.fn();
const onDrop = jest.fn();
const value = {};

const component = mount(
<ChildDragDropProvider dragging="hola" setDragging={setDragging}>
<ChildDragDropProvider dragging={{ id: '2', label: 'hi' }} setDragging={setDragging}>
<DragDrop onDrop={onDrop} droppable={true} value={value}>
<button>Hello!</button>
</DragDrop>
Expand All @@ -94,7 +93,7 @@ describe('DragDrop', () => {
expect(preventDefault).toBeCalled();
expect(stopPropagation).toBeCalled();
expect(setDragging).toBeCalledWith(undefined);
expect(onDrop).toBeCalledWith('hola');
expect(onDrop).toBeCalledWith({ id: '2', label: 'hi' });
});

test('drop function is not called on droppable=false', async () => {
Expand All @@ -104,8 +103,8 @@ describe('DragDrop', () => {
const onDrop = jest.fn();

const component = mount(
<ChildDragDropProvider dragging="hola" setDragging={setDragging}>
<DragDrop onDrop={onDrop} droppable={false} value={{}}>
<ChildDragDropProvider dragging={{ id: 'hi' }} setDragging={setDragging}>
<DragDrop onDrop={onDrop} droppable={false} value={value}>
<button>Hello!</button>
</DragDrop>
</ChildDragDropProvider>
Expand Down Expand Up @@ -138,8 +137,8 @@ describe('DragDrop', () => {

test('items that have droppable=false get special styling when another item is dragged', () => {
const component = mount(
<ChildDragDropProvider dragging={'ignored'} setDragging={() => {}}>
<DragDrop value="ignored" draggable={true} label="a">
<ChildDragDropProvider dragging={value} setDragging={() => {}}>
<DragDrop value={value} draggable={true} label="a">
<button>Hello!</button>
</DragDrop>
<DragDrop onDrop={(x: unknown) => {}} droppable={false}>
Expand All @@ -152,16 +151,16 @@ describe('DragDrop', () => {
});

test('additional styles are reflected in the className until drop', () => {
let dragging: string | undefined;
let dragging: { id: '1' } | undefined;
const getAdditionalClasses = jest.fn().mockReturnValue('additional');
const component = mount(
<ChildDragDropProvider
dragging={dragging}
setDragging={() => {
dragging = 'hello';
dragging = { id: '1' };
}}
>
<DragDrop value="ignored" draggable={true} label="a">
<DragDrop value={{ label: 'ignored', id: '3' }} draggable={true} label="a">
<button>Hello!</button>
</DragDrop>
<DragDrop
Expand Down Expand Up @@ -194,4 +193,171 @@ describe('DragDrop', () => {
component.find('[data-test-subj="lnsDragDrop"]').at(1).simulate('drop');
expect(component.find('.additional')).toHaveLength(0);
});

describe('reordering', () => {
const mountComponent = (
dragging: { id: '1' } | undefined,
onDrop: DropHandler = jest.fn(),
dropTo: DropToHandler = jest.fn()
) =>
mount(
<ChildDragDropProvider
dragging={{ id: '1' }}
setDragging={() => {
dragging = { id: '1' };
}}
>
<ReorderProvider id="groupId">
<DragDrop
label="1"
draggable
droppable
dragType="reorder"
dropType="reorder"
itemsInGroup={['1', '2', '3']}
value={{ id: '1' }}
onDrop={onDrop}
dropTo={dropTo}
>
<span>1</span>
</DragDrop>
<DragDrop
label="2"
draggable
droppable
dragType="reorder"
dropType="reorder"
itemsInGroup={['1', '2', '3']}
value={{
id: '2',
}}
onDrop={onDrop}
dropTo={dropTo}
>
<span>2</span>
</DragDrop>
<DragDrop
label="3"
draggable
droppable
dragType="reorder"
dropType="reorder"
itemsInGroup={['1', '2', '3']}
value={{
id: '3',
}}
onDrop={onDrop}
dropTo={dropTo}
>
<span>3</span>
</DragDrop>
</ReorderProvider>
</ChildDragDropProvider>
);
test(`ReorderableDragDrop component doesn't appear for groups of 1 or less`, () => {
let dragging;
const component = mount(
<ChildDragDropProvider
dragging={dragging}
setDragging={() => {
dragging = { id: '1' };
}}
>
<ReorderProvider id="groupId">
<DragDrop
label="1"
draggable
droppable
dragType="reorder"
dropType="reorder"
itemsInGroup={['1']}
value={{ id: '1' }}
onDrop={jest.fn()}
dropTo={jest.fn()}
>
<div />
</DragDrop>
</ReorderProvider>
</ChildDragDropProvider>
);
expect(component.find(ReorderableDragDrop)).toHaveLength(0);
});
test(`Reorderable component renders properly`, () => {
const component = mountComponent(undefined, jest.fn());
expect(component.find(ReorderableDragDrop)).toHaveLength(3);
});
test(`Elements between dragged and drop get extra class to show the reorder effect when dragging`, () => {
const component = mountComponent({ id: '1' }, jest.fn());
const dataTransfer = {
setData: jest.fn(),
getData: jest.fn(),
};
component
.find(ReorderableDragDrop)
.first()
.find('[data-test-subj="lnsDragDrop"]')
.simulate('dragstart', { dataTransfer });
jest.runAllTimers();

component.find('[data-test-subj="lnsDragDrop-reorderableDrop"]').at(2).simulate('dragover');
expect(component.find('[data-test-subj="lnsDragDrop"]').at(0).prop('style')).toEqual({});
expect(component.find('[data-test-subj="lnsDragDrop"]').at(1).prop('style')).toEqual({
transform: 'translateY(-8px)',
});
expect(component.find('[data-test-subj="lnsDragDrop"]').at(2).prop('style')).toEqual({
transform: 'translateY(-8px)',
});

component.find('[data-test-subj="lnsDragDrop-reorderableDrop"]').at(2).simulate('dragleave');
expect(component.find('[data-test-subj="lnsDragDrop"]').at(1).prop('style')).toEqual({});
expect(component.find('[data-test-subj="lnsDragDrop"]').at(2).prop('style')).toEqual({});
});
test(`Dropping an item runs onDrop function`, () => {
const preventDefault = jest.fn();
const stopPropagation = jest.fn();
const onDrop = jest.fn();

const component = mountComponent({ id: '1' }, onDrop);

component
.find('[data-test-subj="lnsDragDrop-reorderableDrop"]')
.at(1)
.simulate('drop', { preventDefault, stopPropagation });
expect(preventDefault).toBeCalled();
expect(stopPropagation).toBeCalled();
expect(onDrop).toBeCalledWith({ id: '1' });
});
test(`Keyboard navigation: user can reorder an element`, () => {
const onDrop = jest.fn();
const dropTo = jest.fn();
const component = mountComponent({ id: '1' }, onDrop, dropTo);
const keyboardHandler = component
.find(ReorderableDragDrop)
.at(1)
.find('[data-test-subj="lnsDragDrop-keyboardHandler"]');

keyboardHandler.simulate('keydown', { key: 'Space' });
keyboardHandler.simulate('keydown', { key: 'ArrowDown' });
expect(dropTo).toBeCalledWith('3');

keyboardHandler.simulate('keydown', { key: 'ArrowUp' });
expect(dropTo).toBeCalledWith('1');
});
test(`Keyboard Navigation: User cannot move an element outside of the group`, () => {
const onDrop = jest.fn();
const dropTo = jest.fn();
const component = mountComponent({ id: '1' }, onDrop, dropTo);
const keyboardHandler = component
.find(ReorderableDragDrop)
.first()
.find('[data-test-subj="lnsDragDrop-keyboardHandler"]');

keyboardHandler.simulate('keydown', { key: 'Space' });
keyboardHandler.simulate('keydown', { key: 'ArrowUp' });
expect(dropTo).not.toHaveBeenCalled();

keyboardHandler.simulate('keydown', { key: 'ArrowDown' });
expect(dropTo).toBeCalledWith('2');
});
});
});
Loading

0 comments on commit 2103911

Please sign in to comment.