diff --git a/packages/react-core/src/components/DataList/DataList.tsx b/packages/react-core/src/components/DataList/DataList.tsx index 181c987c32f..fdc1b2dbeb1 100644 --- a/packages/react-core/src/components/DataList/DataList.tsx +++ b/packages/react-core/src/components/DataList/DataList.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; - import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/DataList/data-list'; +import { PickOptional } from '../../helpers/typeUtils'; export enum DataListWrapModifier { nowrap = 'nowrap', @@ -9,7 +9,7 @@ export enum DataListWrapModifier { breakWord = 'breakWord' } -export interface DataListProps extends React.HTMLProps { +export interface DataListProps extends Omit, 'onDragStart'> { /** Content rendered inside the DataList list */ children?: React.ReactNode; /** Additional classes added to the DataList list */ @@ -18,61 +18,265 @@ export interface DataListProps extends React.HTMLProps { 'aria-label': string; /** Optional callback to make DataList selectable, fired when DataListItem selected */ onSelectDataListItem?: (id: string) => void; + /** Optional callback to make DataList draggable, fired when dragging ends */ + onDragFinish?: (newItemOrder: string[]) => void; + /** Optional informational callback for dragging, fired when dragging starts */ + onDragStart?: (id: string) => void; + /** Optional informational callback for dragging, fired when an item moves */ + onDragMove?: (oldIndex: number, newIndex: number) => void; + /** Optional informational callback for dragging, fired when dragging is cancelled */ + onDragCancel?: () => void; /** Id of DataList item currently selected */ selectedDataListItemId?: string; /** Flag indicating if DataList should have compact styling */ isCompact?: boolean; /** Determines which wrapping modifier to apply to the DataList */ wrapModifier?: DataListWrapModifier | 'nowrap' | 'truncate' | 'breakWord'; + /** Order of items in a draggable DataList */ + itemOrder?: string[]; +} + +interface DataListState { + draggedItemId: string; + draggingToItemIndex: number; + dragging: boolean; + tempItemOrder: string[]; } interface DataListContextProps { isSelectable: boolean; selectedDataListItemId: string; updateSelectedDataListItem: (id: string) => void; + isDraggable: boolean; + dragStart: (e: React.DragEvent) => void; + dragEnd: (e: React.DragEvent) => void; + dragKeyHandler: (e: React.KeyboardEvent) => void; } export const DataListContext = React.createContext>({ isSelectable: false }); -export const DataList: React.FunctionComponent = ({ - children = null, - className = '', - 'aria-label': ariaLabel, - selectedDataListItemId = '', - onSelectDataListItem, - isCompact = false, - wrapModifier = null, - ...props -}: DataListProps) => { - const isSelectable = onSelectDataListItem !== undefined; - - const updateSelectedDataListItem = (id: string) => { - onSelectDataListItem(id); +const moveItem = (arr: string[], i1: string, toIndex: number) => { + const fromIndex = arr.indexOf(i1); + if (fromIndex === toIndex) { + return arr; + } + const temp = arr.splice(fromIndex, 1); + arr.splice(toIndex, 0, temp[0]); + + return arr; +}; + +export class DataList extends React.Component { + static displayName = 'DataList'; + static defaultProps: PickOptional = { + children: null, + className: '', + selectedDataListItemId: '', + isCompact: false, + wrapModifier: null + }; + dragFinished: boolean = false; + arrayCopy: React.ReactElement[] = React.Children.toArray(this.props.children) as React.ReactElement[]; + ref = React.createRef(); + + state: DataListState = { + tempItemOrder: [], + draggedItemId: null, + draggingToItemIndex: null, + dragging: false + }; + + componentDidUpdate(oldProps: DataListProps) { + if (this.dragFinished) { + this.dragFinished = false; + + this.setState({ + tempItemOrder: [...this.props.itemOrder], + draggedItemId: null, + dragging: false + }); + } + if (oldProps.itemOrder !== this.props.itemOrder) { + this.move(this.props.itemOrder); + } + } + + getIndex = (id: string) => Array.from(this.ref.current.children).findIndex(item => item.id === id); + + move = (itemOrder: string[]) => { + const ulNode = this.ref.current; + const nodes = Array.from(ulNode.children); + if (nodes.map(node => node.id).every((id, i) => id === itemOrder[i])) { + return; + } + while (ulNode.firstChild) { + ulNode.removeChild(ulNode.lastChild); + } + + itemOrder.forEach(id => { + ulNode.appendChild(nodes.find(n => n.id === id)); + }); + }; + + dragStart0 = (el: HTMLElement) => { + const { onDragStart } = this.props; + const draggedItemId = el.id; + + el.classList.add(styles.modifiers.ghostRow); + el.setAttribute('aria-pressed', 'true'); + this.setState({ + draggedItemId, + dragging: true + }); + onDragStart && onDragStart(draggedItemId); + }; + + dragStart = (evt: React.DragEvent) => { + evt.dataTransfer.effectAllowed = 'move'; + evt.dataTransfer.setData('text/plain', evt.currentTarget.id); + this.dragStart0(evt.currentTarget as HTMLElement); }; - return ( - -
    { + el.classList.remove(styles.modifiers.ghostRow); + el.setAttribute('aria-pressed', 'false'); + this.props.onDragFinish(this.state.tempItemOrder); + }; + + dragEnd = (evt: React.DragEvent) => { + this.dragEnd0(evt.currentTarget as HTMLElement); + }; + + dragOver0 = (id: string) => { + const draggingToItemIndex = Array.from(this.ref.current.children).findIndex(item => item.id === id); + if (draggingToItemIndex !== this.state.draggingToItemIndex) { + const tempItemOrder = moveItem([...this.props.itemOrder], this.state.draggedItemId, draggingToItemIndex); + this.move(tempItemOrder); + + this.setState({ + draggingToItemIndex, + tempItemOrder + }); + } + }; + + dragOver = (evt: React.DragEvent) => { + evt.preventDefault(); + const currListItem = (evt.target as Element).closest('li'); + if (currListItem && currListItem.classList.contains(css(styles.modifiers.ghostRow))) { + return; + } + this.dragOver0(currListItem.id); + }; + + handleDragButtonKeys = (evt: React.KeyboardEvent) => { + const { dragging } = this.state; + const { onDragCancel } = this.props; + if ( + evt.key !== ' ' && + evt.key !== 'Escape' && + evt.key !== 'Enter' && + evt.key !== 'ArrowUp' && + evt.key !== 'ArrowDown' + ) { + if (dragging) { + evt.preventDefault(); + } + return; + } + evt.preventDefault(); + + const dragItem = (evt.target as Element).closest('li'); + + if (evt.key === ' ' || (evt.key === 'Enter' && !dragging)) { + this.dragStart0(dragItem); + } else if (dragging) { + if (evt.key === 'Escape' || evt.key === 'Enter') { + this.setState({ + dragging: false + }); + this.dragFinished = true; + if (evt.key === 'Enter') { + this.dragEnd0(dragItem); + } else { + onDragCancel && onDragCancel(); + } + } else if (evt.key === 'ArrowUp') { + const nextSelection = dragItem.previousSibling as HTMLElement; + if (nextSelection) { + this.dragOver0(nextSelection.id); + (dragItem.querySelector(`.${styles.dataListItemDraggableButton}`) as HTMLElement).focus(); + } + } else if (evt.key === 'ArrowDown') { + const nextSelection = dragItem.nextSibling as HTMLElement; + if (nextSelection) { + this.dragOver0(nextSelection.id); + (dragItem.querySelector(`.${styles.dataListItemDraggableButton}`) as HTMLElement).focus(); + } + } + } + }; + + render() { + const { + className, + children, + onSelectDataListItem, + selectedDataListItemId, + isCompact, + onDragStart, + onDragMove, + onDragCancel, + onDragFinish, + wrapModifier, + itemOrder, + ...props + } = this.props; + const { dragging } = this.state; + const isSelectable = onSelectDataListItem !== undefined; + const isDraggable = onDragFinish !== undefined; + + const updateSelectedDataListItem = (id: string) => { + onSelectDataListItem(id); + }; + + const dragProps = isDraggable && { + onDragOver: this.dragOver, + onDrop: this.dragOver + }; + + return ( + - {children} -
-
- ); -}; -DataList.displayName = 'DataList'; +
    + {children} +
