From 1f7ab68a74deedcd6dd091c8a9bc7d5a5b913006 Mon Sep 17 00:00:00 2001 From: "Mr.Dr.Professor Patrick" Date: Fri, 24 Nov 2023 17:37:15 +0100 Subject: [PATCH 1/2] feat: change react-sortable-hoc with react-beautiful-dnd (#1159) --- .storybook/preview.tsx | 10 + package-lock.json | 145 ++++++++++-- package.json | 3 +- src/components/List/List.scss | 21 +- src/components/List/List.tsx | 218 ++++++++++++++---- .../List/__stories__/List.stories.tsx | 10 + src/components/List/components/ListItem.tsx | 30 ++- .../List/components/SimpleContainer.tsx | 62 +++-- src/components/List/types.ts | 4 + 9 files changed, 399 insertions(+), 104 deletions(-) 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; }; From 950848a87c23dd3dd065842c6cbeded6d771cae5 Mon Sep 17 00:00:00 2001 From: Gravity UI Bot <111915794+gravity-ui-bot@users.noreply.github.com> Date: Fri, 24 Nov 2023 19:41:59 +0300 Subject: [PATCH 2/2] chore(main): release 5.21.0 (#1126) --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6627c59e9..3d20435edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [5.21.0](https://github.com/gravity-ui/uikit/compare/v5.20.0...v5.21.0) (2023-11-24) + + +### Features + +* change react-sortable-hoc with react-beautiful-dnd ([#1159](https://github.com/gravity-ui/uikit/issues/1159)) ([1f7ab68](https://github.com/gravity-ui/uikit/commit/1f7ab68a74deedcd6dd091c8a9bc7d5a5b913006)) +* lazy loading for userAvatar ([#1124](https://github.com/gravity-ui/uikit/issues/1124)) ([e76fd7c](https://github.com/gravity-ui/uikit/commit/e76fd7ce9ab4a6adff05f5e816f385e1b5afc316)) + ## [5.20.0](https://github.com/gravity-ui/uikit/compare/v5.19.1...v5.20.0) (2023-11-14) diff --git a/package-lock.json b/package-lock.json index e6361d13fa..3e79f4d242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@gravity-ui/uikit", - "version": "5.20.0", + "version": "5.21.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@gravity-ui/uikit", - "version": "5.20.0", + "version": "5.21.0", "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", diff --git a/package.json b/package.json index ac3862fd83..c03f4ab856 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gravity-ui/uikit", - "version": "5.20.0", + "version": "5.21.0", "description": "Gravity UI base styling and components", "license": "MIT", "repository": {