From aa130f85ceb8e3977163d9f47e3a47238e1d9e66 Mon Sep 17 00:00:00 2001 From: arsenykruglikov <5552804+arsenykruglikov@users.noreply.github.com> Date: Mon, 3 Feb 2025 16:21:24 +0300 Subject: [PATCH] chore(plasma-hope): migrate from `react-sortable-hoc` to `@dnd-kit/sortable` --- packages/plasma-hope/api/plasma-hope.api.md | 6 +- packages/plasma-hope/package-lock.json | 142 ++++++++++++------ packages/plasma-hope/package.json | 3 +- .../PreviewGallery/PreviewGallery.tsx | 24 +-- .../PreviewGallery/PreviewGalleryItems.tsx | 138 ++++++++++++----- 5 files changed, 213 insertions(+), 100 deletions(-) diff --git a/packages/plasma-hope/api/plasma-hope.api.md b/packages/plasma-hope/api/plasma-hope.api.md index 1103a1285f..8c2328517b 100644 --- a/packages/plasma-hope/api/plasma-hope.api.md +++ b/packages/plasma-hope/api/plasma-hope.api.md @@ -116,8 +116,6 @@ import { ShiftProps } from '@salutejs/plasma-core'; import { SmartPaginationDotsProps as SmartPaginationDotsProps_2 } from '@salutejs/plasma-core'; import { SnapAlign } from '@salutejs/plasma-core'; import { SnapType } from '@salutejs/plasma-core'; -import { SortableContainerProps } from 'react-sortable-hoc'; -import { SortableElementProps } from 'react-sortable-hoc'; import { SpacingProps } from '@salutejs/plasma-core'; import { StyledCard } from '@salutejs/plasma-core'; import { StyledComponent } from 'styled-components'; @@ -804,7 +802,7 @@ export { Popup } export { PopupProps } // @public -export const PreviewGallery: FC & SortableContainerProps>; +export const PreviewGallery: FC>; // @public (undocumented) export interface PreviewGalleryItemProps { @@ -826,7 +824,7 @@ export interface PreviewGalleryProps { actionIcon: JSX.Element; // Warning: (ae-forgotten-export) The symbol "InteractionType" needs to be exported by the entry point index.d.ts interactionType?: InteractionType; - items?: Array>; + items?: Array; itemSize?: string; maxHeight?: number; onItemAction?: (id: string | number) => void; diff --git a/packages/plasma-hope/package-lock.json b/packages/plasma-hope/package-lock.json index 64d7b488c0..e2a6dcb8b4 100644 --- a/packages/plasma-hope/package-lock.json +++ b/packages/plasma-hope/package-lock.json @@ -9,12 +9,13 @@ "version": "1.329.0", "license": "MIT", "dependencies": { + "@dnd-kit/core": "6.3.1", + "@dnd-kit/sortable": "10.0.0", "@popperjs/core": "2.9.2", "@salutejs/plasma-core": "1.191.0", "@salutejs/plasma-typo": "0.41.0", "react-file-drop": "3.1.6", "react-popper": "2.3.0", - "react-sortable-hoc": "2.0.0", "storeon": "3.1.4" }, "devDependencies": { @@ -2010,6 +2011,7 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -2105,6 +2107,55 @@ "node": ">=0.1.90" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -6504,14 +6555,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -12631,21 +12674,6 @@ "react-dom": "^16.8.0 || ^17 || ^18" } }, - "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/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -12667,7 +12695,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -13357,8 +13386,7 @@ "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -15434,6 +15462,7 @@ "version": "7.15.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -15508,6 +15537,41 @@ "dev": true, "optional": true }, + "@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "requires": { + "tslib": "^2.0.0" + } + }, + "@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "requires": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + } + }, + "@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "requires": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + } + }, + "@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "requires": { + "tslib": "^2.0.0" + } + }, "@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -18909,14 +18973,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, "is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -23538,16 +23594,6 @@ "warning": "^4.0.2" } }, - "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==", - "requires": { - "@babel/runtime": "^7.2.0", - "invariant": "^2.2.4", - "prop-types": "^15.5.7" - } - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -23566,7 +23612,8 @@ "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true }, "regenerator-transform": { "version": "0.15.2", @@ -24057,8 +24104,7 @@ "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "tsutils": { "version": "3.21.0", diff --git a/packages/plasma-hope/package.json b/packages/plasma-hope/package.json index efd0ec8dab..e46c3b2441 100644 --- a/packages/plasma-hope/package.json +++ b/packages/plasma-hope/package.json @@ -33,12 +33,13 @@ "directory": "packages/plasma-web-core" }, "dependencies": { + "@dnd-kit/core": "6.3.1", + "@dnd-kit/sortable": "10.0.0", "@popperjs/core": "2.9.2", "@salutejs/plasma-core": "1.191.0", "@salutejs/plasma-typo": "0.41.0", "react-file-drop": "3.1.6", "react-popper": "2.3.0", - "react-sortable-hoc": "2.0.0", "storeon": "3.1.4" }, "peerDependencies": { diff --git a/packages/plasma-hope/src/components/PreviewGallery/PreviewGallery.tsx b/packages/plasma-hope/src/components/PreviewGallery/PreviewGallery.tsx index 779a606fa7..c53a4ce413 100644 --- a/packages/plasma-hope/src/components/PreviewGallery/PreviewGallery.tsx +++ b/packages/plasma-hope/src/components/PreviewGallery/PreviewGallery.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useState } from 'react'; import type { FC, HTMLAttributes } from 'react'; -import { SortableContainerProps, SortableElementProps } from 'react-sortable-hoc'; +import type { DragEndEvent } from '@dnd-kit/core'; import { noop } from './utils'; import { PreviewGalleryListItems } from './PreviewGalleryItems'; @@ -16,7 +16,7 @@ export interface PreviewGalleryProps { /** * Массив элементов. */ - items?: Array>; + items?: Array; /** * Тип взаимодействия с галереей - выбор или перетаскивание элементов. */ @@ -50,7 +50,7 @@ export interface PreviewGalleryProps { /** * Компонент для создания галереи превью изображений. */ -export const PreviewGallery: FC & SortableContainerProps> = ({ +export const PreviewGallery: FC> = ({ interactionType = 'selectable', items = [], maxHeight = 0, @@ -59,19 +59,25 @@ export const PreviewGallery: FC { - const distance = 1; - const axis = 'xy'; const [isGrabbing, setGrabbing] = useState(false); const onSortStart = useCallback(() => setGrabbing(true), []); const onSortEnd = useCallback( - (indexes: SortableIndexProps) => { + (event: DragEndEvent) => { setGrabbing(false); - onItemsSortEnd(indexes); + const { active, over } = event; + if (!over || active.id === over.id) { + return; + } + + const oldIndex = items.findIndex((item) => item.id === active.id); + const newIndex = items.findIndex((item) => item.id === over.id); + + onItemsSortEnd({ oldIndex, newIndex }); }, - [onItemsSortEnd], + [items, onItemsSortEnd], ); return ( @@ -81,8 +87,6 @@ export const PreviewGallery: FC>; + items?: Array; /** * Перетаскивается ли элемент. */ @@ -38,50 +47,79 @@ export interface PreviewGalleryListItemsProps { * Опциональная высота для внутреннего scroll. */ maxHeight?: number; + /** + * Callback when sorting ends + */ + onSortEnd?: (event: DragEndEvent) => void; + /** + * Callback when sorting starts + */ + onSortStart?: () => void; } -const PreviewGalleryItem = memo( - SortableElement( - ({ status, ...itemRest }: PreviewGalleryItemProps & AddionalItemProps) => { - return status === 'error' ? ( - - ) : ( - - ); - }, - ), -); +const SortableItem = memo(({ id, status, ...itemRest }: PreviewGalleryItemProps & AddionalItemProps) => { + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id }); + + const style = { + transform: transform ? `translate3d(${transform.x}px, ${transform.y}px, 0)` : undefined, + transition, + zIndex: isDragging ? 1 : undefined, + position: 'relative' as const, + }; + + const Component = status === 'error' ? PreviewGalleryItemError : PreviewGalleryItemBase; + + return ( +
+ +
+ ); +}); /** * Компонент со списком превью изображений. */ -export const PreviewGalleryListItems = SortableContainer( - ({ - items = [], - interactionType, - actionIcon, - itemSize, - isGrabbing, - maxHeight, - onItemAction, - onItemClick, - ...rest - }: PreviewGalleryListItemsProps & AddionalItemProps) => { - const isDragDisabled = interactionType === 'selectable'; +export const PreviewGalleryListItems = ({ + items = [], + interactionType, + actionIcon, + itemSize, + isGrabbing, + maxHeight, + onItemAction, + onItemClick, + onSortEnd, + onSortStart, + ...rest +}: PreviewGalleryListItemsProps & AddionalItemProps) => { + const isDragDisabled = interactionType === 'selectable'; + + // Setup sensors for drag detection + const sensors = useSensors( + useSensor(MouseSensor, { + activationConstraint: { + distance: 1, + }, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 250, + tolerance: 5, + }, + }), + ); - // deleteIcon не указан в зависимости, т.к. предполагается, - // что данный пропс не будет меняться динамически - const iconMemo = useMemo(() => actionIcon, []); + // deleteIcon не указан в зависимости, т.к. предполагается, + // что данный пропс не будет меняться динамически + const iconMemo = useMemo(() => actionIcon, []); + if (isDragDisabled) { return ( - {items.map((item, index) => ( - - ( + + ); - }, -); + } + + return ( + + + item.id)} strategy={rectSortingStrategy}> + {items.map((item) => ( + + + + ))} + + + + ); +};