diff --git a/packages/react-core/src/components/DataList/examples/DataList.md b/packages/react-core/src/components/DataList/examples/DataList.md
index 88e96dd7b4f..6c4a2f26c5b 100644
--- a/packages/react-core/src/components/DataList/examples/DataList.md
+++ b/packages/react-core/src/components/DataList/examples/DataList.md
@@ -13,8 +13,7 @@ propComponents:
'DataListItemRow',
'DataListToggle',
'DataListContent',
- 'DataListDragButton',
- 'DataListControl'
+ 'DataListControl',
]
---
@@ -84,16 +83,6 @@ import global_BorderWidth_sm from '@patternfly/react-tokens/dist/esm/global_Bord
```
-### Draggable
-
-Draggable data lists used to have their own HTML5-based API for drag and drop, which wasn't able to fulfill requirements such as custom styling on items being dragged. So we wrote generic `DragDrop`, `Draggable`, and `Droppable` components for this purpose. Use those new components instead of the deprecated (and buggy!) HTML5-based API.
-
-Note: Keyboard accessibility and screen reader accessibility for the `DragDrop` component are still in development.
-
-```ts isBeta file="./DataListDraggable.tsx"
-
-```
-
### Small grid breakpoint
```ts file="./DataListSmGridBreakpoint.tsx"
diff --git a/packages/react-core/src/components/DataList/examples/DataListDraggable.tsx b/packages/react-core/src/components/DataList/examples/DataListDraggable.tsx
deleted file mode 100644
index c55244af0b1..00000000000
--- a/packages/react-core/src/components/DataList/examples/DataListDraggable.tsx
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from 'react';
-import {
- DataList,
- DataListItem,
- DataListCell,
- DataListItemRow,
- DataListCheck,
- DataListControl,
- DataListDragButton,
- DataListItemCells,
- DragDrop,
- Draggable,
- Droppable,
- getUniqueId
-} from '@patternfly/react-core';
-
-interface ItemType {
- id: string;
- content: string;
-}
-
-const getItems = (count: number) =>
- Array.from({ length: count }, (_, idx) => idx).map((idx) => ({
- id: `draggable-item-${idx}`,
- content: `item ${idx} `.repeat(idx === 4 ? 20 : 1)
- }));
-
-const reorder = (list: ItemType[], startIndex: number, endIndex: number) => {
- const result = list;
- const [removed] = result.splice(startIndex, 1);
- result.splice(endIndex, 0, removed);
- return result;
-};
-
-export const DataListDraggable: React.FunctionComponent = () => {
- const [items, setItems] = React.useState(getItems(10));
- const [liveText, setLiveText] = React.useState('');
-
- function onDrag(source) {
- setLiveText(`Started dragging ${items[source.index].content}`);
- // Return true to allow drag
- return true;
- }
-
- function onDragMove(source, dest) {
- const newText = dest ? `Move ${items[source.index].content} to ${items[dest.index].content}` : 'Invalid drop zone';
- if (newText !== liveText) {
- setLiveText(newText);
- }
- }
-
- function onDrop(source, dest) {
- if (dest) {
- const newItems = reorder(items, source.index, dest.index);
- setItems(newItems);
-
- setLiveText('Dragging finished.');
- return true; // Signal that this is a valid drop and not to animate the item returning home.
- } else {
- setLiveText('Dragging cancelled. List unchanged.');
- }
- }
-
- const uniqueId = getUniqueId();
-
- return (
-
-
-
-
-
- {liveText}
-
-
- Press space or enter to begin dragging, and use the arrow keys to navigate up or down. Press enter to confirm
- the drag, or any other key to cancel the drag operation.
-
-
- );
-};
diff --git a/packages/react-core/src/components/DragDrop/examples/DragDrop.md b/packages/react-core/src/components/DragDrop/examples/DragDrop.md
index b3a379223ee..7e700e1c8e8 100644
--- a/packages/react-core/src/components/DragDrop/examples/DragDrop.md
+++ b/packages/react-core/src/components/DragDrop/examples/DragDrop.md
@@ -2,7 +2,7 @@
id: Drag and drop
section: components
propComponents: [DragDrop, Draggable, Droppable, DraggableItemPosition]
-beta: true
+title: React - next
---
You can use the `DragDrop` component to move items in or between lists. The `DragDrop` component should contain `Droppable` components which contain `Draggable` components.
diff --git a/packages/react-core/src/components/DragDrop/examples/DragDropBasic.tsx b/packages/react-core/src/components/DragDrop/examples/DragDropBasic.tsx
index 684c315bcf5..d80a6a7f213 100644
--- a/packages/react-core/src/components/DragDrop/examples/DragDropBasic.tsx
+++ b/packages/react-core/src/components/DragDrop/examples/DragDropBasic.tsx
@@ -20,14 +20,14 @@ const getItems = (count: number) =>
}));
const reorder = (list: ItemType[], startIndex: number, endIndex: number) => {
- const result = list;
+ const result = [...list];
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
export const DragDropBasic: React.FunctionComponent = () => {
- const [items, setItems] = React.useState(getItems(10));
+ const [items, setItems] = React.useState(getItems(10));
function onDrop(source: SourceType, dest: DestinationType) {
if (dest) {
@@ -42,8 +42,8 @@ export const DragDropBasic: React.FunctionComponent = () => {
return (
- {items.map(({ content }, i) => (
-
+ {items.map(({ id, content }) => (
+
{content}
))}
diff --git a/packages/react-core/src/components/DragDrop/examples/DragDropMultipleLists.tsx b/packages/react-core/src/components/DragDrop/examples/DragDropMultipleLists.tsx
index 50cc54658c8..0400633987f 100644
--- a/packages/react-core/src/components/DragDrop/examples/DragDropMultipleLists.tsx
+++ b/packages/react-core/src/components/DragDrop/examples/DragDropMultipleLists.tsx
@@ -11,6 +11,11 @@ interface SourceType {
index: number;
}
+interface MultipleListState {
+ items1: ItemType[];
+ items2: ItemType[];
+}
+
interface DestinationType extends SourceType {}
const getItems = (count: number, startIndex: number) =>
@@ -35,7 +40,7 @@ const move = (source: ItemType[], destination: ItemType[], sourceIndex: number,
};
export const DragDropMultipleLists: React.FunctionComponent = () => {
- const [items, setItems] = React.useState({
+ const [items, setItems] = React.useState({
items1: getItems(10, 0),
items2: getItems(5, 10)
});
@@ -84,7 +89,7 @@ export const DragDropMultipleLists: React.FunctionComponent = () => {
{Object.entries(items).map(([key, subitems]) => (
- {subitems.map(({ id, content }) => (
+ {(subitems as ItemType[]).map(({ id, content }) => (
{content}
diff --git a/packages/react-core/src/components/DragDrop/index.ts b/packages/react-core/src/components/DragDrop/index.ts
index 032ae9dfe38..7a4a6bd342e 100644
--- a/packages/react-core/src/components/DragDrop/index.ts
+++ b/packages/react-core/src/components/DragDrop/index.ts
@@ -1,3 +1,4 @@
export * from './DragDrop';
export * from './Draggable';
export * from './Droppable';
+export * from './DroppableContext';
diff --git a/packages/react-core/src/components/DualListSelector/examples/DualListSelector.md b/packages/react-core/src/components/DualListSelector/examples/DualListSelector.md
index 60288c158ff..0eee867b386 100644
--- a/packages/react-core/src/components/DualListSelector/examples/DualListSelector.md
+++ b/packages/react-core/src/components/DualListSelector/examples/DualListSelector.md
@@ -85,26 +85,6 @@ The dual list selector can also be built in a composable manner to make customiz
```
-### Composable with drag and drop
-
-This example only allows reordering the contents of the "chosen" pane with drag and drop. To make a pane able to be reordered:
-
-- wrap the `DualListSelectorPane` in a `DragDrop` component
-- wrap the `DualListSelectorList` in a `Droppable` component
-- wrap the `DualListSelectorListItem` components in a `Draggable` component
-- define an `onDrop` callback which reorders the sortable options.
- - The `onDrop` function provides the starting location and destination location for a dragged item. It should return
- true to enable the 'drop' animation in the new location and false to enable the 'drop' animation back to the item's
- old position.
- - define an `onDrag` callback which ensures that the drag event will not cross hairs with the `onOptionSelect` click
- event set on the option. Note: the `ignoreNextOptionSelect` state value is used to prevent selection while dragging.
-
-Note: Keyboard accessibility and screen reader accessibility for the `DragDrop` component are still in development.
-
-```ts file="DualListSelectorComposableDragDrop.tsx"
-
-```
-
### Composable with tree
```ts file="DualListSelectorComposableTree.tsx"
diff --git a/packages/react-core/src/components/DualListSelector/index.ts b/packages/react-core/src/components/DualListSelector/index.ts
index d2b6deb96ff..7f66aa3a037 100644
--- a/packages/react-core/src/components/DualListSelector/index.ts
+++ b/packages/react-core/src/components/DualListSelector/index.ts
@@ -1,7 +1,9 @@
export * from './DualListSelector';
+export * from './DualListSelectorContext';
export * from './DualListSelectorControl';
export * from './DualListSelectorControlsWrapper';
export * from './DualListSelectorPane';
export * from './DualListSelectorList';
export * from './DualListSelectorListItem';
export * from './DualListSelectorTree';
+export * from './DualListSelectorContext';
diff --git a/packages/react-core/src/components/index.ts b/packages/react-core/src/components/index.ts
index a322279493d..a4338497ba5 100644
--- a/packages/react-core/src/components/index.ts
+++ b/packages/react-core/src/components/index.ts
@@ -22,6 +22,7 @@ export * from './DataList';
export * from './DatePicker';
export * from './DescriptionList';
export * from './Divider';
+export * from './DragDrop';
export * from './Drawer';
export * from './Dropdown';
export * from './DualListSelector';
@@ -76,7 +77,6 @@ export * from './Tooltip';
export * from './NumberInput';
export * from './TreeView';
export * from './Wizard';
-export * from './DragDrop';
export * from './TextInputGroup';
export * from './Panel';
export * from './Truncate';
diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json
index b1e5b008ed9..94b6691ee43 100644
--- a/packages/react-docs/package.json
+++ b/packages/react-docs/package.json
@@ -26,6 +26,7 @@
"@patternfly/react-charts": "^7.2.0-prerelease.4",
"@patternfly/react-code-editor": "^5.2.0-prerelease.10",
"@patternfly/react-core": "^5.2.0-prerelease.10",
+ "@patternfly/react-drag-drop": "^5.2.0-prelease.0",
"@patternfly/react-icons": "^5.2.0-prerelease.3",
"@patternfly/react-styles": "^5.2.0-prerelease.2",
"@patternfly/react-table": "^5.2.0-prerelease.10",
diff --git a/packages/react-docs/patternfly-docs/patternfly-docs.source.js b/packages/react-docs/patternfly-docs/patternfly-docs.source.js
index b90f892c8d3..0d6f20116d6 100644
--- a/packages/react-docs/patternfly-docs/patternfly-docs.source.js
+++ b/packages/react-docs/patternfly-docs/patternfly-docs.source.js
@@ -16,12 +16,14 @@ module.exports = (baseSourceMD, sourceProps) => {
const reactCodeEditorPath = require
.resolve('@patternfly/react-code-editor/package.json')
.replace('package.json', 'src');
+ const reactDragDropPath = require.resolve('@patternfly/react-drag-drop/package.json').replace('package.json', 'src');
const reactPropsIgnore = '**/*.test.tsx';
sourceProps(path.join(reactCorePath, '/**/*.tsx'), reactPropsIgnore);
sourceProps(path.join(reactTablePath, '/**/*.tsx'), reactPropsIgnore);
sourceProps(path.join(reactChartsPath, '/**/*.tsx'), reactPropsIgnore);
sourceProps(path.join(reactCodeEditorPath, '/**/*.tsx'), reactPropsIgnore);
+ sourceProps(path.join(reactDragDropPath, '/**/*.tsx'), reactPropsIgnore);
// React MD
sourceMD(path.join(reactCorePath, '/components/**/examples/*.md'), 'react');
@@ -41,6 +43,9 @@ module.exports = (baseSourceMD, sourceProps) => {
// Code Editor MD
sourceMD(path.join(reactCodeEditorPath, '/**/examples/*.md'), 'react');
+ // Drag drop MD
+ sourceMD(path.join(reactDragDropPath, '/**/examples/*.md'), 'react-next');
+
// OUIA MD
sourceMD(path.join(reactCorePath, 'helpers/OUIA/OUIA.md'), 'react');
};
diff --git a/packages/react-drag-drop/package.json b/packages/react-drag-drop/package.json
new file mode 100644
index 00000000000..168e722a531
--- /dev/null
+++ b/packages/react-drag-drop/package.json
@@ -0,0 +1,49 @@
+{
+ "name": "@patternfly/react-drag-drop",
+ "version": "5.2.0-prelease.0",
+ "description": "PatternFly drag and drop solution",
+ "main": "dist/js/index.js",
+ "module": "dist/esm/index.js",
+ "types": "dist/esm/index.d.ts",
+ "sideEffects": false,
+ "publishConfig": {
+ "access": "public"
+ },
+ "patternfly:src": "src/",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/patternfly/patternfly-react.git"
+ },
+ "keywords": [
+ "react",
+ "patternfly",
+ "drag-drop"
+ ],
+ "author": "Red Hat",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/patternfly/patternfly-react/issues"
+ },
+ "homepage": "https://github.com/patternfly/patternfly-react/tree/main/packages/react-drag-drop#readme",
+ "scripts": {
+ "clean": "rimraf dist"
+ },
+ "dependencies": {
+ "@dnd-kit/core": "^6.0.8",
+ "@dnd-kit/modifiers": "^6.0.1",
+ "@dnd-kit/sortable": "^7.0.2",
+ "@patternfly/react-core": "^5.2.0-prerelease.2",
+ "@patternfly/react-icons": "^5.2.0-prerelease.1",
+ "@patternfly/react-styles": "^5.2.0-prerelease.1",
+ "memoize-one": "^5.1.0",
+ "resize-observer-polyfill": "^1.5.1"
+ },
+ "peerDependencies": {
+ "react": "^17 || ^18",
+ "react-dom": "^17 || ^18"
+ },
+ "devDependencies": {
+ "rimraf": "^2.6.2",
+ "typescript": "^4.7.4"
+ }
+}
diff --git a/packages/react-drag-drop/src/components/DragDrop/DragButton.tsx b/packages/react-drag-drop/src/components/DragDrop/DragButton.tsx
new file mode 100644
index 00000000000..a559839b257
--- /dev/null
+++ b/packages/react-drag-drop/src/components/DragDrop/DragButton.tsx
@@ -0,0 +1,23 @@
+import * as React from 'react';
+import { css } from '@patternfly/react-styles';
+import dragButtonStyles from '@patternfly/react-styles/css/components/DataList/data-list';
+import buttonStyles from '@patternfly/react-styles/css/components/Button/button';
+import GripVerticalIcon from '@patternfly/react-icons/dist/esm/icons/grip-vertical-icon';
+
+export interface DragButtonProps extends React.HTMLProps {
+ /** Additional classes added to the drag button */
+ className?: string;
+ /** Sets button type */
+ type?: 'button' | 'submit' | 'reset';
+ /** Flag indicating if drag is disabled for the item */
+ isDisabled?: boolean;
+}
+
+export const DragButton: React.FunctionComponent = ({ className, ...props }: DragButtonProps) => (
+
+);
+DragButton.displayName = 'DragButton';
diff --git a/packages/react-drag-drop/src/components/DragDrop/DragDropSort.tsx b/packages/react-drag-drop/src/components/DragDrop/DragDropSort.tsx
new file mode 100644
index 00000000000..6ba0773cd46
--- /dev/null
+++ b/packages/react-drag-drop/src/components/DragDrop/DragDropSort.tsx
@@ -0,0 +1,150 @@
+import * as React from 'react';
+import { css } from '@patternfly/react-styles';
+import {
+ DndContext,
+ closestCenter,
+ DragOverlay,
+ DndContextProps,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors,
+ DragEndEvent,
+ DragStartEvent
+} from '@dnd-kit/core';
+import {
+ arrayMove,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ verticalListSortingStrategy
+} from '@dnd-kit/sortable';
+import { Draggable } from './Draggable';
+import { DragButton } from './DragButton';
+import { DraggableDataListItem } from './DraggableDataListItem';
+import { DraggableDualListSelectorListItem } from './DraggableDualListSelectorListItem';
+import styles from '@patternfly/react-styles/css/components/DragDrop/drag-drop';
+import flexStyles from '@patternfly/react-styles/css/layouts/Flex/flex';
+
+export type DragDropSortDragEndEvent = DragEndEvent;
+export type DragDropSortDragStartEvent = DragStartEvent;
+
+export interface DraggableObject {
+ /** Unique id of the draggable object */
+ id: string;
+ /** Content rendered in the draggable object */
+ content: React.ReactNode;
+ /** Props spread to the rendered wrapper of the draggable object */
+ props?: any;
+}
+
+export interface DragDropSortProps extends DndContextProps {
+ /** Custom defined content wrapper for draggable items. By default, draggable items are wrapped in a styled div.
+ * Intended to be a 'DataList' or 'DualListSelectorList' without children. */
+ children?: React.ReactElement;
+ /** Sorted array of draggable objects */
+ items: DraggableObject[];
+ /** Callback when user drops a draggable object */
+ onDrop: (event: DragDropSortDragEndEvent, items: DraggableObject[], oldIndex?: number, newIndex?: number) => void;
+ /** Callback when use begins dragging a draggable object */
+ onDrag?: (event: DragDropSortDragStartEvent, oldIndex: number) => void;
+ /** The variant determines which component wraps the draggable object.
+ * Default and defaultWithHandle varaints wrap the draggable object in a div.
+ * DataList vairant wraps the draggable object in a DataListItem
+ * 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' | 'defaultWithHandle' | 'DataList' | 'DualListSelectorList' | 'TableComposable';
+}
+
+export const DragDropSort: React.FunctionComponent = ({
+ items,
+ onDrop = () => {},
+ onDrag = () => {},
+ variant = 'default',
+ children,
+ ...props
+}: DragDropSortProps) => {
+ const [activeId, setActiveId] = React.useState(null);
+ const [dragging, setDragging] = React.useState(false);
+
+ 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);
+ setDragging(false);
+ onDrop(event, newItems, oldIndex, newIndex);
+ return newItems;
+ };
+
+ const handleDragStart = (event: DragStartEvent) => {
+ setActiveId(event.active.id as string);
+ setDragging(true);
+ onDrag(event, itemIds.indexOf(event.active.id as string));
+ };
+
+ const renderedChildren = (
+
+ {items.map((item: DraggableObject) => {
+ switch (variant) {
+ case 'DualListSelectorList':
+ return (
+
+ {item.content}
+
+ );
+ case 'DataList':
+ return (
+
+ {item.content}
+
+ );
+ default:
+ return (
+
+ {item.content}
+
+ );
+ }
+ })}
+
+