+ + ); + } +} diff --git a/packages/react-core/src/components/DataList/DataListCheck.tsx b/packages/react-core/src/components/DataList/DataListCheck.tsx index 2e13b382f48..07622517090 100644 --- a/packages/react-core/src/components/DataList/DataListCheck.tsx +++ b/packages/react-core/src/components/DataList/DataListCheck.tsx @@ -17,6 +17,8 @@ export interface DataListCheckProps extends Omit) => void; /** Aria-labelledby of the DataList checkbox */ 'aria-labelledby': string; + /** Flag to indicate if other controls are used in the DataListItem */ + otherControls?: boolean; } export const DataListCheck: React.FunctionComponent = ({ @@ -27,10 +29,11 @@ export const DataListCheck: React.FunctionComponent = ({ isDisabled = false, isChecked = null, checked = null, + otherControls = false, ...props -}: DataListCheckProps) => ( -
-
+}: DataListCheckProps) => { + const check = ( +
= ({ checked={isChecked || checked} />
-
-); + ); + return ( + + {!otherControls &&
{check}
} + {otherControls && check} +
+ ); +}; DataListCheck.displayName = 'DataListCheck'; diff --git a/packages/react-core/src/components/DataList/DataListControl.tsx b/packages/react-core/src/components/DataList/DataListControl.tsx new file mode 100644 index 00000000000..1a1fa9990fb --- /dev/null +++ b/packages/react-core/src/components/DataList/DataListControl.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/DataList/data-list'; + +export interface DataListControlProps extends React.HTMLProps { + /** Children of the data list control */ + children?: React.ReactNode; + /** Additional classes added to the DataList item control */ + className?: string; +} + +export const DataListControl: React.FunctionComponent = ({ + children, + className = '', + ...props +}: DataListControlProps) => ( +
+ {children} +
+); +DataListControl.displayName = 'DataListControl'; diff --git a/packages/react-core/src/components/DataList/DataListDragButton.tsx b/packages/react-core/src/components/DataList/DataListDragButton.tsx new file mode 100644 index 00000000000..ecc416fd188 --- /dev/null +++ b/packages/react-core/src/components/DataList/DataListDragButton.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/DataList/data-list'; +import GripVerticalIcon from '@patternfly/react-icons/dist/js/icons/grip-vertical-icon'; +import { DataListContext } from './DataList'; + +export interface DataListDragButtonProps 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 DataListDragButton: React.FunctionComponent = ({ + className = '', + isDisabled = false, + ...props +}: DataListDragButtonProps) => ( + + {({ dragKeyHandler }) => ( + + )} + +); +DataListDragButton.displayName = 'DataListDragButton'; diff --git a/packages/react-core/src/components/DataList/DataListItem.tsx b/packages/react-core/src/components/DataList/DataListItem.tsx index 9c407a668cf..d49534e08a0 100644 --- a/packages/react-core/src/components/DataList/DataListItem.tsx +++ b/packages/react-core/src/components/DataList/DataListItem.tsx @@ -22,65 +22,77 @@ export interface DataListItemChildProps { rowid: string; } -export const DataListItem: React.FunctionComponent = ({ - isExpanded = false, - className = '', - id = '', - 'aria-labelledby': ariaLabelledBy, - children, - ...props -}: DataListItemProps) => ( - - {({ isSelectable, selectedDataListItemId, updateSelectedDataListItem }) => { - const selectDataListItem = (event: React.MouseEvent) => { - let target: any = event.target; - while (event.currentTarget !== target) { - if ( - ('onclick' in target && target.onclick) || - target.parentNode.classList.contains(styles.dataListItemAction) || - target.parentNode.classList.contains(styles.dataListItemControl) - ) { - // check other event handlers are not present. - return; - } else { - target = target.parentNode; - } - } - updateSelectedDataListItem(id); - }; +export class DataListItem extends React.Component { + static displayName = 'DataListItem'; + static defaultProps: DataListItemProps = { + isExpanded: false, + className: '', + id: '', + children: null, + 'aria-labelledby': '' + }; + render() { + const { children, isExpanded, className, id, 'aria-labelledby': ariaLabelledBy, ...props } = this.props; + return ( + + {({ isSelectable, selectedDataListItemId, updateSelectedDataListItem, isDraggable, dragStart, dragEnd }) => { + const selectDataListItem = (event: React.MouseEvent) => { + let target: any = event.target; + while (event.currentTarget !== target) { + if ( + ('onclick' in target && target.onclick) || + target.parentNode.classList.contains(styles.dataListItemAction) || + target.parentNode.classList.contains(styles.dataListItemControl) + ) { + // check other event handlers are not present. + return; + } else { + target = target.parentNode; + } + } + updateSelectedDataListItem(id); + }; - const onKeyDown = (event: React.KeyboardEvent) => { - if (event.key === KeyTypes.Enter) { - updateSelectedDataListItem(id); - } - }; + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === KeyTypes.Enter) { + updateSelectedDataListItem(id); + } + }; - return ( -
  • - {React.Children.map( - children, - child => - React.isValidElement(child) && - React.cloneElement(child as React.ReactElement, { - rowid: ariaLabelledBy - }) - )} -
  • - ); - }} -
    -); -DataListItem.displayName = 'DataListItem'; + const dragProps = isDraggable && { + draggable: true, + onDragEnd: dragEnd, + onDragStart: dragStart + }; + + return ( +
  • + {React.Children.map( + children, + child => + React.isValidElement(child) && + React.cloneElement(child as React.ReactElement, { + rowid: ariaLabelledBy + }) + )} +
  • + ); + }} +
    + ); + } +} diff --git a/packages/react-core/src/components/DataList/__tests__/DataList.test.tsx b/packages/react-core/src/components/DataList/__tests__/DataList.test.tsx index 262b321ed9e..9ec2c2256dd 100644 --- a/packages/react-core/src/components/DataList/__tests__/DataList.test.tsx +++ b/packages/react-core/src/components/DataList/__tests__/DataList.test.tsx @@ -22,6 +22,11 @@ describe('DataList', () => { expect(view).toMatchSnapshot(); }); + test('List draggable', () => { + const view = shallow(); + expect(view).toMatchSnapshot(); + }); + test('List', () => { const view = shallow(); expect(view).toMatchSnapshot(); diff --git a/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataList.test.tsx.snap b/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataList.test.tsx.snap index 3e53564e788..347768c139c 100644 --- a/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataList.test.tsx.snap +++ b/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataList.test.tsx.snap @@ -4,6 +4,10 @@ exports[`DataList should match snapshot (auto-generated) 1`] = ` ReactNode diff --git a/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataListCheck.test.tsx.snap b/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataListCheck.test.tsx.snap index aa7da87d35e..5d5db286c1f 100644 --- a/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataListCheck.test.tsx.snap +++ b/packages/react-core/src/components/DataList/__tests__/Generated/__snapshots__/DataListCheck.test.tsx.snap @@ -1,20 +1,22 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DataListCheck should match snapshot (auto-generated) 1`] = ` -
    +
    - +
    + +
    -
    + `; diff --git a/packages/react-core/src/components/DataList/__tests__/__snapshots__/DataList.test.tsx.snap b/packages/react-core/src/components/DataList/__tests__/__snapshots__/DataList.test.tsx.snap index 9c857ea8639..fd28d2ff581 100644 --- a/packages/react-core/src/components/DataList/__tests__/__snapshots__/DataList.test.tsx.snap +++ b/packages/react-core/src/components/DataList/__tests__/__snapshots__/DataList.test.tsx.snap @@ -119,6 +119,10 @@ exports[`DataList List 1`] = ` `; @@ -136,6 +141,10 @@ exports[`DataList List compact 1`] = ` `; @@ -153,6 +163,10 @@ exports[`DataList List default 1`] = ` + +`; + +exports[`DataList List draggable 1`] = ` + +
      `; diff --git a/packages/react-core/src/components/DataList/examples/DataList.md b/packages/react-core/src/components/DataList/examples/DataList.md index e130dd53a5a..609904c9043 100644 --- a/packages/react-core/src/components/DataList/examples/DataList.md +++ b/packages/react-core/src/components/DataList/examples/DataList.md @@ -38,6 +38,7 @@ import { css } from '@patternfly/react-styles'; ## Examples ### Basic + ```js import React from 'react'; import { @@ -89,6 +90,7 @@ SimpleDataList = () => ( ``` ### Compact + ```js import React from 'react'; import { @@ -140,6 +142,7 @@ SimpleDataList = () => ( ``` ### Checkboxes, actions and additional cells + ```js import React from 'react'; import { @@ -356,6 +359,7 @@ class CheckboxActionDataList extends React.Component { ``` ### Actions: single and multiple + ```js import React from 'react'; import { @@ -467,6 +471,7 @@ class ActionsDataList extends React.Component { ``` ### Expandable + ```js import React from 'react'; import { @@ -564,7 +569,12 @@ class ExpandableDataList extends React.Component { ]} /> - + ]} /> - + ]} /> - + ( @@ -1075,7 +1098,10 @@ SimpleDataList = () => ( Primary content , - Really really really really really really really really really really really really really really long description that should be truncated before it ends + + Really really really really really really really really really really really really really really long + description that should be truncated before it ends + ]} /> @@ -1083,3 +1109,166 @@ SimpleDataList = () => ( ); ``` + +### Draggable + +```js +import React from 'react'; +import { + Button, + Dropdown, + DropdownItem, + DropdownPosition, + KebabToggle, + DataList, + DataListItem, + DataListCell, + DataListItemRow, + DataListCheck, + DataListControl, + DataListDragButton, + DataListItemCells, + DataListAction +} from '@patternfly/react-core'; + +class DraggableDataList extends React.Component { + constructor(props) { + super(props); + + this.state = { + liveText: '', + id: '', + itemOrder: ['data1', 'data2', 'data3', 'data4'] + }; + + this.onDragStart = id => { + this.setState({ + id: id, + liveText: `Dragging started for item id: ${id}.` + }); + }; + + this.onDragMove = (oldIndex, newIndex) => { + const { id } = this.state; + this.setState({ + liveText: `Dragging item ${id}.` + }); + }; + + this.onDragCancel = () => { + this.setState({ + liveText: `Dragging cancelled. List is unchanged.` + }); + }; + + this.onDragFinish = itemOrder => { + console.log('need to save new list', itemOrder); + this.setState({ + liveText: `Dragging finished`, + itemOrder + }); + }; + } + + render() { + const { list, liveText } = this.state; + return ( + + + + + + + + + + Item 1 + + ]} + /> + + + + + + + + + + Item 2 + + ]} + /> + + + + + + + + + + Item 3 + + ]} + /> + + + + + + + + + + Item 4 + + ]} + /> + + + +
      + {liveText} +
      +
      + ); + } +} +``` diff --git a/packages/react-core/src/components/DataList/index.ts b/packages/react-core/src/components/DataList/index.ts index 7a10ec01e1e..a65647e0d1a 100644 --- a/packages/react-core/src/components/DataList/index.ts +++ b/packages/react-core/src/components/DataList/index.ts @@ -2,6 +2,8 @@ export * from './DataList'; export * from './DataListAction'; export * from './DataListCell'; export * from './DataListCheck'; +export * from './DataListControl'; +export * from './DataListDragButton'; export * from './DataListItem'; export * from './DataListItemCells'; export * from './DataListItemRow'; diff --git a/packages/react-integration/cypress/integration/datalist.spec.ts b/packages/react-integration/cypress/integration/datalist.spec.ts index 7c6f47907c2..7b7d49274c5 100644 --- a/packages/react-integration/cypress/integration/datalist.spec.ts +++ b/packages/react-integration/cypress/integration/datalist.spec.ts @@ -78,3 +78,21 @@ describe('Data List Compact Demo Test', () => { cy.get('#row2.pf-m-selected').should('be.visible'); }); }); + +describe('Data List Draggable Demo Test', () => { + it('Navigate to demo section', () => { + cy.visit('http://localhost:3000/'); + cy.get('#data-list-draggable-demo-nav-item-link').click(); + cy.url().should('eq', 'http://localhost:3000/data-list-draggable-demo-nav-link'); + }); + + it('Verify drag', () => { + cy.get('#data1').contains('Item 1'); + cy.get('#drag1').type(' '); + cy.get('#drag1').type('{downarrow}'); + cy.get('#data1').should('have.class', 'pf-m-ghost-row'); + cy.get('#drag1').type('{downarrow}'); + cy.get('#drag1').type('{enter}'); + cy.get('#data1').should('not.have.class', 'pf-m-ghost-row'); + }); +}); diff --git a/packages/react-integration/demo-app-ts/src/Demos.ts b/packages/react-integration/demo-app-ts/src/Demos.ts index bf44f697822..0993d7782b5 100644 --- a/packages/react-integration/demo-app-ts/src/Demos.ts +++ b/packages/react-integration/demo-app-ts/src/Demos.ts @@ -180,6 +180,11 @@ export const Demos: DemoInterface[] = [ name: 'Data List Demo', componentType: Examples.DataListDemo }, + { + id: 'data-list-draggable-demo', + name: 'Data List Draggable Demo', + componentType: Examples.DataListDraggableDemo + }, { id: 'data-list-compact-demo', name: 'Data List Compact Demo', diff --git a/packages/react-integration/demo-app-ts/src/components/demos/DataListDemo/DataListDraggableDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/DataListDemo/DataListDraggableDemo.tsx new file mode 100644 index 00000000000..ab99fb159a5 --- /dev/null +++ b/packages/react-integration/demo-app-ts/src/components/demos/DataListDemo/DataListDraggableDemo.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { + DataList, + DataListItem, + DataListCell, + DataListItemRow, + DataListCheck, + DataListControl, + DataListDragButton, + DataListItemCells +} from '@patternfly/react-core'; + +export class DataListDraggableDemo extends React.Component { + static displayName = 'DataListDraggableDemo'; + state = { + liveText: '', + id: '', + itemOrder: ['data1', 'data2', 'data3', 'data4'] + }; + + onDragStart = (id: string) => { + this.setState({ + id, + liveText: `Dragging started for item id: ${id}.` + }); + }; + + onDragMove = (oldIndex: number, newIndex: number) => { + const { id } = this.state; + this.setState({ + liveText: `Dragging item ${id}.` + }); + }; + + onDragCancel = () => { + this.setState({ + liveText: `Dragging cancelled. List is unchanged.` + }); + }; + + onDragFinish = (itemOrder: string[]) => { + this.setState({ + liveText: `Dragging finished`, + itemOrder + }); + }; + + render() { + const { liveText, itemOrder } = this.state; + return ( + + + + + + + + + + Item 1 + + ]} + /> + + + + + + + + + + Item 2 + + ]} + /> + + + + + + + + + + Item 3 + + ]} + /> + + + + + + + + + + Item 4 + + ]} + /> + + + +
      + {liveText} +
      +
      + ); + } +} diff --git a/packages/react-integration/demo-app-ts/src/components/demos/index.ts b/packages/react-integration/demo-app-ts/src/components/demos/index.ts index 3d43dc47dcc..10aa2065bcf 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/index.ts +++ b/packages/react-integration/demo-app-ts/src/components/demos/index.ts @@ -31,6 +31,7 @@ export * from './ContextSelectorDemo/ContextSelectorDemo'; export * from './ClipboardCopyDemo/ClipboardCopyDemo'; export * from './ClipboardCopyDemo/ClipboardCopyExpandedDemo'; export * from './DataListDemo/DataListDemo'; +export * from './DataListDemo/DataListDraggableDemo'; export * from './DataListDemo/DataListCompactDemo'; export * from './ToolbarDemo/ToolbarDemo'; export * from './DescriptionListDemo/DescriptionListDemo';