Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Table] Draggable feature implement by react-dnd #198

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/core/src/table/table.ts
Expand Up @@ -106,6 +106,12 @@ export interface TableRowSelection {
actions?: TableRowAction[];
}

/** === Feature draggable */
export interface TableDraggable {
enabled: boolean;
onDragEnd?: (nextDataSource: any[]) => void;
}

/** === Feature Expandable */
export type ExpandRowBySources = {
dataSource: TableDataSource[];
Expand Down
2 changes: 2 additions & 0 deletions packages/react/package.json
Expand Up @@ -38,6 +38,8 @@
"@types/react-transition-group": "^4.4.5",
"clsx": "^1.2.1",
"lodash": "^4.17.21",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-popper": "^2.3.0",
"react-transition-group": "^4.4.5",
"tslib": "^2.4.1"
Expand Down
44 changes: 44 additions & 0 deletions packages/react/src/Table/Table.stories.tsx
Expand Up @@ -24,6 +24,7 @@ import Switch from '../Switch';
import Input from '../Input';
import Select, { Option } from '../Select';
import { cx } from '../utils/cx';
import Message from '../Message/Message';

export default {
title: 'Data Display/Table',
Expand Down Expand Up @@ -255,6 +256,46 @@ export const ScrollableAndFixedColumn = () => {
);
};

export const Draggable = () => {
const [loading, setLoading] = useState<boolean>(false);
const [source, setSource] = useState<DataType[]>(dataSource);

return (
<div style={{ width: '100%' }}>
<h3>
Draggable
</h3>
<div
style={{
width: '80%',
}}
>
<Table
loading={loading}
columns={columns}
dataSource={source}
draggable={{
enabled: true,
onDragEnd: async (nextSources) => {
setLoading(true);
Message.info('更新排序中...');

setTimeout(() => {
setSource(nextSources);
setLoading(false);
Message.success('已成功更新排序');
}, 1000);
},
}}
scroll={{
y: 400,
}}
/>
</div>
</div>
);
};

export const Selections = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);

