diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index 05b89ff9eb..8ca0115752 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -20,6 +20,16 @@ configure({
});
const withContextProvider: Decorator = (Story, context) => {
+ if (context.parameters?.disableStrictMode) {
+ return (
+
+
+
+
+
+ );
+ }
+
return (
diff --git a/package-lock.json b/package-lock.json
index 0eb1bc0535..e6361d13fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,9 +16,9 @@
"blueimp-md5": "^2.19.0",
"focus-trap": "^7.5.2",
"lodash": "^4.17.21",
+ "react-beautiful-dnd": "^13.1.1",
"react-copy-to-clipboard": "^5.1.0",
"react-popper": "^2.3.0",
- "react-sortable-hoc": "2.0.0",
"react-transition-group": "^4.4.5",
"react-virtualized-auto-sizer": "^1.0.20",
"react-window": "^1.8.9",
@@ -51,6 +51,7 @@
"@types/jest": "^29.5.4",
"@types/lodash": "^4.14.197",
"@types/react": "^18.2.21",
+ "@types/react-beautiful-dnd": "^13.1.7",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.2.7",
"@types/react-transition-group": "^4.4.6",
@@ -7519,6 +7520,15 @@
"@types/node": "*"
}
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
+ "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"node_modules/@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -7687,8 +7697,7 @@
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
- "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
- "dev": true
+ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"node_modules/@types/qs": {
"version": "6.9.8",
@@ -7706,13 +7715,21 @@
"version": "18.2.21",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz",
"integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==",
- "dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/react-beautiful-dnd": {
+ "version": "13.1.7",
+ "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.1.7.tgz",
+ "integrity": "sha512-jQZLov9OkD0xRQkqz8/lx66bHYAYv+g4+POBqnH5Jtt/xo4MygzM879Q9sxAiosPBdNj1JYTdbPxDn3dNRYgow==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/react-copy-to-clipboard": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz",
@@ -7731,6 +7748,17 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-redux": {
+ "version": "7.1.31",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.31.tgz",
+ "integrity": "sha512-merF9AH72krBUekQY6uObXnMsEo1xTeZy9NONNRnqSwvwVe3HtLeRvNIPaKmPDIOWPsSFE51rc2WGpPMqmuCWg==",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
"node_modules/@types/react-transition-group": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
@@ -7761,8 +7789,7 @@
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
- "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
- "dev": true
+ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
"node_modules/@types/semver": {
"version": "7.5.0",
@@ -10921,6 +10948,14 @@
"node": ">=8"
}
},
+ "node_modules/css-box-model": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
+ "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
+ "dependencies": {
+ "tiny-invariant": "^1.0.6"
+ }
+ },
"node_modules/css-functions-list": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz",
@@ -15430,6 +15465,19 @@
"he": "bin/he"
}
},
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
"node_modules/homedir-polyfill": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
@@ -15915,6 +15963,7 @@
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
"dependencies": {
"loose-envify": "^1.0.0"
}
@@ -21821,6 +21870,11 @@
"node": ">=8"
}
},
+ "node_modules/raf-schd": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
+ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ=="
+ },
"node_modules/ramda": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.0.tgz",
@@ -21885,6 +21939,24 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-beautiful-dnd": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz",
+ "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2",
+ "css-box-model": "^1.2.0",
+ "memoize-one": "^5.1.1",
+ "raf-schd": "^4.0.2",
+ "react-redux": "^7.2.0",
+ "redux": "^4.0.4",
+ "use-memo-one": "^1.1.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.5 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/react-colorful": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz",
@@ -21997,8 +22069,7 @@
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
- "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
- "dev": true
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
},
"node_modules/react-popper": {
"version": "2.3.0",
@@ -22014,6 +22085,30 @@
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
+ "node_modules/react-redux": {
+ "version": "7.2.9",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
+ "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.15.4",
+ "@types/react-redux": "^7.1.20",
+ "hoist-non-react-statics": "^3.3.2",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2"
+ },
+ "peerDependencies": {
+ "react": "^16.8.3 || ^17 || ^18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -22070,21 +22165,6 @@
}
}
},
- "node_modules/react-sortable-hoc": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz",
- "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==",
- "dependencies": {
- "@babel/runtime": "^7.2.0",
- "invariant": "^2.2.4",
- "prop-types": "^15.5.7"
- },
- "peerDependencies": {
- "prop-types": "^15.5.7",
- "react": "^16.3.0 || ^17.0.0",
- "react-dom": "^16.3.0 || ^17.0.0"
- }
- },
"node_modules/react-style-singleton": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
@@ -22425,6 +22505,14 @@
"node": ">=8"
}
},
+ "node_modules/redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "dependencies": {
+ "@babel/runtime": "^7.9.2"
+ }
+ },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -24905,8 +24993,7 @@
"node_modules/tiny-invariant": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
- "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
- "dev": true
+ "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="
},
"node_modules/tmpl": {
"version": "1.0.5",
@@ -25782,6 +25869,14 @@
}
}
},
+ "node_modules/use-memo-one": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
+ "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/use-resize-observer": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz",
diff --git a/package.json b/package.json
index 1b408a4521..ac3862fd83 100644
--- a/package.json
+++ b/package.json
@@ -87,9 +87,9 @@
"blueimp-md5": "^2.19.0",
"focus-trap": "^7.5.2",
"lodash": "^4.17.21",
+ "react-beautiful-dnd": "^13.1.1",
"react-copy-to-clipboard": "^5.1.0",
"react-popper": "^2.3.0",
- "react-sortable-hoc": "2.0.0",
"react-transition-group": "^4.4.5",
"react-virtualized-auto-sizer": "^1.0.20",
"react-window": "^1.8.9",
@@ -122,6 +122,7 @@
"@types/jest": "^29.5.4",
"@types/lodash": "^4.14.197",
"@types/react": "^18.2.21",
+ "@types/react-beautiful-dnd": "^13.1.7",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.2.7",
"@types/react-transition-group": "^4.4.6",
diff --git a/src/components/List/List.scss b/src/components/List/List.scss
index 7156d11daa..8cf1d5fd70 100644
--- a/src/components/List/List.scss
+++ b/src/components/List/List.scss
@@ -36,17 +36,6 @@ $block: '.#{variables.$ns}list';
&__item {
height: var(--yc-list-item-height);
- &_sortable {
- cursor: move;
- }
-
- &_sorting {
- z-index: 100001;
- background: var(--g-color-base-simple-hover-solid);
- padding: 0 var(--yc-list-margin);
- cursor: move;
- }
-
&_active {
background: var(--g-color-base-simple-hover);
}
@@ -67,6 +56,16 @@ $block: '.#{variables.$ns}list';
margin-right: 0;
}
}
+
+ // https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/guides/preset-styles.md#phase-dragging-droppable-element
+ &_sortable[data-rbd-drag-handle-context-id]:active {
+ cursor: grabbing;
+ }
+
+ &_dragging {
+ background: var(--g-color-base-simple-hover-solid);
+ z-index: 100001;
+ }
}
&__empty-placeholder {
diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx
index f688d2fd00..3e8c3bee10 100644
--- a/src/components/List/List.tsx
+++ b/src/components/List/List.tsx
@@ -1,9 +1,17 @@
-/* eslint new-cap: "off" */
-
import React from 'react';
+import isEqual from 'lodash/isEqual';
import isObject from 'lodash/isObject';
-import {SortableContainer, SortableElement} from 'react-sortable-hoc';
+import {
+ DragDropContext,
+ Draggable,
+ DraggableProvided,
+ DraggableRubric,
+ DraggableStateSnapshot,
+ DropResult,
+ Droppable,
+ DroppableProvided,
+} from 'react-beautiful-dnd';
import AutoSizer, {Size} from 'react-virtualized-auto-sizer';
import {VariableSizeList as ListContainer} from 'react-window';
@@ -15,15 +23,12 @@ import {getUniqId} from '../utils/common';
import {ListItem, SimpleContainer, defaultRenderItem} from './components';
import {listNavigationIgnoredKeys} from './constants';
-import type {ListItemData, ListItemProps, ListProps, ListSortParams} from './types';
+import type {ListItemData, ListItemProps, ListProps} from './types';
import './List.scss';
const b = block('list');
const DEFAULT_ITEM_HEIGHT = 28;
-const SortableListItem = SortableElement(ListItem);
-const SortableListContainer = SortableContainer(ListContainer, {withRef: true});
-const SortableSimpleContainer = SortableContainer(SimpleContainer, {withRef: true});
type ListState = {
items: ListProps['items'];
@@ -42,6 +47,14 @@ export const listDefaultProps: Partial>> = {
deactivateOnLeave: true,
};
+const reorder = (list: T[], startIndex: number, endIndex: number): T[] => {
+ const result = Array.from(list);
+ const [removed] = result.splice(startIndex, 1);
+ result.splice(endIndex, 0, removed);
+
+ return result;
+};
+
export class List extends React.Component, ListState> {
static defaultProps: Partial>> = listDefaultProps;
@@ -86,7 +99,7 @@ export class List extends React.Component, ListState, prevState: ListState) {
- if (this.props.items !== prevProps.items) {
+ if (!isEqual(this.props.items, prevProps.items)) {
const filter = this.getFilter();
const internalFiltering = filter && !this.props.onFilterUpdate;
@@ -177,7 +190,6 @@ export class List extends React.Component, ListState = (event) => {
const {activeItem, pageSize} = this.state;
@@ -238,22 +250,30 @@ export class List extends React.Component, ListState {
+ private renderItem = ({
+ index,
+ style,
+ provided,
+ isDragging,
+ }: {
+ index: number;
+ style?: React.CSSProperties;
+ provided?: DraggableProvided;
+ isDragging?: boolean;
+ }) => {
const {sortHandleAlign, role} = this.props;
const {items, activeItem} = this.state;
const item = this.getItemsWithLoading()[index];
const sortable = this.props.sortable && items.length > 1 && !this.getFilter();
const active = index === activeItem || index === this.props.activeItemIndex;
- const Item = sortable ? SortableListItem : ListItem;
const selected = Array.isArray(this.props.selectedItemIndex)
? this.props.selectedItemIndex.includes(index)
: index === this.props.selectedItemIndex;
return (
- - extends React.Component, ListState
);
};
+ private renderVirtualizedItem = ({
+ index,
+ style,
+ }: {
+ index: number;
+ style?: React.CSSProperties;
+ }) => {
+ return (
+
+ {(provided: DraggableProvided) => this.renderItem({index, style, provided})}
+
+ );
+ };
+
private renderFilter() {
const {
size,
@@ -302,33 +338,121 @@ export class List extends React.Component, ListState
+ {
+ return this.renderItem({
+ index: rubric.source.index,
+ provided,
+ isDragging: snapshot.isDragging,
+ });
+ }}
+ >
+ {(droppableProvided: DroppableProvided) => (
+
+ {items.map((_item, index) => {
+ return (
+
+ {(
+ provided: DraggableProvided,
+ snapshot: DraggableStateSnapshot,
+ ) => {
+ return this.renderItem({
+ index,
+ isDragging: snapshot.isDragging,
+ provided,
+ style: {height: this.getItemHeight(index)},
+ });
+ }}
+
+ );
+ })}
+
+ )}
+
+
+ );
+ }
return (
-
+
{items.map((_item, index) =>
this.renderItem({index, style: {height: this.getItemHeight(index)}}),
)}
-
+
);
}
private renderVirtualizedContainer() {
- const Container = this.props.sortable ? SortableListContainer : ListContainer;
+ const items = this.getItems();
+
+ if (this.props.sortable) {
+ return (
+
+ {
+ return this.renderItem({
+ index: rubric.source.index,
+ provided,
+ isDragging: snapshot.isDragging,
+ });
+ }}
+ >
+ {(droppableProvided: DroppableProvided) => (
+
+ {({width, height}: Size) => (
+
+ {this.renderVirtualizedItem}
+
+ )}
+
+ )}
+
+
+ );
+ }
- const items = this.getItemsWithLoading();
return (
{({width, height}: Size) => (
- extends React.Component, ListState
{this.renderItem}
-
+
)}
);
@@ -362,17 +481,6 @@ export class List extends React.Component, ListState (item: ListItemData) => {
return String(item).includes(filter);
};
@@ -393,7 +501,7 @@ export class List extends React.Component, ListState {
- const container = this.getContainer();
+ const container = this.refContainer.current;
if (container) {
container.scrollToItem(index);
@@ -479,14 +587,28 @@ export class List extends React.Component, ListState {
+ private onSortEnd = (result: DropResult) => {
+ if (!result.destination) {
+ return;
+ }
+
+ if (result.source.index === result.destination.index) {
+ return;
+ }
+
+ const oldIndex = result.source.index;
+ const newIndex = result.destination.index;
+
if (this.props.onSortEnd) {
- this.props.onSortEnd(params);
+ this.props.onSortEnd({oldIndex, newIndex});
}
+ const nextItems = reorder(this.getItems(), oldIndex, newIndex);
+
this.setState({
+ activeItem: newIndex,
+ items: nextItems,
sorting: false,
- activeItem: params.newIndex,
});
};
diff --git a/src/components/List/__stories__/List.stories.tsx b/src/components/List/__stories__/List.stories.tsx
index fa0acdafb3..c103bbbd9d 100644
--- a/src/components/List/__stories__/List.stories.tsx
+++ b/src/components/List/__stories__/List.stories.tsx
@@ -33,6 +33,11 @@ Sortable.args = {
sortable: true,
itemsHeight: 150,
};
+Sortable.parameters = {
+ // Strict mode ruins sortable list due to this react-beautiful-dnd issue
+ // https://github.com/atlassian/react-beautiful-dnd/issues/2350
+ disableStrictMode: true,
+};
const RenderItemTemplate: ComponentStory = (args) =>
;
export const RenderItem = RenderItemTemplate.bind({});
@@ -44,3 +49,8 @@ RenderItem.args = {
const ShowcaseTemplate: ComponentStory = () => ;
export const Showcase = ShowcaseTemplate.bind({});
+Showcase.parameters = {
+ // Strict mode ruins sortable list due to this react-beautiful-dnd issue
+ // https://github.com/atlassian/react-beautiful-dnd/issues/2350
+ disableStrictMode: true,
+};
diff --git a/src/components/List/components/ListItem.tsx b/src/components/List/components/ListItem.tsx
index cf7541b20b..069c8c1c1f 100644
--- a/src/components/List/components/ListItem.tsx
+++ b/src/components/List/components/ListItem.tsx
@@ -1,5 +1,7 @@
import React from 'react';
+import type {DraggableProvided} from 'react-beautiful-dnd';
+
import {block} from '../../utils/cn';
import {eventBroker} from '../../utils/event-broker';
import {ListQa} from '../constants';
@@ -11,10 +13,21 @@ const b = block('list');
export const defaultRenderItem = (item: T) => String(item);
+function getStyle(provided?: DraggableProvided, style?: React.CSSProperties) {
+ if (!style) {
+ return provided?.draggableProps.style;
+ }
+
+ return {
+ ...provided?.draggableProps.style,
+ ...style,
+ };
+}
+
export class ListItem extends React.Component> {
private static publishEvent = eventBroker.withEventPublisher('List');
- ref = React.createRef();
+ node: HTMLDivElement | null = null;
render() {
const {
@@ -26,6 +39,7 @@ export class ListItem extends React.Component> {
selected,
active,
role = 'listitem',
+ isDragging = false,
} = this.props;
return (
@@ -42,15 +56,18 @@ export class ListItem extends React.Component> {
selected,
inactive: item.disabled,
'sort-handle-align': sortHandleAlign,
+ dragging: isDragging,
},
itemClassName,
)}
- style={style}
+ {...this.props.provided?.draggableProps}
+ {...this.props.provided?.dragHandleProps}
+ style={getStyle(this.props.provided, style)}
onClick={item.disabled ? undefined : this.onClick}
onClickCapture={item.disabled ? undefined : this.onClickCapture}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
- ref={this.ref}
+ ref={this.setRef}
id={`${this.props.listId}-item-${this.props.itemIndex}`}
>
{this.renderSortIcon()}
@@ -59,7 +76,12 @@ export class ListItem extends React.Component> {
);
}
- getRef = () => this.ref;
+ getNode = () => this.node;
+
+ private setRef = (node: HTMLDivElement) => {
+ this.node = node;
+ this.props.provided?.innerRef(node);
+ };
private renderSortIcon() {
const {sortable} = this.props;
diff --git a/src/components/List/components/SimpleContainer.tsx b/src/components/List/components/SimpleContainer.tsx
index b7afa9db83..293227dbe1 100644
--- a/src/components/List/components/SimpleContainer.tsx
+++ b/src/components/List/components/SimpleContainer.tsx
@@ -1,20 +1,29 @@
import React from 'react';
import _range from 'lodash/range';
+import type {DroppableProvided} from 'react-beautiful-dnd';
import type {ListItem} from './ListItem';
export type SimpleContainerProps = React.PropsWithChildren<{
itemCount: number;
+ provided?: DroppableProvided;
+ sortable?: boolean;
}>;
-export type SimpleContainerState = Record>;
+type RefsList = Record>;
+
+export type SimpleContainerState = {
+ refsList: RefsList;
+ minWidth?: number;
+ minHeight?: number;
+};
function getRefs(count: number) {
return _range(count).reduce((acc, index) => {
acc[index] = React.createRef();
return acc;
- }, {} as SimpleContainerState);
+ }, {} as RefsList);
}
export class SimpleContainer extends React.Component {
@@ -22,36 +31,59 @@ export class SimpleContainer extends React.Component
- React.cloneElement(child as React.ReactElement, {ref: this.state[index]}),
+ React.cloneElement(child as React.ReactElement, {ref: this.state.refsList[index]}),
+ );
+
+ return (
+
+ {children}
+
);
- return {children}
;
}
scrollToItem(index: number) {
- const listItem = this.state[index]?.current;
-
- if (listItem && typeof listItem.getRef === 'function') {
- const ref = listItem.getRef();
- if (ref.current) {
- ref.current.scrollIntoView?.({
- block: 'nearest',
- });
+ const listItem = this.state.refsList[index]?.current;
+
+ if (listItem && typeof listItem.getNode === 'function') {
+ const node = listItem.getNode();
+
+ if (node) {
+ node.scrollIntoView?.({block: 'nearest'});
}
}
}
+
+ private setRef = (node: HTMLDivElement) => {
+ this.node = node;
+ this.props.provided?.innerRef(node);
+ };
}
diff --git a/src/components/List/types.ts b/src/components/List/types.ts
index c55aa74480..2cfdbfcf55 100644
--- a/src/components/List/types.ts
+++ b/src/components/List/types.ts
@@ -1,5 +1,7 @@
import type React from 'react';
+import type {DraggableProvided} from 'react-beautiful-dnd';
+
import type {TextInputSize} from '../controls';
import type {QAProps} from '../types';
@@ -60,4 +62,6 @@ export type ListItemProps = {
onClick?: ListProps['onItemClick'];
role?: React.AriaRole;
listId?: string;
+ provided?: DraggableProvided;
+ isDragging?: boolean;
};