Skip to content

Commit b208822

Browse files
andybessmMarcusNotheis
authored andcommitted
feat(AnalyticalTable): Reordern columns with drag and drop (#229)
1 parent 14c75e0 commit b208822

File tree

11 files changed

+770
-20
lines changed

11 files changed

+770
-20
lines changed

packages/main/src/components/AnalyticalTable/AnalyticalTable.test.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { mountThemedComponent } from '@shared/tests/utils';
22
import { AnalyticalTable } from '@ui5/webcomponents-react/lib/AnalyticalTable';
33
import React from 'react';
4+
import { act } from 'react-dom/test-utils';
45

56
const columns = [
67
{
@@ -146,6 +147,7 @@ describe('AnalyticalTable', () => {
146147
.instance();
147148
// @ts-ignore
148149
component.onclick({});
150+
console.log(component);
149151

150152
// test desc function inside the popover element
151153
component = wrapper
@@ -172,9 +174,19 @@ describe('AnalyticalTable', () => {
172174
minRows={5}
173175
selectable={true}
174176
subRowsKey="subRows"
177+
isTreeTable={true}
175178
/>
176179
);
177180

181+
let colInst = wrapper
182+
.find({ role: 'columnheader' })
183+
.at(0)
184+
.instance();
185+
186+
// @ts-ignore
187+
expect(colInst.draggable).toBeDefined();
188+
// @ts-ignore
189+
expect(colInst.draggable).toBeFalsy();
178190
expect(wrapper.render()).toMatchSnapshot();
179191
});
180192

@@ -209,4 +221,33 @@ describe('AnalyticalTable', () => {
209221

210222
expect(wrapper.render()).toMatchSnapshot();
211223
});
224+
225+
test('test drag and drop of a draggable column', () => {
226+
const wrapper = mountThemedComponent(<AnalyticalTable data={data} title={'Test'} columns={columns} />);
227+
228+
// get first column of the table and simulate dragging of it
229+
let componentDrag = wrapper.find({ role: 'columnheader' }).at(0);
230+
let inst = componentDrag.instance();
231+
// @ts-ignore
232+
let dragColumnId = inst.id;
233+
234+
// @ts-ignore
235+
expect(inst.draggable).toBeDefined();
236+
// @ts-ignore
237+
expect(inst.draggable).toBeTruthy();
238+
// @ts-ignore
239+
componentDrag.simulate('drag');
240+
241+
// get second column of the table and simulate dropping on it
242+
let dataTransfer = {};
243+
// @ts-ignore
244+
dataTransfer.getData = () => {
245+
return dragColumnId;
246+
};
247+
let componentDrop = wrapper.find({ role: 'columnheader' }).at(1);
248+
// @ts-ignore
249+
componentDrop.simulate('drop', { dataTransfer: dataTransfer });
250+
251+
expect(wrapper.render()).toMatchSnapshot();
252+
});
212253
});

packages/main/src/components/AnalyticalTable/ColumnHeader/Resizer.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Resizer = (props) => {
88
const startX = useRef(0);
99
const resizerRef: RefObject<HTMLDivElement> = useRef();
1010
const onColumnSizeChanged = props['onColumnSizeChanged'];
11+
const onColumnBeingResized = props['onColumnBeingResized'];
1112

1213
const onResize = useCallback(
1314
(e) => {
@@ -30,8 +31,9 @@ const Resizer = (props) => {
3031
document.removeEventListener('mouseleave', onEndResize);
3132

3233
delete resizerRef.current.parentElement.style.userSelect;
34+
onColumnBeingResized({ value: false });
3335
},
34-
[onResize, resizerRef]
36+
[onResize, resizerRef, onColumnBeingResized]
3537
);
3638

3739
const onStartResize = useCallback(
@@ -44,8 +46,9 @@ const Resizer = (props) => {
4446
document.addEventListener('mousemove', onResize);
4547
document.addEventListener('mouseup', onEndResize);
4648
document.addEventListener('mouseleave', onEndResize);
49+
onColumnBeingResized({ value: true });
4750
},
48-
[onResize, onEndResize, parentWidth, startX, resizerRef]
51+
[onResize, onEndResize, parentWidth, startX, resizerRef, onColumnBeingResized]
4952
);
5053

5154
return (

packages/main/src/components/AnalyticalTable/ColumnHeader/index.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Event } from '@ui5/webcomponents-react-base/lib/Event';
22
import { StyleClassHelper } from '@ui5/webcomponents-react-base/lib/StyleClassHelper';
33
import { Icon } from '@ui5/webcomponents-react/lib/Icon';
4-
import React, { CSSProperties, FC, ReactNode, ReactNodeArray, useMemo } from 'react';
5-
import { createUseStyles } from 'react-jss';
4+
import React, { CSSProperties, DragEventHandler, FC, ReactNode, ReactNodeArray, useMemo } from 'react';
5+
import { createUseStyles, useTheme } from 'react-jss';
66
import { JSSTheme } from '../../../interfaces/JSSTheme';
77
import { Resizer } from './Resizer';
88
import { ColumnType } from '../types/ColumnType';
@@ -13,6 +13,7 @@ import '@ui5/webcomponents/dist/icons/sort-descending';
1313
import '@ui5/webcomponents/dist/icons/sort-ascending';
1414

1515
export interface ColumnHeaderProps {
16+
id: string;
1617
defaultSortDesc: boolean;
1718
onFilteredChange: (event: Event) => void;
1819
children: ReactNode | ReactNodeArray;
@@ -26,6 +27,14 @@ export interface ColumnHeaderProps {
2627
isLastColumn?: boolean;
2728
onSort?: (e: Event) => void;
2829
onGroupBy?: (e: Event) => void;
30+
onDragStart: DragEventHandler<HTMLDivElement>;
31+
onDragOver: DragEventHandler<HTMLDivElement>;
32+
onDrop: DragEventHandler<HTMLDivElement>;
33+
onDragEnter: DragEventHandler<HTMLDivElement>;
34+
onDragEnd: DragEventHandler<HTMLDivElement>;
35+
dragOver: boolean;
36+
isResizing: boolean;
37+
isDraggable: boolean;
2938
}
3039

3140
const styles = ({ parameters }: JSSTheme) => ({
@@ -64,6 +73,7 @@ export const ColumnHeader: FC<ColumnHeaderProps> = (props) => {
6473
const classes = useStyles(props);
6574

6675
const {
76+
id,
6777
children,
6878
column,
6979
className,
@@ -73,7 +83,14 @@ export const ColumnHeader: FC<ColumnHeaderProps> = (props) => {
7383
filterable,
7484
isLastColumn,
7585
onSort,
76-
onGroupBy
86+
onGroupBy,
87+
onDragEnter,
88+
onDragOver,
89+
onDragStart,
90+
onDrop,
91+
onDragEnd,
92+
isDraggable,
93+
dragOver
7794
} = props;
7895

7996
const openBy = useMemo(() => {
@@ -106,6 +123,7 @@ export const ColumnHeader: FC<ColumnHeaderProps> = (props) => {
106123
}, [classes, column.filterValue, column.isSorted, column.isGrouped, column.isSortedDesc, children]);
107124

108125
const isResizable = !isLastColumn && column.canResize;
126+
const theme = useTheme() as JSSTheme;
109127
const innerStyle: CSSProperties = useMemo(() => {
110128
const modifiedStyles = {
111129
...style,
@@ -118,13 +136,27 @@ export const ColumnHeader: FC<ColumnHeaderProps> = (props) => {
118136
if (isResizable) {
119137
modifiedStyles.maxWidth = `calc(100% - 16px)`;
120138
}
139+
if (dragOver) {
140+
modifiedStyles.borderLeft = '3px solid ' + theme.parameters.sapSelectedColor;
141+
}
121142
return modifiedStyles as CSSProperties;
122143
}, [style, isResizable]);
123144

124145
if (!column) return null;
125146

126147
return (
127-
<div className={className} style={style} role="columnheader">
148+
<div
149+
id={id}
150+
className={className}
151+
style={style}
152+
role="columnheader"
153+
draggable={isDraggable}
154+
onDragEnter={onDragEnter}
155+
onDragOver={onDragOver}
156+
onDragStart={onDragStart}
157+
onDrop={onDrop}
158+
onDragEnd={onDragEnd}
159+
>
128160
{groupable || sortable || filterable ? (
129161
<ColumnHeaderModal
130162
openBy={openBy}

0 commit comments

Comments
 (0)