qwertyvipul
qwertyvipul

Reputation: 31

Auto Scroll Horizontal Mui TabList on Drag with react-beautiful-dnd

I have tried to implement Drag & Drop feature in Mui TabList using react-beautiful-dnd. The dnd is working fine but, I am facing an issue making horizontal list of tabs auto scroll while dragging when there are too many tabs to fit in the screen and the variant prop of Mui TabList is scrollable. See this codesandbox example - https://codesandbox.io/s/draggable-and-scrollable-mui-tabs-xqgl77. This seems like a standard use case, so there must be some solution. Please refer my code below -

App.js

import * as React from "react";
import "./styles.css";
import DraggableTabsList from "./components/DraggableTabsList";

export default function App() {
  const [tabs, setTabs] = React.useState(
    [...Array(55)].map((_, index) => ({
      id: `tab${index + 1}`,
      label: `Tab ${index + 1}`,
      value: `${index + 1}`,
      content: `Content ${index + 1}`
    }))
  );

  const onDragEnd = (result) => {
    const newTabs = Array.from(tabs);
    const draggedTab = newTabs.splice(result.source.index, 1)[0];
    newTabs.splice(result.destination.index, 0, draggedTab);
    setTabs(newTabs);
  };

  return (
    <div className="App">
      <DraggableTabsList onDragEnd={onDragEnd} tabs={tabs} />
    </div>
  );
}

/components/DraggableTabsList.jsx

import * as React from "react";
import Box from "@mui/material/Box";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import TabPanel from "@mui/lab/TabPanel";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import DraggableTab from "./DraggableTab";
import Tab from "@mui/material/Tab";
import Stack from "@mui/material/Stack";

export default function DraggableTabsList(props) {
  const [value, setValue] = React.useState("1");

  const handleChange = (event, newValue) => {
    setValue(newValue);
  };

  const _renderTabList = (droppableProvided) => (
    <TabList
      onChange={handleChange}
      aria-label="Draggable Tabs"
      variant="scrollable"
    >
      {props.tabs.map((tab, index) => {
        const child = <Tab label={tab.label} value={tab.value} key={index} />;

        return (
          <DraggableTab
            label={tab.label}
            value={tab.value}
            index={index}
            key={index}
            child={child}
          />
        );
      })}
      {droppableProvided ? droppableProvided.placeholder : null}
    </TabList>
  );

  const _renderTabListWrappedInDroppable = () => (
    <DragDropContext onDragEnd={props.onDragEnd}>
      <Droppable droppableId="1" direction="horizontal">
        {(droppableProvided) => (
          <div
            ref={droppableProvided.innerRef}
            {...droppableProvided.droppableProps}
          >
            {_renderTabList(droppableProvided)}
          </div>
        )}
      </Droppable>
    </DragDropContext>
  );

  return (
    <Box sx={{ width: "100%", typography: "body1" }}>
      <TabContext value={value}>
        <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
          <Stack direction="column">{_renderTabListWrappedInDroppable()}</Stack>
        </Box>
        {props.tabs.map((tab, index) => (
          <TabPanel value={tab.value} key={index}>
            {tab.content}
          </TabPanel>
        ))}
      </TabContext>
    </Box>
  );
}

components/DraggableTab.jsx

import * as React from "react";
import { Draggable } from "react-beautiful-dnd";

export default function DraggableTab(props) {
  return (
    <Draggable
      draggableId={`${props.index}`}
      index={props.index}
      disableInteractiveElementBlocking
    >
      {(draggableProvided) => (
        <div
          ref={draggableProvided.innerRef}
          {...draggableProvided.draggableProps}
        >
          {React.cloneElement(props.child, {
            ...props,
            ...draggableProvided.dragHandleProps,
            style: { cursor: "inherit" }
          })}
        </div>
      )}
    </Draggable>
  );
}

Thanks!

Upvotes: 0

Views: 6371

Answers (1)

qwertyvipul
qwertyvipul

Reputation: 31

This can be solved by - in /components/DraggableTabsList.jsx wrap the Droppable inside a div container with style {display:"flex", overflow:"auto"}. The new code would look like -

<div style={{ display: 'flex', overflow: 'auto' }}>
    <Droppable droppableId="1" direction="horizontal">
        {(droppableProvided) => (
            <div
                ref={droppableProvided.innerRef}
                {...droppableProvided.droppableProps}
            >
                {_renderTabList(droppableProvided)}
            </div>
        )}
    </Droppable>
</div>

This will make the tabs horizontally draggable through the entire tab list, but the scroll buttons from the MUI TabList will not render.

Codesandbox - https://codesandbox.io/s/mr9y10

Upvotes: 1

Related Questions