Skip to content

uiwjs/react-tabs-draggable

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

58 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-tabs-draggable

CI Open in unpkg npm version

Draggable tabs for React. Demo Preview: @uiwjs.github.io/react-tabs-draggable

Install

Not dependent on uiw.

npm install @uiw/react-tabs-draggable --save

Base Usage

import React, { useState } from 'react';
import Tabs, { Tab } from '@uiw/react-tabs-draggable';

function App() {
  const [activeKey, setActiveKey] = useState('tab-1');
  return (
    <div>
      <Tabs activeKey={activeKey} style={{ gap: 12 }} onTabClick={(id) => setActiveKey(id)}>
        <Tab id="tab-1">{activeKey === 'tab-1' && 'â–¶'}Google</Tab>
        <Tab id="tab-2">{activeKey === 'tab-2' && 'â–¶'}MicroSoft</Tab>
        <Tab id="tab-3">{activeKey === 'tab-3' && 'â–¶'}Baidu</Tab>
        <Tab id="tab-4">{activeKey === 'tab-4' && 'â–¶'}Taobao</Tab>
        <Tab id="tab-5">{activeKey === 'tab-5' && 'â–¶'}JD</Tab>
      </Tabs>
      <div style={{ background: '#fff', padding: 12 }}>{activeKey}</div>
    </div>
  );
}
export default App;

Disable Draggable

The first tab is disabled.

import React, { useState } from 'react';
import Tabs, { Tab } from '@uiw/react-tabs-draggable';
import styled from 'styled-components';

const TabItem = styled(Tab)`
  background-color: #b9b9b9;
  padding: 3px 7px;
  border-radius: 5px 5px 0 0;
  &.w-active {
    color: #fff;
    background-color: #333;
  }
`;

const Content = styled.div`
  border-top: 1px solid #333;
`;

function App() {
  const [activeKey, setActiveKey] = useState('tab-0-1');
  return (
    <div>
      <Tabs activeKey={activeKey} style={{ gap: 6 }} onTabClick={(id) => setActiveKey(id)}>
        <TabItem id="tab-0-1">Google</TabItem>
        <TabItem id="tab-0-2">MicroSoft</TabItem>
        <TabItem id="tab-0-3">Baidu</TabItem>
        <TabItem id="tab-0-4">Taobao</TabItem>
        <TabItem id="tab-0-5">JD</TabItem>
      </Tabs>
      <Content>{activeKey}</Content>
    </div>
  );
}
export default App;

Add & Close tab

The first tab is disabled.

import React, { Fragment, useState, useCallback } from 'react';
import Tabs, { Tab, useDataContext } from '@uiw/react-tabs-draggable';
import styled from 'styled-components';

const TabWarp = styled(Tabs)`
  max-width: 450px;
  border-bottom: 1px solid #333;
  margin-bottom: -2px;
  &:hover::-webkit-scrollbar {
    height: 0px;
    background-color: red;
  }
  &:hover::-webkit-scrollbar-track {
    background-color: #333;
  }
  &:hover::-webkit-scrollbar-thumb {
    background-color: green;
  }
`;

const TabItem = styled(Tab)`
  background-color: #b9b9b9;
  padding: 3px 7px;
  border-radius: 5px 5px 0 0;
  user-select: none;
  &.w-active {
    color: #fff;
    background-color: #333;
  }
`;

function insertAndShift(arr, from, to) {
  let cutOut = arr.splice(from, 1)[0];
  arr.splice(to, 0, cutOut);
  return arr;
}

let count = 9;

