vizhub-core / vzcode

Mob Programming Code Editor
MIT License
71 stars 14 forks source link

Draggable tabs to re-order #56

Open Anooj-Pai opened 1 year ago

curran commented 1 year ago

Some ideas from AI:

import { useCallback, useMemo, useState } from 'react';
import { FileId, Files } from '../../types';
import './style.scss';

...

const Tab = ({ ... }) => {
  ...

  // This will hold the ID of the file/tab being dragged.
  const [draggedFile, setDraggedFile] = useState(null);

  const handleDragStart = (event: React.DragEvent) => {
    event.dataTransfer.setData('text/plain', fileId.toString());
    setDraggedFile(fileId);
  };

  return (
    <div
      ...
      draggable="true"
      onDragStart={handleDragStart}
    >
      ...
    </div>
  );
};

export const TabList = ({ ... }) => {

  const handleDrop = useCallback(
    (event: React.DragEvent, targetFileId: FileId) => {
      event.preventDefault();

      const draggedFileId = Number(event.dataTransfer.getData('text/plain'));

      // Swap positions of draggedFileId and targetFileId in tabList.
      const newTabList = [...tabList];
      const draggedIndex = newTabList.indexOf(draggedFileId);
      const targetIndex = newTabList.indexOf(targetFileId);
      newTabList[draggedIndex] = targetFileId;
      newTabList[targetIndex] = draggedFileId;

      // You need to implement the "setTabList" method to update the state or props.
      setTabList(newTabList);
    },
    [tabList],
  );

  const handleDragOver = (event: React.DragEvent) => {
    event.preventDefault();
  };

  return (
    <div className="vz-tab-list">
      {files &&
        tabList.map((fileId: FileId) => (
          <Tab
            ...
            onDrop={(event) => handleDrop(event, fileId)}
            onDragOver={handleDragOver}
          />
        ))}
    </div>
  );
};
import { useCallback, useMemo } from 'react';
import { FileId, Files } from '../../types';
import './style.scss';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

const ItemType = {
  TAB: 'TAB',
};

const Tab = ({ ... }) => {
  ...

  const [, ref] = useDrag({
    type: ItemType.TAB,
    item: { fileId },
  });

  const [, drop] = useDrop({
    accept: ItemType.TAB,
    hover(draggedItem) {
      if (draggedItem.fileId !== fileId) {
        // Implement the reorder logic
        // E.g., reorderTabs(draggedItem.fileId, fileId);
      }
    }
  });

  return (
    <div
      ref={(node) => {
        ref(node);
        drop(node);
      }}
      className={isActive ? 'tab active' : 'tab'}
      onClick={() => {
        setActiveFileId(fileId);
      }}
    >
      ...
    </div>
  );
};

export const TabList = ({ ... }) => {
  return (
    <DndProvider backend={HTML5Backend}>
      <div className="vz-tab-list">
        {files &&
          tabList.map((fileId: FileId) => (
            <Tab
              ...
            />
          ))}
      </div>
    </DndProvider>
  );
};
curran commented 1 year ago

https://master--5fc05e08a4a65d0021ae0bf2.chromatic.com/?path=/story/presets-sortable-horizontal--basic-setup

curran commented 1 year ago

More ideas from GPT4:

To add drag-and-drop reordering for the tabs, we can use the HTML Drag and Drop API. Below are the steps to implement this feature:

  1. Add draggable="true" to the Tab component.
  2. Set up drag event listeners for the tabs: onDragStart, onDragOver, and onDrop.
  3. Handle the drag start to set the dragged tab's index.
  4. Handle drag over to specify the drop target.
  5. Handle the drop to reorder the tabs in the tabList array.
  6. Update the state when the tabs are reordered.

Here's how you can modify the provided code to support drag & drop reordering:

// Inside TabList component:

const [draggedTabIndex, setDraggedTabIndex] = useState<number | null>(null);

const handleDragStart = (index: number) => {
  setDraggedTabIndex(index);
};

const handleDrop = (index: number) => {
  if (draggedTabIndex !== null && draggedTabIndex !== index) {
    const newTabList = [...tabList];
    const [removed] = newTabList.splice(draggedTabIndex, 1);
    newTabList.splice(index, 0, removed);
    setTabList(newTabList);
  }
  setDraggedTabIndex(null);
};

return (
  <div className="vz-tab-list">
    {files &&
      tabList.map((fileId: FileId, index: number) => (
        <Tab
          key={fileId}
          fileId={fileId}
          index={index}
          onDragStart={() => handleDragStart(index)}
          onDrop={() => handleDrop(index)}
          isActive={fileId === activeFileId}
          setActiveFileId={setActiveFileId}
          closeTabs={closeTabs}
          fileName={files[fileId].name}
        />
      ))}
  </div>
);

// Inside Tab component:

const { index, onDragStart, onDrop } = props;

return (
  <div
    className={isActive ? 'tab active' : 'tab'}
    onClick={() => {
      setActiveFileId(fileId);
    }}
    draggable="true"
    onDragStart={onDragStart}
    onDragOver={(e) => e.preventDefault()}
    onDrop={onDrop}
  >
    {tabName}
    <div
      className={'bx bx-x tab-close'}
      onClick={handleCloseClick}
    ></div>
  </div>
);

Note:

curran commented 2 months ago

TODO drop a video of what VSCode does