The Draggable kit gives you a full subcomponent structure that allows it to be used with almost any kits.
DraggableProvider
= This provider manages all settings that allows drag and drop to function and must be used as the outermost wrapper. It has 2 REQUIRED props: initialItems
(initial data) and onReorder
(function that returns mutated data as items are reordered via drag and drop). Devs must manage state as shown.
Draggable.Container
= This specifies the container within which items can be dropped.
Draggable.Item
= This specifies the items that can be dragged and dropped. dragId
is a REQUIRED prop for Draggable.Item.
import React, { useState } from "react"; import { Flex, Image, Draggable, DraggableProvider } from 'playbook-ui' // Initial items to be dragged const data = [ { id: "21", url: "https://unsplash.it/500/400/?image=633", }, { id: "22", url: "https://unsplash.it/500/400/?image=634", }, { id: "23", url: "https://unsplash.it/500/400/?image=637", }, ]; const DraggableDefault = (props) => { const [initialState, setInitialState] = useState(data); return ( <> <DraggableProvider initialItems={data} onReorder={(items) => setInitialState(items)} > <Draggable.Container> <Flex> {initialState.map(({ id, url }) => ( <Draggable.Item dragId={id} key={id} > <Image alt={id} margin="xs" size="md" url={url} /> </Draggable.Item> ))} </Flex> </Draggable.Container> </DraggableProvider> </> ); }; export default DraggableDefault;
For a simplified version of the Draggable API for the List kit, you can do the following:
Use DraggableProvider
and manage state as shown.
The List kit is optimized to work with the draggable kit. To enable drag, use the enableDrag
prop on List kit AND dragId
prop on ListItem. An additional optional boolean prop (set to true by default) of dragHandle
is also available on List kit to show the drag handle icon.
import React, { useState } from "react"; import { DraggableProvider, List, ListItem } from 'playbook-ui' // Initial items to be dragged const data = [ { id: "31", text: "Philadelphia", }, { id: "32", text: "New Jersey", }, { id: "33", text: "Maryland", }, { id: "34", text: "Connecticut", }, ]; const DraggableWithList = (props) => { const [initialState, setInitialState] = useState(data); return ( <> <DraggableProvider initialItems={data} onReorder={(items) => setInitialState(items)} > <List enableDrag > {initialState.map(({ id, text }) => ( <ListItem dragId={id} key={id} > {text} </ListItem> ))} </List> </DraggableProvider> </> ); }; export default DraggableWithList;
For a simplified version of the Draggable API for the SelectableList kit, you can do the following:
Use DraggableProvider
and manage state as shown.
The SelectableList kit is optimized to work with the draggable kit. To enable drag, use the enableDrag
prop on SelectableList kit AND dragId
prop on SelectableList.Item. An additional optional boolean prop (set to true by default) of dragHandle
is also available on SelectableList kit to show the drag handle icon.
import React, { useState } from "react"; import { SelectableList, DraggableProvider } from 'playbook-ui' // Initial items to be dragged const data = [ { id: "41", text: "Task 1", }, { id: "42", text: "Task 2", }, { id: "43", text: "Task 3", }, { id: "44", text: "Task 4", }, ]; const DraggableWithSelectableList = (props) => { const [initialState, setInitialState] = useState(data); return ( <> <DraggableProvider initialItems={data} onReorder={(items) => setInitialState(items)} > <SelectableList enableDrag variant="radio" > {initialState.map(({ id, text }) => ( <SelectableList.Item dragId={id} key={id} label={text} name="radio-test" value={id} /> ))} </SelectableList> </DraggableProvider> </> ); }; export default DraggableWithSelectableList
For a simplified version of the Draggable API for the Card kit, you can do the following:
Use DraggableProvider
and manage state as shown.
Draggable.Container
creates the container within which the cards can be dragged and dropped.
The Card kit is optimized to work with the draggable kit. To enable drag, use the draggableItem
and dragId
props on the Card kit as shown. An additional optional boolean prop (set to true by default) of dragHandle
is also available to show the drag handle icon.
import React, { useState } from "react"; import { Flex, Draggable, DraggableProvider, Badge, Title, Icon, Caption, Card } from 'playbook-ui' // Initial items to be dragged const data = [ { id: "21", text: "Joe Black", }, { id: "22", text: "Nancy White", }, { id: "23", text: "Bill Green", }, ]; const DraggableWithCards = (props) => { const [initialState, setInitialState] = useState(data); return ( <DraggableProvider initialItems={data} onReorder={(items) => setInitialState(items)} > <Draggable.Container > {initialState.map(({ id, text }) => ( <Card dragId={id} draggableItem highlight={{ color: "primary", position: "side" }} key={id} marginBottom="xs" padding="xs" > <Flex alignItems="stretch" flexDirection="column" > <Flex gap="xs"> <Title size={4} text={text} /> <Badge text="35-12345" variant="primary" /> </Flex> <Caption size="xs" text="8:00A • Township Name • 90210" /> <Flex gap="xxs" spacing="between" > <Flex gap="xxs"> <Caption color="error" size="xs" > <Icon icon="house-circle-exclamation" /> </Caption> <Caption color="success" size="xs"> <Icon icon="file-circle-check" /> </Caption> </Flex> <Flex> <Badge rounded text="Schedule QA" variant="warning" /> <Badge rounded text="Flex" variant="primary" /> <Badge rounded text="R99" variant="primary" /> </Flex> </Flex> </Flex> </Card> ))} </Draggable.Container> </DraggableProvider> ); }; export default DraggableWithCards;
The draggable kit can also be used in conjunction with the table kit to create draggable table rows. To do this:
DraggableProvider
and manage state as shown. draggableContainer
prop on the Table.Body to designate it as the Draggable ContainerdraggableItem
prop on the Table.Row to designate it as the Draggable Item. Make sure to also pass id to the dragId
prop here. We recommending using the default dropZone type
with the Table kit.
import React, { useState } from "react"; import { Flex, DraggableProvider, Avatar, Body, Table } from 'playbook-ui' // Initial items to be dragged const data = [ { id: "1", task: "Task 1", assignee_name: "Terry Miles", assignee_img: "https://randomuser.me/api/portraits/men/44.jpg", }, { id: "2", task: "Task 2", assignee_name: "Sophia Miles", assignee_img: "https://randomuser.me/api/portraits/women/8.jpg", }, { id: "3", task: "Task 3", assignee_name: "Alice Jones", assignee_img: "https://randomuser.me/api/portraits/women/10.jpg", }, { id: "4", task: "Task 4", assignee_name: "Mike James", assignee_img: "https://randomuser.me/api/portraits/men/8.jpg", }, { id: "5", task: "Task 5", assignee_name: "James Guy", assignee_img: "https://randomuser.me/api/portraits/men/18.jpg", } ]; const DraggableWithTableReact = (props) => { const [initialState, setInitialState] = useState(data); return ( <> <DraggableProvider initialItems={data} onReorder={(items) => setInitialState(items)} > <Table responsive="none" size="sm" > <Table.Head> <Table.Row> <Table.Header>{"id"}</Table.Header> <Table.Header>{"name"}</Table.Header> <Table.Header>{"task number"}</Table.Header> </Table.Row> </Table.Head> <Table.Body draggableContainer> {initialState.map(({ id, task, assignee_name, assignee_img }) => ( <Table.Row dragId={id} draggableItem key={id} > <Table.Cell>{id}</Table.Cell> <Table.Cell> <Flex align="center"> <Avatar imageUrl={assignee_img} size="xs" /> <Body paddingLeft="xxs" text={assignee_name} /> </Flex> </Table.Cell> <Table.Cell>{task}</Table.Cell> </Table.Row> ))} </Table.Body> </Table> </DraggableProvider> </> ); }; export default DraggableWithTableReact;
The Draggable kit can also be used to achieve more complex, multiple container functionality as shown here. This complex usage requires the full subcomponent structure.
import React, { useState } from "react"; import { Flex, Draggable, DraggableProvider, Badge, Title, Caption, Card, FlexItem, Avatar, Body } from 'playbook-ui' // Initial groups to drag between const containers = ["To Do", "In Progress", "Done"]; // Initial items to be dragged const data = [ { id: "11", container: "To Do", title: "Task 1", description: "Bug fixes", assignee_name: "Terry Miles", assignee_img: "https://randomuser.me/api/portraits/men/44.jpg", }, { id: "12", container: "To Do", title: "Task 2", description: "Documentation", assignee_name: "Sophia Miles", assignee_img: "https://randomuser.me/api/portraits/women/8.jpg", }, { id: "13", container: "In Progress", title: "Task 3", description: "Add a variant", assignee_name: "Alice Jones", assignee_img: "https://randomuser.me/api/portraits/women/10.jpg", }, { id: "14", container: "To Do", title: "Task 4", description: "Add jest tests", assignee_name: "Mike James", assignee_img: "https://randomuser.me/api/portraits/men/8.jpg", }, { id: "15", container: "Done", title: "Task 5", description: "Alpha testing", assignee_name: "James Guy", assignee_img: "https://randomuser.me/api/portraits/men/18.jpg", }, { id: "16", container: "In Progress", title: "Task 6", description: "Release", assignee_name: "Sally Jones", assignee_img: "https://randomuser.me/api/portraits/women/28.jpg", }, ]; const DraggableMultipleContainer = (props) => { const [initialState, setInitialState] = useState(data); const badgeProperties = (container) => { switch (container) { case "To Do": return { text: "queue", color: "warning" }; case "In Progress": return { text: "progress", color: "primary" }; default: return { text: "done", color: "success" }; } }; return ( <DraggableProvider initialItems={data} onReorder={(items) => setInitialState(items)} > <Flex justifyContent="center" > {containers?.map((container) => ( <Draggable.Container container={container} htmlOptions={{style:{ width: "200px", height: "70vh"}}} key={container} padding="sm" > <Caption textAlign="center">{container}</Caption> <Flex alignItems="stretch" orientation="column" > {initialState .filter((item) => item.container === container) .map( ({ assignee_img, assignee_name, description, id, title, }) => ( <Draggable.Item container={container} dragId={id} key={id} > <Card marginBottom="sm" padding="sm" > <Flex justify="between"> <FlexItem> <Flex> <Avatar imageUrl={assignee_img} name={assignee_name} size="xxs" /> <Title paddingLeft="xs" size={4} text={title} /> </Flex> </FlexItem> <Badge marginLeft="sm" rounded text={badgeProperties(container).text} variant={badgeProperties(container).color} /> </Flex> <Body paddingTop="xs" text={description} /> </Card> </Draggable.Item> ) )} </Flex> </Draggable.Container> ))} </Flex> </DraggableProvider> ); }; export default DraggableMultipleContainer;
The Draggable kit lets you customize the style of drop zones that appear when dragging an item.
By default, drop zones are in the "ghost" style, but you can also choose from "shadow," "outline," and "line."
When using the "line" type, make sure to set the appropriate direction
attribute within the dropZone
prop based on the orientation of your draggable view. By default, this is set to "vertical," but it can also be adjusted to "horizontal." Note that the direction attribute only applies to the "line" type and does not affect other drop zone styles. For more on the "line" style in particular, check out the Draggable Drop Zones Line doc example.
import React, { useState } from "react"; import { Body, Flex, FlexItem, Card, Caption, Draggable, DraggableProvider } from 'playbook-ui' // Initial items to be dragged const dataShadow = [ { id: "51", text: "Task 1", }, { id: "52", text: "Task 2", }, { id: "53", text: "Task 3", }, ]; const dataOutline = [ { id: "61", text: "Task 1", }, { id: "62", text: "Task 2", }, { id: "63", text: "Task 3", }, ]; const dataLine = [ { id: "71", text: "Task 1", }, { id: "72", text: "Task 2", }, { id: "73", text: "Task 3", }, ]; const DraggableDropZones = (props) => { const [initialShadowState, setInitialShadowState] = useState(dataShadow); const [initialOutlineState, setInitialOutlineState] = useState(dataOutline); const [initialLineState, setInitialLineState] = useState(dataLine); return ( <> <Flex justify="between" > <FlexItem marginRight="xl"> <DraggableProvider dropZone={{type: "shadow"}} initialItems={dataShadow} onReorder={(items) => setInitialShadowState(items)} > <Caption marginBottom="xs" text="Shadow" textAlign="center" /> <Draggable.Container htmlOptions={{style:{ width: "200px"}}} > {initialShadowState.map(({ id, text }) => ( <Card dragId={id} draggableItem key={id} marginBottom="xs" padding="xs" > <Flex alignItems="stretch" flexDirection="column" > <Body text={text} /> </Flex> </Card> ))} </Draggable.Container> </DraggableProvider> </FlexItem> <FlexItem marginRight="xl"> <DraggableProvider dropZone={{type: "outline"}} initialItems={dataOutline} onReorder={(items) => setInitialOutlineState(items)} > <Caption marginBottom="xs" text="Outline" textAlign="center" /> <Draggable.Container htmlOptions={{style:{ width: "200px"}}} > {initialOutlineState.map(({ id, text }) => ( <Card dragId={id} draggableItem key={id} marginBottom="xs" padding="xs" > <Flex alignItems="stretch" flexDirection="column" > <Body text={text} /> </Flex> </Card> ))} </Draggable.Container> </DraggableProvider> </FlexItem> <FlexItem> <DraggableProvider dropZone={{type: "line"}} initialItems={dataLine} onReorder={(items) => setInitialLineState(items)} > <Caption marginBottom="xs" text="Line" textAlign="center" /> <Draggable.Container htmlOptions={{style:{ width: "200px"}}} > {initialLineState.map(({ id, text }) => ( <Card dragId={id} draggableItem key={id} marginBottom="xs" padding="xs" > <Flex alignItems="stretch" flexDirection="column" > <Body text={text} /> </Flex> </Card> ))} </Draggable.Container> </DraggableProvider> </FlexItem> </Flex> </> ); }; export default DraggableDropZones;
The default color
for Draggable kit drop zones is "neutral", with "primary" or "purple" as additional options. When type
is set to "line", the default color is "primary" and "purple" is the only other option.
import React, { useState } from "react"; import { Flex, Image, Caption, Draggable, DraggableProvider } from 'playbook-ui' // Initial items to be dragged const dataPrimary = [ { id: "81", url: "https://unsplash.it/500/400/?image=633", }, { id: "82", url: "https://unsplash.it/500/400/?image=634", }, { id: "83", url: "https://unsplash.it/500/400/?image=637", }, ]; const dataPurple = [ { id: "91", url: "https://unsplash.it/500/400/?image=633", }, { id: "92", url: "https://unsplash.it/500/400/?image=634", }, { id: "93", url: "https://unsplash.it/500/400/?image=637", }, ]; const DraggableDropZonesColors = (props) => { const [initialPrimaryState, setInitialPrimaryState] = useState(dataPrimary); const [initialPurpleState, setInitialPurpleState] = useState(dataPurple); return ( <> <Caption marginBottom="xs" text="Primary" /> <DraggableProvider dropZone={{type: "shadow", color: "primary"}} initialItems={dataPrimary} onReorder={(items) => setInitialPrimaryState(items)} > <Draggable.Container> <Flex> {initialPrimaryState.map(({ id, url }) => ( <Draggable.Item dragId={id} key={id} marginRight="sm" > <Image alt={id} size="md" url={url} /> </Draggable.Item> ))} </Flex> </Draggable.Container> </DraggableProvider> <Caption marginBottom="xs" text="Purple" /> <DraggableProvider dropZone={{type: "outline", color: "purple"}} initialItems={dataPurple} onReorder={(items) => setInitialPurpleState(items)} > <Draggable.Container> <Flex> {initialPurpleState.map(({ id, url }) => ( <Draggable.Item dragId={id} key={id} marginRight="sm" > <Image alt={id} size="md" url={url} /> </Draggable.Item> ))} </Flex> </Draggable.Container> </DraggableProvider> </> ); }; export default DraggableDropZonesColors;
When using the "line" style, make sure to set the appropriate direction
attribute within the dropZone
prop based on the orientation of your draggable view. By default, this is set to "vertical," but it can also be adjusted to "horizontal." Note that the direction attribute only applies to the "line" style and does not affect other drop zone styles. The default color
for "line" is "primary" and "purple" is the only alternative color option.
The length of the line is calculated based off of the width (for "vertical") or height (for "horizontal") of the parent container holding the draggable items.
Additionally, if the parent container of the DraggableProvider
/DraggableContainer
(or a subcontainer within) does not have a set height (for "vertical") or width (for "horizontal"), like the Draggable Drop Zones doc example does, elements on the page may jump up (for "vertical") or to the left (for "horizontal") when an item is actively being dragged. To prevent this, give a parent element a fixed height (for "vertical") or width (for "horizontal") as demonstrated in this doc example.
import React, { useState } from "react"; import { Flex, Caption, Draggable, DraggableProvider, Image } from 'playbook-ui' const dataLineVertical = [ { id: "211", url: "https://unsplash.it/500/400/?image=633", }, { id: "212", url: "https://unsplash.it/500/400/?image=634", }, { id: "213", url: "https://unsplash.it/500/400/?image=637", }, ]; const dataLineHorizontal = [ { id: "2111", url: "https://unsplash.it/500/400/?image=633", }, { id: "2122", url: "https://unsplash.it/500/400/?image=634", }, { id: "2133", url: "https://unsplash.it/500/400/?image=637", }, ]; const DraggableDropZones = (props) => { const [initialLineVerticalState, setInitialLineVerticalState] = useState(dataLineVertical); const [initialLineHorizontalState, setInitialLineHorizontalState] = useState(dataLineHorizontal); return ( <> <Caption marginBottom="xs" marginTop="xl" text="Vertical" /> <DraggableProvider dropZone={{ type: "line", color: "purple" }} initialItems={dataLineVertical} onReorder={(items) => setInitialLineVerticalState(items)} > <Draggable.Container> <Flex flexDirection="column" height="367px" > {initialLineVerticalState.map(({ id, url }) => ( <Draggable.Item dragId={id} key={id} marginBottom="sm" > <Image alt={id} size="md" url={url} /> </Draggable.Item> ))} </Flex> </Draggable.Container> </DraggableProvider> <Caption marginBottom="xs" marginTop="xl" text="Horizontal" /> <Flex> <DraggableProvider dropZone={{ type: "line", direction: "horizontal" }} initialItems={dataLineHorizontal} onReorder={(items) => setInitialLineHorizontalState(items)} > <Draggable.Container htmlOptions={{style:{ width: "285px"}}} > <Flex alignItems="stretch" flexDirection="row" height="110px" > {initialLineHorizontalState.map(({ id, url }) => ( <Draggable.Item dragId={id} key={id} marginRight="sm" > <Image alt={id} size="md" url={url} /> </Draggable.Item> ))} </Flex> </Draggable.Container> </DraggableProvider> </Flex> </> ); }; export default DraggableDropZones;