DragAndDrop
<DragAndDroptype="list"items={{'my-container': [{id: '1',props: {children: 'Item 1',},},{id: '2',props: {children: (<div><div>Item 2</div><div>With Other Content</div></div>),},},{id: '3',props: {children: (<div><div>Item 3</div><div>With Other Content</div><div>With Other Content</div></div>),},},{id: '4',props: {children: (<div><div>Item 4</div><div>With Other Content</div><div>With Other Content</div><div>With Other Content</div></div>),},},],}}>{(items) => {return (<DragAndDropContainer id="my-container">{items['my-container'].map(({ id, props: { children, ...restProps } }) => (<DragAndDropItem key={id} id={id} {...restProps}>{children}</DragAndDropItem>))}</DragAndDropContainer>);}}</DragAndDrop>
Horizontal dragging
The DragAndDrop component supports setting the type prop to column
which will lock the drag behavior to the horizontal axis and change the drag icon. The DragAndDropContainer styles will need to be updated to incorporate a preferred column style.
<DragAndDroptype="column"items={{'horizontal-container': [{id: '1',props: {children: 'Item 1',},},{id: '2',props: {children: `Item 2`,},},{id: '3',props: {children: 'Item 3',},},{id: '4',props: {children: 'Item 4',},},],}}>{(items) => {return (<DragAndDropContainerid="horizontal-container"style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gridGap: '1rem' }}>{items['horizontal-container'].map(({ id, props }) => (<DragAndDropItem key={id} id={id}>{props.children}</DragAndDropItem>))}</DragAndDropContainer>);}}</DragAndDrop>
Theme
Setting the theme
prop to border
displays a border around each DragAndDropItem
.
<DragAndDroptype="list"theme="border"items={{'my-container-theme': [{id: '1-theme',props: {children: 'Item 1',},},{id: '2-theme',props: {children: `Item 2`,},},{id: '3-theme',props: {children: 'Item 3',},},{id: '4-theme',props: {children: 'Item 4',},},],}}>{(items) => {return (<DragAndDropContainer id="my-container-theme">{items['my-container-theme'].map(({ id, props }) => (<DragAndDropItem key={id} id={id}>{props.children}</DragAndDropItem>))}</DragAndDropContainer>);}}</DragAndDrop>
Multiple DragAndDropContainers
To allow items to move between multiple DragAndDropContainer
s you will need to:
- Set the
DragAndDrop
component'stype
prop to becanvas
. - Add two or more
DragAndDropContainer
components inside a singleDragAndDrop
component - Give each
DragAndDropContainer
a unique ID that matches the container key used in the data passed to theDragAndDrop
component'sitems
prop. - The
DragAndDrop
component's child works as a function that will provide the sorted items data. - You can use the unique container id to render the
DragAndDropItem
s into eachDragAndDropContainer
.
<DragAndDroptype="canvas"items={{'container-a': [{id: '1-a',props: {children: 'Item 1 a',},},{id: '2-a',props: {children: 'Item 2 a',},},{id: '3-a',props: {children: 'Item 3 a',},},],'container-b': [],}}>{(items) => {return (<div className="row"><DragAndDropContainer id="container-a" className="col-6"><section><h1>Container A</h1>{items['container-a'].length ? (items['container-a'].map(({ id, props }) => (<DragAndDropItem key={id} id={id}>{props.children}</DragAndDropItem>))) : (<p className="text-center p-3" style={{ border: '1px dashed var(--bs-blue-300)' }}>Place an item here</p>)}</section></DragAndDropContainer><DragAndDropContainer id="container-b" className="col-6"><section><h1>Container B</h1>{items['container-b'].length ? (items['container-b'].map(({ id, props }) => (<DragAndDropItem key={id} id={id}>{props.children}</DragAndDropItem>))) : (<p className="text-center p-3" style={{ border: '1px dashed var(--bs-blue-300)' }}>Place an item here</p>)}</section></DragAndDropContainer></div>);}}</DragAndDrop>
Controlled state for Inputs inside DragAndDropItems
To use form inputs inside the DragAndDropItems
you will need to:
- Add your items data into state.
- Pass the items state down to the
DragAndDrop
component'sitems
prop. - Use the
DragAndDrop
component'sonDragEnd
prop to set the items back into state when items are sorted. - Update the item state using the input's
onChange
event which will cause a re-render with the new data.
function Example() {const [items, setItems] = useState({'input-container': [{id: 'input-1',props: {value: 'Item 1',},},{id: 'input-2',props: {value: 'Item 2',},},{id: 'input-3',props: {value: 'Item 3',},},{id: 'input-4',props: {value: 'Item 4',},},],});// update the items in state when an input is changedconst handleChange = (containerId, itemIndex, value) => {setItems((previousItems) => {// create new objects/arrays so we don't mutate the originalconst newItems = {...previousItems,[containerId]: [...previousItems[containerId]],};// update the props for the changed itemnewItems[containerId][itemIndex].props.value = value;return newItems;});};// update the items in state whenever they are sortedconst handleDragEnd = (event, nextItems) => {setItems(nextItems);};return (<DragAndDrop onDragEnd={handleDragEnd} type="list" items={items}>{(items) => {return (<DragAndDropContainer id="input-container">{items['input-container'].map(({ id, props }, itemIndex) => (<DragAndDropItem key={id} id={id}><TextFieldid={`${id}-field`}aria-label={`text field ${id}`}{...props}// pass the container id, item index, and value to the change handleronChange={(value) => handleChange('input-container', itemIndex, value)}/></DragAndDropItem>))}</DragAndDropContainer>);}}</DragAndDrop>);}
Removable items
To be able to delete DragAndDropItems
you will need to:
- Add your items data into state.
- Pass the items state down to the
DragAndDrop
component'sitems
prop. - Use the
DragAndDrop
component'sonDragEnd
prop to set the items back into state when items are sorted. - When you custom delete button is clicked remove the item from state.
function Example() {const [items, setItems] = useState({'removable-item-container': [{id: 'removable-1',props: {children: 'Item 1',},},{id: 'removable-2',props: {children: 'Item 2',},},{id: 'removable-3',props: {children: 'Item 3',},},{id: 'removable-4',props: {children: 'Item 4',},},],});const handleClick = (containerId, itemIndex) => {setItems((previousItems) => {// create new object so we don't mutate the originalconst newItems = {...previousItems,};newItems[containerId].splice(itemIndex, 1);return newItems;});};const handleDragEnd = (event, nextItems) => {setItems(nextItems);};return (<div><style>{`.item-content {display: flex;justify-content: space-between;flex: 1;align-items: center;}.delete-button {font-size: 20px;color: #cccccc !important;}.delete-button:hover {color: #444444 !important;}.delete-button:active {color: #0d6efd !important;}`}</style><DragAndDrop onDragEnd={handleDragEnd} type="list" items={items}>{(items) => {return (<DragAndDropContainer id="removable-item-container">{items['removable-item-container'].map(({ id, props }, itemIndex) => (<DragAndDropItem key={id} id={id}><div className="item-content"><span>{props.children}</span><Buttonid={`${id}-delete-button`}variant="link"onClick={() => handleClick('removable-item-container', itemIndex)}className="delete-button"aria-label="delete item"><span className="icon-x-circle" /></Button></div></DragAndDropItem>))}</DragAndDropContainer>);}}</DragAndDrop></div>);}
Using ListGroup
It's possible to create a ListGroup styled DragAndDrop using the as
props of DragAndDropContainer and DragAndDropItem. Using the ListGroup.Item.Header
and ListGroup.Item.Body
components to match the visual style of the ListGroup component.
function Example() {return (<DragAndDroptype="list"items={{'my-container': [{id: '1',props: {title: 'Item 1',icon: <span className="icon-chevron-right" />,prefix: <Switch aria-label="switch in item" />,},},{id: '2',props: {title: 'Item 2',icon: <span className="icon-chevron-right" />,prefix: <Switch aria-label="switch in item" />,description: 'With Other Content',},},{id: '3',props: {title: 'Item 3',icon: <span className="icon-chevron-right" />,prefix: <Switch aria-label="switch in item" />,description: 'With Other Content',footer: 'With Footer Content',},},{id: '4',props: {title: 'Item 4',icon: <span className="icon-chevron-right" />,prefix: <Switch aria-label="switch in item" />,description: 'With Other Content',footer: 'With Footer Content',alert: (<Alert variant="info" size="sm">Alert</Alert>),},},{id: '5',props: {title: 'Item 5',icon: <span className="icon-chevron-right" />,prefix: <Switch aria-label="switch in item" />,description: 'With Other Content',footer: 'With Footer Content',alert: (<Alert variant="info" size="sm">Alert</Alert>),actions: (<div className="d-flex gap-3"><Button size="sm" variant="outline-primary">Edit</Button><Button size="sm" variant="outline-danger">Delete</Button></div>),},},],}}>{(items) => {return (<DragAndDropContainer id="my-container" as={ListGroup} collapse={false}>{items['my-container'].map(({id,props: { title, icon, description, footer, actions, alert, prefix, ...restProps },}) => (<DragAndDropItem as={ListGroup.Item} key={id} id={id} {...restProps}><ListGroupItemTemplateprefix={prefix}header={<ListGroup.Item.Header title={title} icon={icon} />}body={(description || alert || actions || footer) && (<ListGroup.Item.Bodydescription={description}alert={alert}actions={actions}footer={footer}/>)}/></DragAndDropItem>),)}</DragAndDropContainer>);}}</DragAndDrop>);}render(<Example />);