function App() {
  const [data, setData] = useState([
    { id: 'tab-4-1', children: 'Google' },
    { id: 'tab-4-2', children: 'MicroSoft' },
    { id: 'tab-4-3', children: 'Baidu' },
    { id: 'tab-4-4', children: 'Taobao' },
    { id: 'tab-4-5', children: 'JD' },
    { id: 'tab-4-6', children: 'Apple' },
    { id: 'tab-4-7', children: 'Bing' },
    { id: 'tab-4-8', children: 'Gmail' },
    { id: 'tab-4-9', children: 'Gitter' },
  ]);
  const [test, setTest] = useState(1);
  const [activeKey, setActiveKey] = useState('');

  const tabClick = (id, evn) => {
    evn.stopPropagation();
    setActiveKey(id);
    setTest(test + 1);
  };
  const closeHandle = (item, evn) => {
    evn.stopPropagation();
    const idx = data.findIndex((m) => m.id === item.id);

    let active = '';
    if (idx > -1 && activeKey) {
      active = data[idx - 1] ? data[idx - 1].id : data[idx].id;
      setActiveKey(active || '');
    }
    setData(data.filter((m) => m.id !== item.id));
  };
  const addHandle = () => {
    ++count;
    const newData = [...data, { id: `tab-3-${count}`, children: `New Tab ${count}` }];
    setData(newData);
  };
  const tabDrop = (id, index) => {
    const oldIndex = [...data].findIndex((m) => m.id === id);
    const newData = insertAndShift([...data], oldIndex, index);
    setData(newData);
  };
  return (
    <Fragment>
      <button onClick={addHandle}>Add{count}</button>
      <TabWarp
        activeKey={activeKey}
        style={{ gap: 3, overflow: 'auto' }}
        onTabClick={(id, evn) => tabClick(id, evn)}
        onTabDrop={(id, index) => tabDrop(id, index)}
      >
        {data.map((m, idx) => {
          return (
            <TabItem key={idx} id={m.id} draggable={idx !== 0}>
              {m.children}
              <button onClick={(evn) => closeHandle(m, evn)}>x</button>
            </TabItem>
          );
        })}
      </TabWarp>
      <div>{activeKey}</div>
    </Fragment>
  );
}
export default App;
import React, { Fragment, useState, useCallback } from 'react';
import Tabs, { Tab, useDataContext } from '@uiw/react-tabs-draggable';
import styled from 'styled-components';

const TabWarp = styled(Tabs)`
  max-width: 450px;
  border-bottom: 1px solid #333;
  margin-bottom: -2px;
  gap: 3px;
`;

const TabItem = styled(Tab)`
  background-color: #b9b9b9;
  padding: 3px 7px;
  border-radius: 5px 5px 0 0;
  user-select: none;
  flex-wrap: nowrap;
  overflow: hidden;
  word-break: keep-all;
  align-items: center;
  display: flex;
  position: relative;
  flex-direction: row;
  &.w-active {
    color: #fff;
    background-color: #333;
  }
`;

function insertAndShift(arr, from, to) {
  let cutOut = arr.splice(from, 1)[0];
  arr.splice(to, 0, cutOut);
  return arr;
}

let count = 9;

function App() {
  const [data, setData] = useState([
    { id: 'tab-4-1', children: 'Google' },
    { id: 'tab-4-2', children: 'MicroSoft' },
    { id: 'tab-4-3', children: 'Baidu' },
    { id: 'tab-4-4', children: 'Taobao' },
    { id: 'tab-4-5', children: 'JD' },
    { id: 'tab-4-6', children: 'Apple' },
    { id: 'tab-4-7', children: 'Bing' },
    { id: 'tab-4-8', children: 'Gmail' },
    { id: 'tab-4-9', children: 'Gitter' },
  ]);
  const [test, setTest] = useState(1);
  const [activeKey, setActiveKey] = useState('');

  const tabClick = (id, evn) => {
    evn.stopPropagation();
    setActiveKey(id);
    setTest(test + 1);
  };
  const closeHandle = (item, evn) => {
    evn.stopPropagation();
    setData(data.filter((m) => m.id !== item.id));
  };
  const addHandle = () => {
    ++count;
    const newData = [...data, { id: `tab-3-${count}`, children: `New Tab ${count}` }];
    setData(newData);
  };
  const tabDrop = (id, index, offset) => {
    const oldIndex = [...data].findIndex((m) => m.id === id);
    const newData = insertAndShift([...data], oldIndex, index);
    setData(newData);
  };
  return (
    <Fragment>
      <button onClick={addHandle}>Add{count}</button>
      <TabWarp
        onTabClick={(id, evn) => tabClick(id, evn)}
        onTabDrop={(id, index, offset) => tabDrop(id, index, offset)}
      >
        {data.map((m, idx) => {
          return (
            <TabItem key={idx} id={m.id}>
              {m.children}
              <button onClick={(evn) => closeHandle(m, evn)}>x</button>
            </TabItem>
          );
        })}
      </TabWarp>
      <div>{activeKey}</div>
    </Fragment>
  );
}
export default App;

Props

export interface TabsProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
  activeKey?: string;
  onTabClick?: (id: string, evn: React.MouseEvent<HTMLDivElement>) => void;
  /**
   * Optional. Called when a compatible item is dropped on the target.
   */
  onTabDrop?: (id: string, index?: number, offset?: XYCoord | null) => void;
}
export interface TabProps extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
  id: string;
  index?: number;
  /** Whether the Y axis can be dragged */
  dragableY?: boolean;
}
export declare const Tab: FC<PropsWithChildren<TabProps>>;

Development

npm run watch     # Listen create type and .tsx files.
npm run start     # Preview code example.

Contributors

As always, thanks to our amazing contributors!

Made with action-contributors.

License

Licensed under the MIT License.