Skip to content

Commit

Permalink
adding a ghost image
Browse files Browse the repository at this point in the history
  • Loading branch information
mbondyra committed Feb 9, 2021
1 parent fe6dae9 commit e19f9ad
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 44 deletions.

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

33 changes: 28 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 @@ -6,6 +6,19 @@
transition: background-color $euiAnimSpeedFast ease-in-out, border-color $euiAnimSpeedFast ease-in-out;
}

.lnsDragDrop_ghost {
@include lnsDraggable;
border: $euiBorderWidthThin dashed $euiBorderColor;
position: absolute !important; // sass-lint:disable-line no-important
top:0;
width: 100%;
left:0;
opacity:.9;
transform: translate(-25px, -30px);
z-index: $euiZLevel2;
pointer-events: none;
}

// Draggable item
.lnsDragDrop-isDraggable {
@include lnsDraggable;
Expand Down Expand Up @@ -87,11 +100,6 @@
z-index: $euiZLevel1;
}

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

.lnsDragDrop__keyboardHandler {
top: 0;
position: absolute;
Expand All @@ -105,3 +113,18 @@
pointer-events: none;
}
}

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

.lnsDragDrop-isHidden-noFocus {
opacity: 0;
.lnsDragDrop__keyboardHandler {
&:focus,
&:focus-within {
animation: none;
}
}
}
63 changes: 41 additions & 22 deletions x-pack/plugins/lens/public/drag_drop/drag_drop.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DragContextState,
ReorderProvider,
DragDropIdentifier,
DraggingIdentifier,
DropTargets,
} from './providers';
import { act } from 'react-dom/test-utils';
Expand Down Expand Up @@ -81,7 +82,7 @@ describe('DragDrop', () => {
const component = mount(
<ChildDragDropProvider
{...defaultContext}
dragging={value}
dragging={{ ...value, ghost: <button>Hello!</button> }}
setDragging={setDragging}
setA11yMessage={setA11yMessage}
>
Expand All @@ -96,7 +97,7 @@ describe('DragDrop', () => {
jest.runAllTimers();

expect(dataTransfer.setData).toBeCalledWith('text', 'hello');
expect(setDragging).toBeCalledWith(value);
expect(setDragging).toBeCalledWith({ ...value, ghost: <button>Hello!</button> });
expect(setA11yMessage).toBeCalledWith('Lifted hello');
});

Expand All @@ -109,7 +110,7 @@ describe('DragDrop', () => {
const component = mount(
<ChildDragDropProvider
{...defaultContext}
dragging={{ id: '2', humanData: { label: 'label1' } }}
dragging={{ id: '2', humanData: { label: 'label1' }, ghost: <button>Hello!</button> }}
setDragging={setDragging}
>
<DragDrop onDrop={onDrop} dropType="field_add" value={value} order={[2, 0, 1, 0]}>
Expand All @@ -125,7 +126,10 @@ describe('DragDrop', () => {
expect(preventDefault).toBeCalled();
expect(stopPropagation).toBeCalled();
expect(setDragging).toBeCalledWith(undefined);
expect(onDrop).toBeCalledWith({ id: '2', humanData: { label: 'label1' } }, 'field_add');
expect(onDrop).toBeCalledWith(
{ id: '2', humanData: { label: 'label1' }, ghost: <button>Hello!</button> },
'field_add'
);
});

test('drop function is not called on dropType undefined', async () => {
Expand All @@ -137,7 +141,7 @@ describe('DragDrop', () => {
const component = mount(
<ChildDragDropProvider
{...defaultContext}
dragging={{ id: 'hi', humanData: { label: 'label1' } }}
dragging={{ id: 'hi', humanData: { label: 'label1' }, ghost: <button>Hello!</button> }}
setDragging={setDragging}
>
<DragDrop onDrop={onDrop} dropType={undefined} value={value} order={[2, 0, 1, 0]}>
Expand Down Expand Up @@ -175,7 +179,7 @@ describe('DragDrop', () => {

test('items that has dropType=undefined get special styling when another item is dragged', () => {
const component = mount(
<ChildDragDropProvider {...defaultContext} dragging={value}>
<ChildDragDropProvider {...defaultContext} dragging={{ ...value, ghost: <div /> }}>
<DragDrop value={value} draggable={true} order={[2, 0, 1, 0]}>
<button>Hello!</button>
</DragDrop>
Expand All @@ -194,7 +198,9 @@ describe('DragDrop', () => {
});

test('additional styles are reflected in the className until drop', () => {
let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined;
let dragging:
| { id: '1'; humanData: { label: 'label1' }; ghost: React.ReactElement }
| undefined;
const getAdditionalClassesOnEnter = jest.fn().mockReturnValue('additional');
const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable');
const setA11yMessage = jest.fn();
Expand All @@ -206,7 +212,7 @@ describe('DragDrop', () => {
dragging={dragging}
setA11yMessage={setA11yMessage}
setDragging={() => {
dragging = { id: '1', humanData: { label: 'label1' } };
dragging = { id: '1', humanData: { label: 'label1' }, ghost: <div>Hello</div> };
}}
setActiveDropTarget={(val) => {
activeDropTarget = { activeDropTarget: val };
Expand Down Expand Up @@ -246,7 +252,9 @@ describe('DragDrop', () => {
});

test('additional enter styles are reflected in the className until dragleave', () => {
let dragging: { id: '1'; humanData: { label: 'label1' } } | undefined;
let dragging:
| { id: '1'; humanData: { label: 'label1' }; ghost: React.ReactElement }
| undefined;
const getAdditionalClasses = jest.fn().mockReturnValue('additional');
const getAdditionalClassesOnDroppable = jest.fn().mockReturnValue('droppable');
const setActiveDropTarget = jest.fn();
Expand All @@ -256,7 +264,7 @@ describe('DragDrop', () => {
setA11yMessage={jest.fn()}
dragging={dragging}
setDragging={() => {
dragging = { id: '1', humanData: { label: 'label1' } };
dragging = { id: '1', humanData: { label: 'label1' }, ghost: <div /> };
}}
setActiveDropTarget={setActiveDropTarget}
activeDropTarget={
Expand Down Expand Up @@ -350,7 +358,7 @@ describe('DragDrop', () => {
<ChildDragDropProvider
{...{
...defaultContext,
dragging: items[0].value,
dragging: { ...items[0].value, ghost: <div /> },
setActiveDropTarget,
setA11yMessage,
activeDropTarget: {
Expand Down Expand Up @@ -427,7 +435,7 @@ describe('DragDrop', () => {
const registerDropTarget = jest.fn();
const baseContext = {
dragging,
setDragging: (val?: DragDropIdentifier) => {
setDragging: (val?: DraggingIdentifier) => {
dragging = val;
},
keyboardMode,
Expand Down Expand Up @@ -479,7 +487,11 @@ describe('DragDrop', () => {
test(`Reorderable group with lifted element renders properly`, () => {
const setA11yMessage = jest.fn();
const setDragging = jest.fn();
const component = mountComponent({ dragging: items[0], setDragging, setA11yMessage });
const component = mountComponent({
dragging: { ...items[0], ghost: <span>1</span> },
setDragging,
setA11yMessage,
});
act(() => {
component
.find('[data-test-subj="lnsDragDrop"]')
Expand All @@ -488,7 +500,7 @@ describe('DragDrop', () => {
jest.runAllTimers();
});

expect(setDragging).toBeCalledWith(items[0]);
expect(setDragging).toBeCalledWith({ ...items[0], ghost: <span>1</span> });
expect(setA11yMessage).toBeCalledWith('Lifted label1');
expect(
component
Expand All @@ -498,7 +510,7 @@ describe('DragDrop', () => {
});

test(`Reordered elements get extra styles to show the reorder effect when dragging`, () => {
const component = mountComponent({ dragging: items[0] });
const component = mountComponent({ dragging: { ...items[0], ghost: <div /> } });

act(() => {
component
Expand Down Expand Up @@ -545,7 +557,11 @@ describe('DragDrop', () => {
const setA11yMessage = jest.fn();
const setDragging = jest.fn();

const component = mountComponent({ dragging: items[0], setDragging, setA11yMessage });
const component = mountComponent({
dragging: { ...items[0], ghost: <div /> },
setDragging,
setA11yMessage,
});

component
.find('[data-test-subj="lnsDragDrop-reorderableDropLayer"]')
Expand All @@ -558,14 +574,14 @@ describe('DragDrop', () => {
);
expect(preventDefault).toBeCalled();
expect(stopPropagation).toBeCalled();
expect(onDrop).toBeCalledWith(items[0], 'reorder');
expect(onDrop).toBeCalledWith({ ...items[0], ghost: <div /> }, 'reorder');
});

test(`Keyboard Navigation: User cannot move an element outside of the group`, () => {
const setA11yMessage = jest.fn();
const setActiveDropTarget = jest.fn();
const component = mountComponent({
dragging: items[0],
dragging: { ...items[0], ghost: <div /> },
keyboardMode: true,
activeDropTarget: {
activeDropTarget: undefined,
Expand Down Expand Up @@ -594,7 +610,7 @@ describe('DragDrop', () => {
});
test(`Keyboard navigation: user can drop element to an activeDropTarget`, () => {
const component = mountComponent({
dragging: items[0],
dragging: { ...items[0], ghost: <div /> },
activeDropTarget: {
activeDropTarget: { ...items[2], dropType: 'reorder', onDrop },
dropTargetsByOrder: {
Expand All @@ -621,7 +637,10 @@ describe('DragDrop', () => {
test(`Keyboard Navigation: Doesn't call onDrop when movement is cancelled`, () => {
const setA11yMessage = jest.fn();
const onDropHandler = jest.fn();
const component = mountComponent({ dragging: items[0], setA11yMessage }, onDropHandler);
const component = mountComponent(
{ dragging: { ...items[0], ghost: <div /> }, setA11yMessage },
onDropHandler
);
const keyboardHandler = component.find('[data-test-subj="lnsDragDrop-keyboardHandler"]');
keyboardHandler.simulate('keydown', { key: 'Space' });
keyboardHandler.simulate('keydown', { key: 'Escape' });
Expand All @@ -640,7 +659,7 @@ describe('DragDrop', () => {
test(`Keyboard Navigation: Reordered elements get extra styles to show the reorder effect`, () => {
const setA11yMessage = jest.fn();
const component = mountComponent({
dragging: items[0],
dragging: { ...items[0], ghost: <div /> },
keyboardMode: true,
activeDropTarget: {
activeDropTarget: undefined,
Expand Down Expand Up @@ -704,7 +723,7 @@ describe('DragDrop', () => {
'2,0,1,1': { ...items[1], onDrop, dropType: 'reorder' },
},
}}
dragging={items[0]}
dragging={{ ...items[0], ghost: <div /> }}
setActiveDropTarget={setActiveDropTarget}
setA11yMessage={setA11yMessage}
>
Expand Down
33 changes: 30 additions & 3 deletions x-pack/plugins/lens/public/drag_drop/drag_drop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ interface BaseProps {
* Order for keyboard dragging. This takes an array of numbers which will be used to order hierarchically
*/
order: number[];

/**
* don't display ghost image for the drop element
*/
noGhost?: boolean;
}

/**
Expand Down Expand Up @@ -232,7 +237,7 @@ const DragInner = memo(function DragInner({

const currentTarget = e?.currentTarget;
setTimeout(() => {
setDragging(value);
setDragging({ ...value, ghost: children });
setA11yMessage(announce.lifted(value.humanData));
if (onDragStart) {
onDragStart(currentTarget);
Expand Down Expand Up @@ -278,8 +283,19 @@ const DragInner = memo(function DragInner({
: announce.noTarget()
);
};
const shouldShowGhostImageInstead =
isDragging &&
dragType === 'move' &&
keyboardMode &&
activeDropTarget?.activeDropTarget &&
activeDropTarget?.activeDropTarget.dropType !== 'reorder';
return (
<div className={className} data-test-subj={`lnsDragDrop_draggable-${value.humanData.label}`}>
<div
className={classNames(className, {
'lnsDragDrop-isHidden-noFocus': shouldShowGhostImageInstead,
})}
data-test-subj={`lnsDragDrop_draggable-${value.humanData.label}`}
>
<EuiScreenReaderOnly showOnFocus>
<button
aria-label={value.humanData.label}
Expand Down Expand Up @@ -321,7 +337,8 @@ const DragInner = memo(function DragInner({
{React.cloneElement(children, {
'data-test-subj': dataTestSubj || 'lnsDragDrop',
className: classNames(children.props.className, 'lnsDragDrop', 'lnsDragDrop-isDraggable', {
'lnsDragDrop-isHidden': isDragging && dragType === 'move' && !keyboardMode,
'lnsDragDrop-isHidden':
(isDragging && dragType === 'move' && !keyboardMode) || shouldShowGhostImageInstead,
}),
draggable: true,
onDragEnd: dragEnd,
Expand All @@ -344,6 +361,7 @@ const DropInner = memo(function DropInner(props: DropInnerProps) {
isNotDroppable,
dragType = 'copy',
dropType,
noGhost,
keyboardMode,
activeDropTarget,
registerDropTarget,
Expand Down Expand Up @@ -431,6 +449,15 @@ const DropInner = memo(function DropInner(props: DropInnerProps) {
onDrop: drop,
draggable,
})}
{!noGhost &&
dragging?.ghost &&
activeDropTargetMatches &&
keyboardMode &&
dropType !== 'reorder'
? React.cloneElement(dragging.ghost, {
className: classNames('lnsDragDrop_ghost', children.props.className),
})
: null}
</>
);
});
Expand Down
Loading

0 comments on commit e19f9ad

Please sign in to comment.