Expand Down Expand Up @@ -423,6 +464,9 @@ export const WithActions = () => {
<Table
columns={withActionColumns}
dataSource={dataSource}
draggable={{
enabled: true,
}}
/>
</div>
</div>
Expand Down
170 changes: 92 additions & 78 deletions packages/react/src/Table/Table.tsx
Expand Up @@ -5,6 +5,8 @@ import {
useMemo,
useRef,
} from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import {
tableClasses as classes,
TableColumn,
Expand All @@ -17,6 +19,7 @@ import {
TableRefresh as TableRefreshType,
ExpandRowBySources,
TableScrolling,
TableDraggable,
} from '@mezzanine-ui/core/table';
import { EmptyProps } from '../Empty';
import { TableContext, TableDataContext, TableComponentContext } from './TableContext';
Expand All @@ -36,7 +39,7 @@ import { useComposeRefs } from '../hooks/useComposeRefs';

export interface TableBaseProps<T>
extends
Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'role'> {
Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'role' | 'draggable'> {
/**
* customized body className
*/
Expand All @@ -62,6 +65,12 @@ export interface TableBaseProps<T>
* Notice that each source should contain `key` or `id` prop as data primary key.
*/
dataSource: TableDataSource[];
/**
* Draggable table row. This feature allows sort items easily. Be aware of combining different features (may be buggy at sometimes).
* When `draggable.enabled` is true, draggable will be enabled.
* `draggable.onDragEnd` return new dataSource for you
*/
draggable?: TableDraggable;
/**
* props exported from `<Empty />` component.
*/
Expand Down Expand Up @@ -146,6 +155,7 @@ const Table = forwardRef<HTMLTableElement, TableProps<Record<string, unknown>>>(
columns,
components,
dataSource: dataSourceProp,
draggable: draggableProp,
emptyProps,
expandable: expandableProp,
fetchMore: fetchMoreProp,
Expand Down Expand Up @@ -248,6 +258,7 @@ const Table = forwardRef<HTMLTableElement, TableProps<Record<string, unknown>>>(
},
} : undefined,
scroll: scrollProp,
draggable: draggableProp,
}), [
dataSource,
emptyProps,
Expand All @@ -265,6 +276,7 @@ const Table = forwardRef<HTMLTableElement, TableProps<Record<string, unknown>>>(
paginationProp,
isHorizontalScrolling,
scrollProp,
draggableProp,
]);

const tableDataContextValue = useMemo(() => ({
Expand All @@ -282,92 +294,94 @@ const Table = forwardRef<HTMLTableElement, TableProps<Record<string, unknown>>>(
<TableContext.Provider value={tableContextValue}>
<TableDataContext.Provider value={tableDataContextValue}>
<TableComponentContext.Provider value={tableComponentContextValue}>
<Loading
loading={loading}
stretch
tip={loadingTip}
overlayProps={{
className: classes.loading,
}}
>
<div
ref={scrollBody.ref}
className={cx(classes.scrollContainer, scrollContainerClassName)}
onScroll={scrollBody.onScroll}
style={tableContextValue.scroll ? {
'--table-scroll-x': tableContextValue.scroll.x
? `${tableContextValue.scroll.x}px`
: '100%',
'--table-scroll-y': tableContextValue.scroll.y
? `${tableContextValue.scroll.y}px`
: 'unset',
} as CSSProperties : undefined}
<DndProvider backend={HTML5Backend}>
<Loading
loading={loading}
stretch
tip={loadingTip}
overlayProps={{
className: classes.loading,
}}
>
<table
ref={tableRefs}
{...rest}
className={cx(classes.host, className)}
<div
ref={scrollBody.ref}
className={cx(classes.scrollContainer, scrollContainerClassName)}
onScroll={scrollBody.onScroll}
style={tableContextValue.scroll ? {
'--table-scroll-x': tableContextValue.scroll.x
? `${tableContextValue.scroll.x}px`
: '100%',
'--table-scroll-y': tableContextValue.scroll.y
? `${tableContextValue.scroll.y}px`
: 'unset',
} as CSSProperties : undefined}
>
{isRefreshShow ? (
<tbody>
<tr>
<td>
<TableRefresh onClick={(refreshProp as TableRefreshType).onClick} />
</td>
</tr>
</tbody>
) : null}
<TableHeader className={headerClassName} />
<TableBody
ref={bodyRef}
className={bodyClassName}
rowClassName={bodyRowClassName}
/>
</table>
</div>
{paginationProp ? (
<TablePagination bodyRef={bodyRef} />
) : null}
<div
ref={scrollElement.trackRef}
style={scrollElement.trackStyle}
onMouseDown={scrollElement.onMouseDown}
onMouseUp={scrollElement.onMouseUp}
role="button"
tabIndex={-1}
className="mzn-table-scroll-bar-track"
>
<table
ref={tableRefs}
{...rest}
className={cx(classes.host, className)}
>
{isRefreshShow ? (
<tbody>
<tr>
<td>
<TableRefresh onClick={(refreshProp as TableRefreshType).onClick} />
</td>
</tr>
</tbody>
) : null}
<TableHeader className={headerClassName} />
<TableBody
ref={bodyRef}
className={bodyClassName}
rowClassName={bodyRowClassName}
/>
</table>
</div>
{paginationProp ? (
<TablePagination bodyRef={bodyRef} />
) : null}
<div
style={{
width: '100%',
height: '100%',
position: 'relative',
}}
ref={scrollElement.trackRef}
style={scrollElement.trackStyle}
onMouseDown={scrollElement.onMouseDown}
onMouseUp={scrollElement.onMouseUp}
role="button"
tabIndex={-1}
className="mzn-table-scroll-bar-track"
>
<div
ref={scrollElement.ref}
onMouseDown={scrollElement.onMouseDown}
onMouseUp={scrollElement.onMouseUp}
onMouseEnter={scrollElement.onMouseEnter}
onMouseLeave={scrollElement.onMouseLeave}
role="button"
style={scrollElement.style}
tabIndex={-1}
className="mzn-table-scroll-bar"
style={{
width: '100%',
height: '100%',
position: 'relative',
}}
>
<div
style={{
width: `${scrollElement.scrollBarSize}px`,
height: '100%',
borderRadius: '10px',
backgroundColor: '#7d7d7d',
transition: '0.1s',
}}
/>
ref={scrollElement.ref}
onMouseDown={scrollElement.onMouseDown}
onMouseUp={scrollElement.onMouseUp}
onMouseEnter={scrollElement.onMouseEnter}
onMouseLeave={scrollElement.onMouseLeave}
role="button"
style={scrollElement.style}
tabIndex={-1}
className="mzn-table-scroll-bar"
>
<div
style={{
width: `${scrollElement.scrollBarSize}px`,
height: '100%',
borderRadius: '10px',
backgroundColor: '#7d7d7d',
transition: '0.1s',
}}
/>
</div>
</div>
</div>
</div>
</Loading>
</Loading>
</DndProvider>
</TableComponentContext.Provider>
</TableDataContext.Provider>
</TableContext.Provider>
Expand Down
35 changes: 33 additions & 2 deletions packages/react/src/Table/TableBody.tsx
@@ -1,17 +1,24 @@
import {
forwardRef,
useCallback,
useContext,
useEffect,
useState,
} from 'react';
import {
tableClasses as classes,
TableDataSource,
} from '@mezzanine-ui/core/table';
import isEqual from 'lodash/isEqual';
import { useDrop } from 'react-dnd';
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
import { cx } from '../utils/cx';
import { TableContext, TableDataContext } from './TableContext';
import TableBodyRow from './TableBodyRow';
import TableBodyRow, { MZN_TABLE_DRAGGABLE_KEY } from './TableBodyRow';
import Empty from '../Empty';
import Loading from '../Loading/Loading';
import { useComposeRefs } from '../hooks/useComposeRefs';
import { usePreviousValue } from '../hooks/usePreviousValue';

export interface TableBodyProps extends NativeElementPropsWithoutKeyAndRef<'div'> {
/**
Expand All @@ -35,6 +42,7 @@ const TableBody = forwardRef<HTMLTableSectionElement, TableBodyProps>(function T
emptyProps,
fetchMore,
pagination,
draggable,
} = useContext(TableContext) || {};

/** customizing empty */
Expand Down Expand Up @@ -65,10 +73,29 @@ const TableBody = forwardRef<HTMLTableSectionElement, TableBodyProps>(function T
dataSource.slice(currentStartCount, currentEndCount)
) : dataSource;

/** Feature Dragging */
const prevDataSource = usePreviousValue(currentDataSource);
const [draggingSource, setDraggingSource] = useState<typeof dataSource>(currentDataSource);
const onResetDragging = useCallback(() => setDraggingSource(currentDataSource), [currentDataSource]);
const onDragEnd = useCallback(() => {
if (draggable?.onDragEnd) {
draggable.onDragEnd(draggingSource);
}
}, [draggable, draggingSource]);

useEffect(() => {
if (!isEqual(currentDataSource, prevDataSource)) {
setDraggingSource(currentDataSource);
}
}, [currentDataSource, prevDataSource]);

const [, dropRef] = useDrop(() => ({ accept: MZN_TABLE_DRAGGABLE_KEY }));
const composedRef = useComposeRefs([ref, dropRef]);

return (
<tbody
{...rest}
ref={ref}
ref={composedRef}
className={cx(
classes.body,
className,
Expand All @@ -80,6 +107,10 @@ const TableBody = forwardRef<HTMLTableSectionElement, TableBodyProps>(function T
className={rowClassName}
rowData={rowData}
rowIndex={index}
draggingData={draggingSource}
setDraggingData={setDraggingSource}
onDragEnd={draggable?.onDragEnd ? onDragEnd : undefined}
onResetDragging={onResetDragging}
/>
)) : (
<tr>
Expand Down