Skip to content

Commit

Permalink
refactor DragDropSort to use DragDropContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
kmcfaul committed Jun 13, 2024
1 parent 49ee6d1 commit 1795c24
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 143 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface DragDropContainerProps extends DndContextProps {
* DualListSelectorList variant wraps the draggable objects in a DualListSelectorListItem and a div.pf-c-dual-list-selector__item-text element
* TableComposable variant wraps the draggable objects in TODO
* */
variant?: 'default' | 'DataList' | 'DualListSelector' | 'TableComposable';
variant?: 'default' | 'DataList' | 'DualListSelectorList' | 'TableComposable';
}

export const DragDropContainer: React.FunctionComponent<DragDropContainerProps> = ({
Expand Down Expand Up @@ -233,7 +233,7 @@ export const DragDropContainer: React.FunctionComponent<DragDropContainerProps>

let content;
switch (variant) {
case 'DualListSelector':
case 'DualListSelectorList':
content = (
<DraggableDualListSelectorListItem key={item.id} id={item.id} {...item.props}>
{item.content}
Expand Down
155 changes: 16 additions & 139 deletions packages/react-drag-drop/src/next/components/DragDrop/DragDropSort.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { css } from '@patternfly/react-styles';
import {
DndContext,
closestCenter,
DragOverlay,
DndContextProps,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
DragStartEvent,
CollisionDetection,
pointerWithin,
rectIntersection,
getFirstCollision,
UniqueIdentifier
} from '@dnd-kit/core';
import { arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
import { Draggable } from './Draggable';
import { DraggableDataListItem } from './DraggableDataListItem';
import { DraggableDualListSelectorListItem } from './DraggableDualListSelectorListItem';
import styles from '@patternfly/react-styles/css/components/DragDrop/drag-drop';
import { canUseDOM } from '@patternfly/react-core';
import { DndContextProps, DragEndEvent, DragStartEvent } from '@dnd-kit/core';
import { Droppable } from './Droppable';
import { DragDropContainer } from './DragDropContainer';

export type DragDropSortDragEndEvent = DragEndEvent;
export type DragDropSortDragStartEvent = DragStartEvent;
Expand Down Expand Up @@ -68,131 +45,31 @@ export const DragDropSort: React.FunctionComponent<DragDropSortProps> = ({
children,
...props
}: DragDropSortProps) => {
const [activeId, setActiveId] = React.useState<string>(null);
const itemIds = React.useMemo(() => (items ? Array.from(items, (item) => item.id as string) : []), [items]);

const getItemById = (id: string): DraggableObject => items.find((item) => item.id === id);

const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates
})
);

const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
const oldIndex = itemIds.indexOf(active.id as string);
const newIndex = itemIds.indexOf(over.id as string);
const newItems = arrayMove(items, oldIndex, newIndex);
onDrop(event, newItems, oldIndex, newIndex);
setActiveId(null);
};

const handleDragStart = (event: DragStartEvent) => {
setActiveId(event.active.id as string);
onDrag(event, itemIds.indexOf(event.active.id as string));
};

const lastOverId = React.useRef<UniqueIdentifier | null>(null);
const collisionDetectionStrategy: CollisionDetection = React.useCallback(
(args) => {
if (activeId && activeId in items) {
return closestCenter({
...args,
droppableContainers: args.droppableContainers.filter((container) => container.id in items)
});
}

const pointerIntersections = pointerWithin(args);
const intersections = pointerIntersections.length > 0 ? pointerIntersections : rectIntersection(args);
let overId = getFirstCollision(intersections, 'id');

if (overId != null) {
if (overId in items) {
const containerItems = items;

if (containerItems.length > 0) {
overId = closestCenter({
...args,
droppableContainers: args.droppableContainers.filter(
(container) => container.id !== overId && containerItems.find((obj) => obj.id === container.id)
)
})[0]?.id;
}
}

lastOverId.current = overId;

return [{ id: overId }];
}
return lastOverId.current ? [{ id: lastOverId.current }] : [];
},
[activeId, items]
);

const getDragOverlay = () => {
if (!activeId) {
return;
}
const item = getItemById(activeId);

let content;
switch (variant) {
case 'DualListSelectorList':
content = (
<DraggableDualListSelectorListItem key={item.id} id={item.id} {...item.props}>
{item.content}
</DraggableDualListSelectorListItem>
);
break;
case 'DataList':
content = (
<DraggableDataListItem key={item.id} id={item.id} {...item.props}>
{item.content}
</DraggableDataListItem>
);
break;
default:
content = (
<Draggable
useDragButton={variant === 'defaultWithHandle' || variant === 'default'}
key={item.id}
id={item.id}
{...item.props}
>
{item.content}
</Draggable>
);
}

return (
<div
className={css(styles.draggable, styles.modifiers.dragging)}
style={
{
'--pf-v5-c-draggable--m-dragging--BackgroundColor': 'var(--pf-v5-global--BackgroundColor--100)'
} as React.CSSProperties
}
>
{content}
</div>
);
const handleDragEnd = (event: DragEndEvent, newItems: Record<string, DraggableObject[]>) => {
const { active, over } = event;
const oldIndex = itemIds.indexOf(active.id as string);
const newIndex = itemIds.indexOf(over.id as string);
onDrop(event, newItems[dropZoneId], oldIndex, newIndex);
};

const dragOverlay = <DragOverlay>{activeId && getDragOverlay()}</DragOverlay>;
const dropZoneId = props.id ? props.id : 'droppable';
const containerVariant = variant === 'defaultWithHandle' ? 'default' : variant;

return (
<DndContext
sensors={sensors}
collisionDetection={collisionDetectionStrategy}
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
{...props}
<DragDropContainer
variant={containerVariant}
items={{ [dropZoneId]: items }}
onDrag={handleDragStart}
onDrop={handleDragEnd}
>
<Droppable items={items} id="droppable" variant={variant} {...(children && { wrapper: children })} />
{canUseDOM ? ReactDOM.createPortal(dragOverlay, document.getElementById('root')) : dragOverlay}
</DndContext>
<Droppable items={items} id={dropZoneId} variant={variant} {...(children && { wrapper: children })} />
</DragDropContainer>
);
};
DragDropSort.displayName = 'DragDropSort';
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const DragDropContainerDualListSelector: React.FunctionComponent = () =>
onDrop={handleDragOperation}
onContainerMove={handleDragOperation}
onCancel={handleDragOperation}
variant="DualListSelector"
variant="DualListSelectorList"
>
<DualListSelector>
<DualListSelectorPane
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ To enable multiple drop zones with `<DataList>` components, place one or more `<

### Dual list selector

To enable multiple drop zones in a `<DualListSelector>`, wrap the `<DualListSelector>` component with `<DragDropContainer>`, define the `variant` as "DualListSelector", and then within each pane's `<DualListSelectorList>`, include a `<Droppable>` component with the variant "DualListSelectorList".
To enable multiple drop zones in a `<DualListSelector>`, wrap the `<DualListSelector>` component with `<DragDropContainer>`, and then include a `<Droppable>` component within each pane. Both `<DragDropContainer>` and `<Droppable>` should define the `variant` property as "DualListSelectorList".

```ts file="./DragDropContainerDualListSelector.tsx"

Expand Down

0 comments on commit 1795c24

Please sign in to comment.