DroppableListBox

A droppable listbox allows to drag and drop items from one list to another.

Installyarn add @diallink-corp/convergo-react-dnd
Version4.1.2
Usageimport {DroppableListBox} from '@diallink-corp/convergo-react-dnd'

Example

function Example() {
  const dragList = useListState({
    initialItems: [
      { id: '1', type: 'folder', text: 'One' },
      { id: '2', type: 'item', text: 'Two' },
      { id: '3', type: 'folder', text: 'Three' }
    ]
  });

  const id = useRef(2);
  const dropList = useListState({
    initialItems: [
      { id: 'Four', type: 'folder', text: 'Four' },
      { id: 'Five', type: 'item', text: 'Five' }
    ]
  });

  const ref = useRef(null);

  const onDragEnd = (e) => {
    if (e.dropOperation === 'move') {
      dragList.removeItem(...e.keys);
    }
  };

  const onCut = (keys) => {
    dragList.removeItem(...keys);
  };

  const onDrop = async (e) => {
    if (e.target.type === 'root' || e.target.dropPosition !== 'on') {
      const items = [];
      for (const item of e.items) {
        let type: string;
        if (item.kind === 'text') {
          if (item.types.has('folder')) {
            type = 'folder';
          } else if (item.types.has('item')) {
            type = 'item';
          }

          if (!type) {
            continue;
          }

          items.push({
            id: String(++id.current),
            type,
            text: await item.getText(type)
          });
        }
      }

      if (e.target.type === 'root') {
        dropList.prependItem(...items);
      } else if (e.target.dropPosition === 'before') {
        dropList.insertItemBefore(e.target.key, ...items);
      } else {
        dropList.insertItemAfter(e.target.key, ...items);
      }
    }
  };

  const handleRemoveItem = (key) => {
    const item = dropList.getItem(key);
    dragList.appendItem(item);
    dropList.removeItem(key);
  };

  return (
    <div style={{ display: 'flex', width: '100%' }}>
      <DraggableCollection
        items={dragList.items || []}
        selectedKeys={dragList.selectedKeys}
        onSelectionChange={dragList.setSelectedKeys}
        onDragEnd={onDragEnd}
        onCut={onCut}
        aria-label="Draggable list of items"
      >
        {(item) => (
          <Item key={item.id} textValue={item.text}>
            {item.type === 'folder' && (
              <Icon name="FolderIcon" variant="outline" />
            )}
            <span>{item.text}</span>
          </Item>
        )}
      </DraggableCollection>
      <DroppableListBox items={dropList.items || []} onDrop={onDrop} ref={ref}>
        {(item) => (
          <Item key={item.id} textValue={item.text}>
            {item.type === 'folder' && (
              <Icon name="FolderIcon" variant="outline" />
            )}
            <span>{item.text}</span>
            <Button
              size="small"
              variant="text"
              aria-label={`Remove item ${item.text}`}
              onPress={() => handleRemoveItem(item.id)}
              iconName="XMarkIcon"
              iconVariant="outline"
            />
          </Item>
        )}
      </DroppableListBox>
    </div>
  );
}
function Example() {
  const dragList = useListState({
    initialItems: [
      { id: '1', type: 'folder', text: 'One' },
      { id: '2', type: 'item', text: 'Two' },
      { id: '3', type: 'folder', text: 'Three' }
    ]
  });

  const id = useRef(2);
  const dropList = useListState({
    initialItems: [
      { id: 'Four', type: 'folder', text: 'Four' },
      { id: 'Five', type: 'item', text: 'Five' }
    ]
  });

  const ref = useRef(null);

  const onDragEnd = (e) => {
    if (e.dropOperation === 'move') {
      dragList.removeItem(...e.keys);
    }
  };

  const onCut = (keys) => {
    dragList.removeItem(...keys);
  };

  const onDrop = async (e) => {
    if (
      e.target.type === 'root' ||
      e.target.dropPosition !== 'on'
    ) {
      const items = [];
      for (const item of e.items) {
        let type: string;
        if (item.kind === 'text') {
          if (item.types.has('folder')) {
            type = 'folder';
          } else if (item.types.has('item')) {
            type = 'item';
          }

          if (!type) {
            continue;
          }

          items.push({
            id: String(++id.current),
            type,
            text: await item.getText(type)
          });
        }
      }

      if (e.target.type === 'root') {
        dropList.prependItem(...items);
      } else if (e.target.dropPosition === 'before') {
        dropList.insertItemBefore(e.target.key, ...items);
      } else {
        dropList.insertItemAfter(e.target.key, ...items);
      }
    }
  };

  const handleRemoveItem = (key) => {
    const item = dropList.getItem(key);
    dragList.appendItem(item);
    dropList.removeItem(key);
  };

  return (
    <div style={{ display: 'flex', width: '100%' }}>
      <DraggableCollection
        items={dragList.items || []}
        selectedKeys={dragList.selectedKeys}
        onSelectionChange={dragList.setSelectedKeys}
        onDragEnd={onDragEnd}
        onCut={onCut}
        aria-label="Draggable list of items"
      >
        {(item) => (
          <Item key={item.id} textValue={item.text}>
            {item.type === 'folder' && (
              <Icon name="FolderIcon" variant="outline" />
            )}
            <span>{item.text}</span>
          </Item>
        )}
      </DraggableCollection>
      <DroppableListBox
        items={dropList.items || []}
        onDrop={onDrop}
        ref={ref}
      >
        {(item) => (
          <Item key={item.id} textValue={item.text}>
            {item.type === 'folder' && (
              <Icon name="FolderIcon" variant="outline" />
            )}
            <span>{item.text}</span>
            <Button
              size="small"
              variant="text"
              aria-label={`Remove item ${item.text}`}
              onPress={() =>
                handleRemoveItem(item.id)}
              iconName="XMarkIcon"
              iconVariant="outline"
            />
          </Item>
        )}
      </DroppableListBox>
    </div>
  );
}
function Example() {
  const dragList =
    useListState({
      initialItems: [
        {
          id: '1',
          type: 'folder',
          text: 'One'
        },
        {
          id: '2',
          type: 'item',
          text: 'Two'
        },
        {
          id: '3',
          type: 'folder',
          text: 'Three'
        }
      ]
    });

  const id = useRef(2);
  const dropList =
    useListState({
      initialItems: [
        {
          id: 'Four',
          type: 'folder',
          text: 'Four'
        },
        {
          id: 'Five',
          type: 'item',
          text: 'Five'
        }
      ]
    });

  const ref = useRef(
    null
  );

  const onDragEnd = (
    e
  ) => {
    if (
      e.dropOperation ===
        'move'
    ) {
      dragList
        .removeItem(
          ...e.keys
        );
    }
  };

  const onCut =
    (keys) => {
      dragList
        .removeItem(
          ...keys
        );
    };

  const onDrop = async (
    e
  ) => {
    if (
      e.target.type ===
        'root' ||
      e.target
          .dropPosition !==
        'on'
    ) {
      const items = [];
      for (
        const item of e
          .items
      ) {
        let type: string;
        if (
          item.kind ===
            'text'
        ) {
          if (
            item.types
              .has(
                'folder'
              )
          ) {
            type =
              'folder';
          } else if (
            item.types
              .has(
                'item'
              )
          ) {
            type =
              'item';
          }

          if (!type) {
            continue;
          }

          items.push({
            id: String(
              ++id
                .current
            ),
            type,
            text:
              await item
                .getText(
                  type
                )
          });
        }
      }

      if (
        e.target.type ===
          'root'
      ) {
        dropList
          .prependItem(
            ...items
          );
      } else if (
        e.target
          .dropPosition ===
          'before'
      ) {
        dropList
          .insertItemBefore(
            e.target.key,
            ...items
          );
      } else {
        dropList
          .insertItemAfter(
            e.target.key,
            ...items
          );
      }
    }
  };

  const handleRemoveItem =
    (key) => {
      const item =
        dropList.getItem(
          key
        );
      dragList
        .appendItem(
          item
        );
      dropList
        .removeItem(key);
    };

  return (
    <div
      style={{
        display: 'flex',
        width: '100%'
      }}
    >
      <DraggableCollection
        items={dragList
          .items || []}
        selectedKeys={dragList
          .selectedKeys}
        onSelectionChange={dragList
          .setSelectedKeys}
        onDragEnd={onDragEnd}
        onCut={onCut}
        aria-label="Draggable list of items"
      >
        {(item) => (
          <Item
            key={item.id}
            textValue={item
              .text}
          >
            {item
                  .type ===
                'folder' &&
              (
                <Icon
                  name="FolderIcon"
                  variant="outline"
                />
              )}
            <span>
              {item.text}
            </span>
          </Item>
        )}
      </DraggableCollection>
      <DroppableListBox
        items={dropList
          .items || []}
        onDrop={onDrop}
        ref={ref}
      >
        {(item) => (
          <Item
            key={item.id}
            textValue={item
              .text}
          >
            {item
                  .type ===
                'folder' &&
              (
                <Icon
                  name="FolderIcon"
                  variant="outline"
                />
              )}
            <span>
              {item.text}
            </span>
            <Button
              size="small"
              variant="text"
              aria-label={`Remove item ${item.text}`}
              onPress={() =>
                handleRemoveItem(
                  item.id
                )}
              iconName="XMarkIcon"
              iconVariant="outline"
            />
          </Item>
        )}
      </DroppableListBox>
    </div>
  );
}

API