From 83eef0bc77c4f7df40f0419cac5438f11bb43864 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Fri, 2 Dec 2022 13:57:47 -0500 Subject: [PATCH] chore: migrates from react-sortable-hoc to @dnd-kit --- package.json | 5 +- .../ReactSelect/MultiValue/index.scss | 25 + .../elements/ReactSelect/MultiValue/index.tsx | 62 + .../ReactSelect}/MultiValueLabel/index.scss | 18 +- .../ReactSelect/MultiValueLabel/index.tsx | 50 + .../ReactSelect/ValueContainer/index.scss | 26 + .../ReactSelect/ValueContainer/index.tsx | 22 + .../elements/ReactSelect/index.scss | 49 +- .../components/elements/ReactSelect/index.tsx | 198 +- .../components/elements/ReactSelect/types.ts | 3 + .../Relationship/MultiValueLabel/index.tsx | 46 - .../forms/field-types/Relationship/index.tsx | 4 - .../forms/field-types/Relationship/types.ts | 6 +- src/utilities/arrayMove.ts | 9 - yarn.lock | 4452 +++++++++-------- 15 files changed, 2583 insertions(+), 2392 deletions(-) create mode 100644 src/admin/components/elements/ReactSelect/MultiValue/index.scss create mode 100644 src/admin/components/elements/ReactSelect/MultiValue/index.tsx rename src/admin/components/{forms/field-types/Relationship => elements/ReactSelect}/MultiValueLabel/index.scss (52%) create mode 100644 src/admin/components/elements/ReactSelect/MultiValueLabel/index.tsx create mode 100644 src/admin/components/elements/ReactSelect/ValueContainer/index.scss create mode 100644 src/admin/components/elements/ReactSelect/ValueContainer/index.tsx delete mode 100644 src/admin/components/forms/field-types/Relationship/MultiValueLabel/index.tsx delete mode 100644 src/utilities/arrayMove.ts diff --git a/package.json b/package.json index 5df36fdb5b..c38cd53adb 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,8 @@ "@babel/preset-typescript": "^7.12.1", "@babel/register": "^7.11.5", "@date-io/date-fns": "^2.10.6", + "@dnd-kit/core": "^6.0.5", + "@dnd-kit/sortable": "^7.0.1", "@faceless-ui/modal": "^2.0.1", "@faceless-ui/scroll-info": "^1.2.3", "@faceless-ui/window-info": "^2.0.2", @@ -177,7 +179,6 @@ "react-router-navigation-prompt": "^1.9.6", "react-select": "^3.0.8", "react-simple-code-editor": "^0.11.0", - "react-sortable-hoc": "^2.0.0", "react-toastify": "^8.2.0", "sanitize-filename": "^1.6.3", "sass": "^1.55.0", @@ -309,4 +310,4 @@ "publishConfig": { "registry": "https://registry.npmjs.org/" } -} \ No newline at end of file +} diff --git a/src/admin/components/elements/ReactSelect/MultiValue/index.scss b/src/admin/components/elements/ReactSelect/MultiValue/index.scss new file mode 100644 index 0000000000..8ac2748d35 --- /dev/null +++ b/src/admin/components/elements/ReactSelect/MultiValue/index.scss @@ -0,0 +1,25 @@ +@import '../../../../scss/styles.scss'; + +.multi-value { + &.rs__multi-value { + padding: 0; + background: transparent; + border: $style-stroke-width-s solid var(--theme-elevation-800); + line-height: calc(#{$baseline} - #{$style-stroke-width-s * 2}); + margin: base(.25) base(.5) base(.25) 0; + + &.draggable { + cursor: grab; + } + } + + .rs__multi-value__remove { + padding: 0 base(.125); + cursor: pointer; + + &:hover { + color: var(--theme-elevation-800); + background: var(--theme-error-150); + } + } +} diff --git a/src/admin/components/elements/ReactSelect/MultiValue/index.tsx b/src/admin/components/elements/ReactSelect/MultiValue/index.tsx new file mode 100644 index 0000000000..2b1ca08671 --- /dev/null +++ b/src/admin/components/elements/ReactSelect/MultiValue/index.tsx @@ -0,0 +1,62 @@ +import React, { MouseEventHandler } from 'react'; +import { + MultiValueProps, + components as SelectComponents, +} from 'react-select'; +import { useSortable } from '@dnd-kit/sortable'; +import { Option as OptionType } from '../types'; + +import './index.scss'; + +const baseClass = 'multi-value'; + +export const MultiValue: React.FC> = (props) => { + const { + className, + isDisabled, + innerProps, + data: { + value, + }, + } = props; + + const { attributes, listeners, setNodeRef, transform } = useSortable({ + id: value as string, + }); + + const onMouseDown: MouseEventHandler = (e) => { + // prevent the dropdown from opening when clicking on the drag handle + e.preventDefault(); + e.stopPropagation(); + }; + + const classes = [ + baseClass, + className, + !isDisabled && 'draggable', + ].filter(Boolean).join(' '); + + return ( + + ); +}; diff --git a/src/admin/components/forms/field-types/Relationship/MultiValueLabel/index.scss b/src/admin/components/elements/ReactSelect/MultiValueLabel/index.scss similarity index 52% rename from src/admin/components/forms/field-types/Relationship/MultiValueLabel/index.scss rename to src/admin/components/elements/ReactSelect/MultiValueLabel/index.scss index 0e1341ede9..d64f0907ca 100644 --- a/src/admin/components/forms/field-types/Relationship/MultiValueLabel/index.scss +++ b/src/admin/components/elements/ReactSelect/MultiValueLabel/index.scss @@ -1,4 +1,4 @@ -@import '../../../../../scss/styles.scss'; +@import '../../../../scss/styles.scss'; .multi-value-label { display: flex; @@ -13,8 +13,24 @@ justify-content: center; margin-left: base(0.25); + .icon { + width: base(0.75); + height: base(0.75); + } + &:hover { background-color: var(--theme-error-150); } } + + &__label { + padding: 0 base(.125) 0 base(.25); + max-width: 150px; + color: currentColor; + + > * { + text-overflow: ellipsis; + overflow: hidden; + } + } } diff --git a/src/admin/components/elements/ReactSelect/MultiValueLabel/index.tsx b/src/admin/components/elements/ReactSelect/MultiValueLabel/index.tsx new file mode 100644 index 0000000000..2cd392eb7c --- /dev/null +++ b/src/admin/components/elements/ReactSelect/MultiValueLabel/index.tsx @@ -0,0 +1,50 @@ +import React, { Fragment } from 'react'; +import { components, MultiValueProps } from 'react-select'; +import { useDocumentDrawer } from '../../DocumentDrawer'; +import Edit from '../../../icons/Edit'; +import { Option } from '../../../forms/field-types/Relationship/types'; +import './index.scss'; + +const baseClass = 'multi-value-label'; + +export const MultiValueLabel: React.FC> = (props) => { + const { + data: { + value, + relationTo, + label, + }, + selectProps, + } = props; + + const { DocumentDrawer, DocumentDrawerToggler } = useDocumentDrawer(); + + return ( +
+
+ +
+ {relationTo && ( + + + + + + + )} +
+ ); +}; diff --git a/src/admin/components/elements/ReactSelect/ValueContainer/index.scss b/src/admin/components/elements/ReactSelect/ValueContainer/index.scss new file mode 100644 index 0000000000..815e44718c --- /dev/null +++ b/src/admin/components/elements/ReactSelect/ValueContainer/index.scss @@ -0,0 +1,26 @@ +@import '../../../../scss/styles.scss'; + +.value-container { + flex-grow: 1; + + .rs__value-container { + padding: base(.25) 0; + min-height: base(1.5); + + > * { + margin: 0; + padding-top: 0; + padding-bottom: 0; + } + + &--is-multi { + margin-left: - base(0.25); + padding-top: base(0.25); + padding-bottom: base(0.25); + + > * { + margin: base(.125); + } + } + } +} diff --git a/src/admin/components/elements/ReactSelect/ValueContainer/index.tsx b/src/admin/components/elements/ReactSelect/ValueContainer/index.tsx new file mode 100644 index 0000000000..82aea89190 --- /dev/null +++ b/src/admin/components/elements/ReactSelect/ValueContainer/index.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { components as SelectComponents, ValueContainerProps } from 'react-select'; +import { Option } from '../types'; + +import './index.scss'; + +const baseClass = 'value-container'; + +export const ValueContainer: React.FC> = (props) => { + const { + selectProps, + } = props; + + return ( +
+ +
+ ); +}; diff --git a/src/admin/components/elements/ReactSelect/index.scss b/src/admin/components/elements/ReactSelect/index.scss index 97fd1bb174..b3ea3b4baf 100644 --- a/src/admin/components/elements/ReactSelect/index.scss +++ b/src/admin/components/elements/ReactSelect/index.scss @@ -6,24 +6,7 @@ div.react-select { height: auto; padding-top: base(.25); padding-bottom: base(.25); - } - - .rs__value-container { - padding: base(.25) 0; - min-height: base(1.5); - - >* { - margin-top: 0; - margin-bottom: 0; - padding-top: 0; - padding-bottom: 0; - } - - &--is-multi { - margin-left: - base(.25); - padding-top: 0; - padding-bottom: 0; - } + flex-wrap: nowrap; } .rs__indicators { @@ -83,34 +66,6 @@ div.react-select { color: currentColor; } - .rs__multi-value { - padding: 0; - background: transparent; - border: $style-stroke-width-s solid var(--theme-elevation-800); - line-height: calc(#{$baseline} - #{$style-stroke-width-s * 2}); - margin: base(.25) base(.5) base(.25) 0; - - &.draggable { - cursor: grab; - } - } - - .rs__multi-value__label { - padding: 0 base(.125) 0 base(.25); - max-width: 150px; - color: currentColor; - } - - .rs__multi-value__remove { - padding: 0 base(.125); - cursor: pointer; - - &:hover { - color: var(--theme-elevation-800); - background: var(--theme-error-150); - } - } - &--error { div.rs__control { background-color: var(--theme-error-200); @@ -120,4 +75,4 @@ div.react-select { &.rs--is-disabled .rs__control { background: var(--theme-elevation-200); } -} \ No newline at end of file +} diff --git a/src/admin/components/elements/ReactSelect/index.tsx b/src/admin/components/elements/ReactSelect/index.tsx index 264b137266..6f06c46318 100644 --- a/src/admin/components/elements/ReactSelect/index.tsx +++ b/src/admin/components/elements/ReactSelect/index.tsx @@ -1,61 +1,35 @@ -import React, { MouseEventHandler, useCallback } from 'react'; -import Select, { - components as SelectComponents, - MultiValueProps, - Props as SelectProps, -} from 'react-select'; +import React, { useCallback, useId } from 'react'; import { - SortableContainer, - SortableContainerProps, - SortableElement, - SortStartHandler, - SortEndHandler, - SortableHandle, -} from 'react-sortable-hoc'; + DragEndEvent, + useDroppable, + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import Select from 'react-select'; import { useTranslation } from 'react-i18next'; -import { arrayMove } from '../../../../utilities/arrayMove'; -import { Props, Option as OptionType } from './types'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, +} from '@dnd-kit/sortable'; +import { Props } from './types'; import Chevron from '../../icons/Chevron'; import { getTranslation } from '../../../../utilities/getTranslation'; - +import { MultiValueLabel } from './MultiValueLabel'; +import { MultiValue } from './MultiValue'; +import { ValueContainer } from './ValueContainer'; import './index.scss'; -const SortableMultiValue = SortableElement( - (props: MultiValueProps) => { - // this prevents the menu from being opened/closed when the user clicks - // on a value to begin dragging it. ideally, detecting a click (instead of - // a drag) would still focus the control and toggle the menu, but that - // requires some magic with refs that are out of scope for this example - const onMouseDown: MouseEventHandler = (e) => { - e.preventDefault(); - e.stopPropagation(); - }; - const classes = [ - props.className, - !props.isDisabled && 'draggable', - ].filter(Boolean).join(' '); - - return ( - - ); - }, -); - - -const SortableMultiValueLabel = SortableHandle((props) => ); - -const SortableSelect = SortableContainer(Select) as React.ComponentClass & SortableContainerProps>; - -const ReactSelect: React.FC = (props) => { +const SelectAdapter: React.FC = (props) => { const { t, i18n } = useTranslation(); const { className, - showError = false, + showError, options, onChange, value, @@ -63,12 +37,11 @@ const ReactSelect: React.FC = (props) => { placeholder = t('general:selectValue'), isSearchable = true, isClearable = true, - isMulti, - isSortable, filterOption = undefined, isLoading, onMenuOpen, components, + droppableRef, } = props; const classes = [ @@ -77,54 +50,6 @@ const ReactSelect: React.FC = (props) => { showError && 'react-select--error', ].filter(Boolean).join(' '); - const onSortStart: SortStartHandler = useCallback(({ helper }) => { - const portalNode = helper; - if (portalNode && portalNode.style) { - portalNode.style.cssText += 'pointer-events: auto; cursor: grabbing;'; - } - }, []); - - const onSortEnd: SortEndHandler = useCallback(({ oldIndex, newIndex }) => { - onChange(arrayMove(value as OptionType[], oldIndex, newIndex)); - }, [onChange, value]); - - if (isMulti && isSortable) { - return ( - node.getBoundingClientRect()} - // react-select props: - placeholder={getTranslation(placeholder, i18n)} - {...props} - value={value as OptionType[]} - onChange={onChange} - disabled={disabled ? 'disabled' : undefined} - className={classes} - classNamePrefix="rs" - captureMenuScroll - options={options} - isSearchable={isSearchable} - isClearable={isClearable} - isLoading={isLoading} - onMenuOpen={onMenuOpen} - filterOption={filterOption} - components={{ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore We're failing to provide a required index prop to SortableElement - MultiValue: SortableMultiValue, - MultiValueLabel: SortableMultiValueLabel, - DropdownIndicator: Chevron, - ...components, - }} - /> - ); - } - return (