├── .babelrc ├── .codeclimate.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor.yml ├── devServer.js ├── docs ├── components │ ├── AddAndClose.js │ ├── Async.js │ ├── Basic.js │ ├── Complicated.js │ ├── Draggable.js │ ├── IconTab.js │ ├── Modal.js │ └── utils.js ├── index.ejs ├── logo.png ├── root.js ├── standalone.html └── themes.js ├── flow-typed ├── npm │ └── styled-components_v2.x.x.js ├── react-icons.js └── react-poppop.js ├── package.json ├── rollup.config.js ├── src ├── AsyncPanel.js ├── CloseButton.js ├── DragTab.js ├── DragTabList.js ├── ExtraButton.js ├── IconSvg.js ├── Panel.js ├── PanelList.js ├── SortMethod.js ├── Tab.js ├── TabList.js ├── TabModal.js ├── Tabs.js ├── helpers │ ├── delete.js │ └── move.js ├── index.js ├── themes │ ├── bootstrap │ │ └── index.js │ ├── bulma │ │ └── index.js │ └── material-design │ │ └── index.js └── utils │ ├── countTab.js │ └── isType.js ├── test ├── AsyncPanel.test.js ├── DragTabList.test.js ├── Panel.test.js ├── PanelList.test.js ├── SortMethod.test.js ├── Tab.test.js ├── TabList.test.js ├── TabModal.test.js ├── Tabs.test.js ├── __snapshots__ │ ├── DragTabList.test.js.snap │ ├── Panel.test.js.snap │ ├── PanelList.test.js.snap │ ├── Tab.test.js.snap │ ├── TabList.test.js.snap │ ├── TabModal.test.js.snap │ └── Tabs.test.js.snap ├── enzyme-setup.js ├── helpers │ └── delete.test.js ├── index.test.js ├── shim.js ├── tabListTest.js └── utils │ ├── countTab.test.js │ └── isType.test.js ├── webpack.config.dev.js ├── webpack.config.prod.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react" 5 | ], 6 | "plugins": [ 7 | "add-module-exports", 8 | "transform-class-properties", 9 | "transform-object-rest-spread", 10 | "transform-flow-strip-types" 11 | ], 12 | "env": { 13 | "development": { 14 | "plugins": [ 15 | "flow-react-proptypes" 16 | ] 17 | }, 18 | "production": { 19 | "plugins": [ 20 | "transform-react-remove-prop-types" 21 | ] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | exclude_patterns: 2 | - "test/*" 3 | - "docs/*" 4 | - "flow-typed/*" -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*.js] 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | flow_typed/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "prettier", 4 | "eslint:recommended", 5 | "plugin:react/recommended", 6 | "plugin:jest/recommended", 7 | "plugin:flowtype/recommended" 8 | ], 9 | "parser": "babel-eslint", 10 | "parserOptions": { 11 | "ecmaVersion": 7, 12 | "sourceType": "module" 13 | }, 14 | "env": { 15 | "browser": true, 16 | "node": true, 17 | "es6": true, 18 | "jest": true 19 | }, 20 | "plugins": [ 21 | "prettier", 22 | "react", 23 | "jest", 24 | "flowtype" 25 | ], 26 | "rules": { 27 | "switch-colon-spacing": 0, 28 | "require-jsdoc": 0, 29 | "react/prop-types": 0, 30 | "no-unused-vars": [ 31 | "error" 32 | ], 33 | "no-console": 0 34 | } 35 | } -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/flow-remove-types/.* 3 | .*/node_modules/babel-plugin-transform-react-remove-prop-types/.* 4 | .*/node_modules/babel-plugin-flow-react-proptypes/src/__test__/.* 5 | .*/lib/.* 6 | 7 | [include] 8 | 9 | [libs] 10 | 11 | [options] 12 | suppress_type=$FlowFixMe 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | _gh-pages 3 | lib 4 | yarn.lock 5 | .DS_Store 6 | bundle-stats.html 7 | dist 8 | coverage -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/*.map 3 | example 4 | src 5 | scss 6 | test.js 7 | server.js 8 | webpack* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 8 4 | cache: 5 | yarn: true 6 | before_install: 7 | yarn add codecov 8 | script: 9 | - npm run flow 10 | - npm test -- --coverage 11 | after_success: 12 | codecov 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.5.0 2 | 3 | * Support `AsyncPanel`: can lazy loading panel content 4 | 5 | # 1.0.0 6 | 7 | ### Breaking change 8 | 9 | Version 1 redesign the api and add more features. 10 | 11 | * **Mobile supported** — Touch support. Easy to use on mobile device 12 | * **Draggable tab** — Support drag and drop tab 13 | * **Add & Delete** — Tab can be added and deleted 14 | * **Customizable style** — Based on `styled-components`, super easy to customize tab style 15 | * **API based** — All actions are controllable 16 | 17 | # 0.6.2 18 | 19 | * update readme 20 | * move react-dnd to top component 21 | 22 | # 0.5.0 23 | 24 | * add drag and drop 25 | * use es6 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 ctxhou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |
5 |

6 | 7 | [![version](https://img.shields.io/npm/v/react-tabtab.svg)](https://www.npmjs.com/package/react-tabtab/) 8 | [![travis](https://travis-ci.org/ctxhou/react-tabtab.svg?branch=master)](https://travis-ci.org/ctxhou/react-tabtab) 9 | [![appveyor](https://ci.appveyor.com/api/projects/status/jn6724u4k4uer53j?svg=true)](https://ci.appveyor.com/project/ctxhou/react-tabtab) 10 | ![david](https://david-dm.org/ctxhou/react-tabtab.svg) 11 | [![codecov](https://codecov.io/gh/ctxhou/react-tabtab/branch/master/graph/badge.svg)](https://codecov.io/gh/ctxhou/react-tabtab) 12 | ![download](https://img.shields.io/npm/dm/react-tabtab.svg) 13 | ![gzip size](http://img.badgesize.io/https://unpkg.com/react-tabtab/dist/react-tabtab.min.js?compression=gzip) 14 | 15 | > A mobile support, draggable, editable and api based Tab for ReactJS.
16 | > *Support react >= `v16.3`* 17 | 18 | **Note: Since v2, we don't support v15 and old styled-components version (<4.0.0) [v15 document](https://github.com/ctxhou/react-tabtab/blob/v1.8.4/README.md)** 19 | 20 | ### [Demo](https://ctxhou.github.io/react-tabtab/) 21 | 22 | ![demo gif](https://media.giphy.com/media/xUOxeRdRrSvSLiX528/giphy.gif) 23 | 24 | ## Features 25 | 26 | * **Mobile supported** — Touch support. Easy to use on mobile device 27 | * **Draggable tab** — Support drag and drop tab 28 | * **Add & Delete** — Tab can be added and deleted 29 | * **Async content** — Lazy load panel content 30 | * **Customizable style** — Based on `styled-components`, super easy to customize tab style 31 | * **API based** — All actions are controllable 32 | * **ARIA accessible** 33 | 34 | ## Table of Contents 35 | 36 | 37 | 38 | - [Installation](#installation) 39 | - [Usage](#usage) 40 | * [Minimal setup](#minimal-setup) 41 | * [Draggable tab](#draggable-tab) 42 | * [Async Panel](#async-panel) 43 | * [Another Example](#another-example) 44 | - [Components / Api](#components--api) 45 | * [<Tabs />](#lttabs-gt) 46 | * [<TabList />](#lttablist-gt) 47 | * [<Tab />](#lttab-gt) 48 | * [<DragTabList />](#ltdragtablist-gt) 49 | * [<DragTab/ >](#ltdragtab-gt) 50 | * [<PanelList/ >](#ltpanellist-gt) 51 | * [<Panel />](#ltpanel-gt) 52 | * [<AsyncPanel />](#ltasyncpanel-gt) 53 | - [Customize style](#customize-style) 54 | * [Use current style](#use-current-style) 55 | * [Make your own style](#make-your-own-style) 56 | - [Development](#development) 57 | - [License](#license) 58 | 59 | 60 | 61 | ## Installation 62 | 63 | Install it with npm or yarn 64 | 65 | Install `styled-components`. Because we put the `styled-components` to the peerDependencies, it suggests by [styled-components official blog](https://www.styled-components.com/docs/faqs#i-am-a-library-author-should-i-bundle-styledcomponents-with-my-library) 66 | 67 | ```js 68 | $ npm install react-tabtab --save 69 | $ npm install styled-components@^4.0.0 --save 70 | ``` 71 | 72 | Then, import the module by module bundler like `webpack`, `browserify` 73 | 74 | ```js 75 | // es6 76 | import {Tabs, DragTabList, DragTab, PanelList, Panel, ExtraButton} from 'react-tabtab'; 77 | 78 | // not using es6 79 | var Tabtab = require('react-tabtab'); 80 | var Tabs = Tabtab.Tabs; 81 | var DragTabList = Tabtab.DragTabList; 82 | var DragTab = Tabtab.DragTab; 83 | var PanelList = Tabtab.PanelList; 84 | var Panel = Tabtab.Panel; 85 | var ExtraButton = Tabtab.ExtraButton; 86 | ``` 87 | 88 | UMD build is also available. If you do this, you'll need to include the dependencies: 89 | 90 | For example: 91 | 92 | ```html 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ``` 102 | 103 | You can reference [standalone.html](https://github.com/ctxhou/react-tabtab/blob/master/docs/standalone.html) example. 104 | 105 | 106 | ## Usage 107 | 108 | React-tabtab is a tab component with highly customization. You can create a tab in simply setting. You also can create a tab system full with `draggable`, `async loading`, `close and create button`. 109 | All the actions are api based. It means there is `no state` in the component. Developers have full control. 110 | 111 | ### Minimal setup 112 | 113 | ```js 114 | import React, {Component} from 'react'; 115 | import ReactDOM from 'react-dom'; 116 | import {Tabs, TabList, Tab, PanelList, Panel} from 'react-tabtab'; 117 | 118 | class Basic extends Component { 119 | render() { 120 | return ( 121 | 122 | 123 | Tab1 124 | Tab2 125 | 126 | 127 | Content1 128 | Content2 129 | 130 | 131 | ) 132 | } 133 | } 134 | 135 | ReactDOM.render(, document.getElementById('root')); 136 | ``` 137 | 138 | It's simple to use. Zero configuration! 139 | 140 | ### Draggable tab 141 | 142 | ```js 143 | import React, {Component} from 'react'; 144 | import {Tabs, DragTabList, DragTab, PanelList, Panel} from 'react-tabtab'; 145 | import {simpleSwitch} from 'react-tabtab/lib/helpers/move'; 146 | 147 | export default class Drag extends Component { 148 | constructor(props) { 149 | super(props); 150 | this.handleTabChange = this.handleTabChange.bind(this); 151 | this.handleTabSequenceChange = this.handleTabSequenceChange.bind(this); 152 | this.state = { 153 | activeIndex: 0, 154 | } 155 | } 156 | 157 | handleTabChange(index) { 158 | this.setState({activeIndex: index}); 159 | } 160 | 161 | handleTabSequenceChange({oldIndex, newIndex}) { 162 | const {tabs} = this.state; 163 | const updateTabs = simpleSwitch(tabs, oldIndex, newIndex); 164 | this.setState({tabs: updateTabs, activeIndex: newIndex}); 165 | } 166 | 167 | render() { 168 | const {activeIndex} = this.state; 169 | return ( 170 | 173 | 174 | DragTab1 175 | DragTab2 176 | 177 | 178 | Content1 179 | Content2 180 | 181 | 182 | ) 183 | } 184 | } 185 | ReactDOM.render(, document.getElementById('root')); 186 | ``` 187 | 188 | Based on above example, the different to implement `normal tab` or `drag tab` is using different wrapper and child. 189 | 190 | And all the actions are controllable. You can customize your switch action. But if you don't want to write customized switch logic, you can directly use `import {simpleSwitch} from 'react-tabtab/lib/helpers/move'` this built-in function. 191 | 192 | **normal tab** 193 | 194 | ```js 195 | 196 | 197 | Tab1 198 | 199 | 200 | Content1 201 | 202 | 203 | ``` 204 | 205 | **drag tab** 206 | 207 | ```js 208 | 209 | 210 | DragTab1 211 | 212 | 213 | Content1 214 | 215 | 216 | ``` 217 | 218 | ### Async Panel 219 | 220 | In some case, if the data is large or we want to save the bandwidth, lazy loading the content is possible solution. You can use `AsyncPanel` to laze load panel content. 221 | Moreover, you can mix lazy load panel with normal panel! 222 | 223 | ```js 224 | import React, {Component} from 'react'; 225 | import ReactDOM from 'react-dom'; 226 | import {Tabs, TabList, Tab, PanelList, AsyncPanel, Panel} from 'react-tabtab'; 227 | 228 | function loadContentFunc(callback) { 229 | setTimeout(() => { 230 | callback(null, [ 231 | {product: 'json'}, 232 | {product: 'joseph'} 233 | ]); 234 | }, 100); 235 | } 236 | 237 | // You also can provide promise as return function: 238 | // function loadContentFunc() { 239 | // return fetch('/products') 240 | // .then(resp => resp.json()) 241 | // .then(data => data); 242 | // } 243 | 244 | class AsyncTab extends Component { 245 | render() { 246 | return ( 247 | 248 | 249 | Tab1 250 | Tab2 251 | 252 | 253 | Content1 254 | (
{JSON.stringify(data)}
)} 256 | renderLoading={() => (
Loading...
)} 257 | cache={true} 258 | /> 259 |
260 |
261 | ) 262 | } 263 | } 264 | 265 | ReactDOM.render(, document.getElementById('root')); 266 | ``` 267 | 268 | To implement lazy loading, use `AsyncPanel` to wrap your panel content. Remember to provide `loadContent`, `render`, `renderLoading` these 3 props. 269 | 270 | In `loadContent` props, both `callback` and `promise` type are supported. 271 | 272 | If you use `callback`, remember to call `callback` when finish async loading. 273 | 274 | If you use `promise`, need to return promise action. 275 | 276 | When data is loading, the panel content will show `renderLoading` component. 277 | 278 | After finishing loading data, the panel content will show `render` component and react-tabtab will pass the `loadContent` result as first parameter. So you can customize the component of panel content. 279 | 280 | Live example: [Link](https://ctxhou.github.io/react-tabtab/#async) 281 | 282 | ### Another Example 283 | 284 | Except drag and drop tab, `react-tabtab` also support other usable application, like: 285 | 286 | * Add and close button: [Example](https://ctxhou.github.io/react-tabtab/#add-close) 287 | * Modal view at mobile support: [Example](https://ctxhou.github.io/react-tabtab/#modal) 288 | * Auto detect number of tab and make it scrollable 289 | * **All the action is controllable**:[Example](https://ctxhou.github.io/react-tabtab/#complicated) 290 | 291 | All of these features are api based, so you can customize each action on demand. 292 | 293 | More code examples are avalable [here](https://github.com/ctxhou/react-tabtab/blob/master/docs/components/). 294 | 295 | ## Components / Api 296 | 297 | ### <Tabs /> 298 | 299 | `` is the main component of `react-tabtab`. Most of the api is passed from it. 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 338 | 339 | 340 | 341 | 342 | 343 | 348 | 349 | 350 | 351 | 352 | 353 | 356 | 357 | 358 | 359 | 360 | 361 | 365 | 366 | 367 | 368 | 369 | 370 | 375 | 376 | 377 | 378 | 379 | 380 | 383 | 384 | 385 | 386 | 395 | 396 | 399 | 400 | 401 |
propstypedefault
defaultIndexintnullset the initial active key
activeIndexintnullcontrol current activeIndex.
You need to pass new activeIndex value if you want to show different tab.
defaultIndexintnullset the initial active key
showModalButtonboolean
number
4 332 |
    333 |
  • true: always show button
  • 334 |
  • false: always hide button
  • 335 |
  • [number]: when number of tab >= [number], show button
  • 336 |
337 |
showArrowButtonauto
boolean
auto 344 |
  • auto: detect tab width, if they exceed container, show button
  • 345 |
  • true: always show button
  • 346 |
  • false: always hide button
  • 347 |
    ExtraButtonReact Nodenull 354 | customize extra button content, example: `+` button 355 |
    onTabChange() => tabIndexnull 362 | return tabIndex is clicked
    363 | You can use this api with activeIndex. When user click tab, update activeIndex. 364 |
    onTabSequenceChange() => {oldIndex, newIndex}null 371 | return changed oldIndex and newIndex value
    372 | With this api, you can do switch tab very easily. 373 | Note:This api is only called by <DragTab/> 374 |
    onTabEdit() => {type: [delete], index}null 381 | When user click close button , this api will return the clicked close button index. 382 |
    customStyle 387 |
    388 | {
    389 |   TabList: React.Element,
    390 |   Tab: React.Element,
    391 |   Panel: React.Element,
    392 |   ActionButton: React.Element
    393 | }
    394 |
    Bootstrap theme 397 | customized tab style component 398 |
    402 | 403 | ### <TabList /> 404 | 405 | Use to wrap ``. 406 | 407 | ### <Tab /> 408 | 409 | Normal Tab. Show the children component on tab. 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 |
    propstypedefault
    closablebooleanfalsewhether to show close button
    427 | 428 | **Example** 429 | 430 | ```js 431 | 432 | 433 | map tab 434 | 435 | ``` 436 | 437 | ### <DragTabList /> 438 | 439 | Use to wrap ``. 440 | 441 | ### <DragTab/ > 442 | 443 | A draggable tab. Api is the same with `` 444 | 445 | ### <PanelList/ > 446 | 447 | Use to wrap `` 448 | 449 | ### <Panel /> 450 | 451 | Tab content. 452 | 453 | ### <AsyncPanel /> 454 | 455 | Lazy loading panel content. 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 471 | 472 | 473 | 474 | 475 | 476 | 479 | 480 | 481 | 482 | 483 | 484 | 487 | 488 | 489 | 490 | 491 | 492 | 495 | 496 | 497 | 498 | 499 |
    propstypedefault
    loadContent * 468 | (cb) => cb(error, data) or
    469 | (cb) => Promise 470 |
    nullwhen loadContent finish, call the callback or you can return promise
    render * 477 | (data) => Component 478 | nullwhen finish loading data, render this component
    renderLoading * 485 | () => Component 486 | nullwhen it is loading data, render this component
    cache 493 | boolean 494 | trueshould cache the data
    500 | 501 | ## Customize style 502 | 503 | `react-tabtab` is based on `styled-components`. Therefore, it's super easy to customize the tab style. 504 | 505 | Just extend the default component style and pass it to `customStyle` props. 506 | 507 | ### Use current style 508 | 509 | You can check the current style at `src/themes` folder. 510 | 511 | For example, if you want to use `material-design`, import the style and pass to `customStyle` props. 512 | 513 | **Example:** 514 | 515 | ```js 516 | import {Component} from 'react'; 517 | import {Tabs, TabList, Tab, PanelList, Panel} from 'react-tabtab'; 518 | import * as customStyle from 'react-tabtab/lib/themes/material-design'; 519 | 520 | class Customized extends Component { 521 | render() { 522 | return ( 523 | 524 | 525 | Tab1 526 | Tab2 527 | 528 | 529 | Content1 530 | Content2 531 | 532 | 533 | ) 534 | } 535 | } 536 | ``` 537 | 538 | And now your tab is material design style! 539 | 540 | ### Make your own style 541 | 542 | If current theme doesn't meet your demand, follow this three steps and create a new one. 543 | 544 | **First step: import current style** 545 | 546 | ```js 547 | import styled from 'styled-components'; 548 | import { styled as styledTabTab } from 'react-tabtab'; 549 | 550 | let {TabListStyle, ActionButtonStyle, TabStyle, PanelStyle} = styledTabTab; 551 | ``` 552 | 553 | **Second: extend style and export it** 554 | 555 | ```js 556 | import styled from 'styled-components'; 557 | import { styled as styledTabTab } from 'react-tabtab'; 558 | 559 | let {TabListStyle, ActionButtonStyle, TabStyle, PanelStyle} = styledTabTab; 560 | 561 | TabListStyle = styled(TabListStyle)` 562 | // write css 563 | `; 564 | 565 | TabStyle = styled(TabStyle)` 566 | // write css 567 | `; 568 | 569 | ActionButtonStyle = styled(ActionButtonStyle)` 570 | // write css 571 | `; 572 | 573 | PanelStyle = styled(PanelStyle)` 574 | // write css 575 | `; 576 | 577 | // need to follow this object naming 578 | module.exports = { 579 | TabList: TabListStyle, 580 | ActionButton: ActionButtonStyle, 581 | Tab: TabStyle, 582 | Panel: PanelStyle 583 | } 584 | ``` 585 | 586 | **Last: import your style and use it!** 587 | 588 | When you finish the new `react-tabtab` style, feel free to add it to `theme/` folder and send PR! 589 | 590 | ## Development 591 | 592 | ```bash 593 | $ yarn 594 | $ npm start 595 | ``` 596 | ## License 597 | 598 | MIT [@ctxhou](github.com/ctxhou) 599 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | environment: 4 | nodejs_version: "6" 5 | 6 | install: 7 | - ps: Install-Product node $env:nodejs_version x64 8 | - yarn 9 | 10 | cache: 11 | - '%LOCALAPPDATA%/Yarn' 12 | 13 | test_script: 14 | - node --version 15 | - npm --version 16 | - npm run flow 17 | - npm test 18 | 19 | # Don't actually build. 20 | build: off 21 | -------------------------------------------------------------------------------- /devServer.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.config.dev'); 5 | 6 | var app = express(); 7 | var compiler = webpack(config); 8 | 9 | app.use(require('webpack-dev-middleware')(compiler, { 10 | noInfo: true, 11 | publicPath: config.output.publicPath 12 | })); 13 | 14 | app.use(require('webpack-hot-middleware')(compiler)); 15 | 16 | app.use(express.static('public')); 17 | 18 | app.use('*', function (req, res, next) { 19 | var filename = path.join(compiler.outputPath, 'index.html'); 20 | compiler.outputFileSystem.readFile(filename, function(err, result){ 21 | if (err) { 22 | return next(err); 23 | } 24 | res.set('content-type','text/html'); 25 | res.send(result); 26 | res.end(); 27 | }); 28 | }); 29 | 30 | app.listen(5000, 'localhost', function(err) { 31 | if (err) { 32 | console.log(err); 33 | return; 34 | } 35 | 36 | console.log('Listening at http://localhost:5000'); 37 | }); -------------------------------------------------------------------------------- /docs/components/AddAndClose.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Tabs, TabList, Tab, PanelList, Panel, ExtraButton} from '../../src'; 3 | import Plus from 'react-icons/lib/fa/plus'; 4 | import {makeData} from './utils'; 5 | 6 | export default class Closable extends Component { 7 | constructor(props) { 8 | super(props); 9 | const tabs = makeData(3); 10 | 11 | this.state = { 12 | tabs, 13 | activeIndex: 0 14 | }; 15 | } 16 | 17 | handleExtraButton = () => { 18 | const {tabs} = this.state; 19 | const newTabs = [...tabs, {title: 'New Tab', content: 'New Content'}]; 20 | this.setState({tabs: newTabs, activeIndex: newTabs.length - 1}); 21 | } 22 | 23 | handleTabChange = (index) => { 24 | this.setState({activeIndex: index}); 25 | } 26 | 27 | handleEdit = ({type, index}) => { 28 | this.setState((state) => { 29 | let {tabs, activeIndex} = state; 30 | if (type === 'delete') { 31 | tabs = [...tabs.slice(0, index), ...tabs.slice(index + 1)]; 32 | } 33 | if (index - 1 >= 0) { 34 | activeIndex = index - 1; 35 | } else { 36 | activeIndex = 0; 37 | } 38 | return {tabs, activeIndex}; 39 | }); 40 | } 41 | 42 | render() { 43 | const {tabs, activeIndex} = this.state; 44 | const tabTemplate = []; 45 | const panelTemplate = []; 46 | 47 | tabs.forEach((tab, i) => { 48 | const closable = tabs.length > 1; 49 | tabTemplate.push({tab.title}); 50 | panelTemplate.push({tab.content}); 51 | }) 52 | return ( 53 | 59 | 60 | 61 | }> 62 | 63 | {tabTemplate} 64 | 65 | 66 | {panelTemplate} 67 | 68 | 69 | ) 70 | } 71 | } -------------------------------------------------------------------------------- /docs/components/Async.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Tabs, TabList, Tab, PanelList, AsyncPanel, Panel} from '../../src'; 3 | import {Facebook} from 'react-content-loader' 4 | const MyFacebookLoader = () => 5 | 6 | function fakeCbFetch(cb) { 7 | setTimeout(() => { 8 | cb(null, { 9 | content: 'hi' 10 | }); 11 | }, 1500); 12 | } 13 | 14 | function fakePromiseFetch() { 15 | return new Promise((res) => { 16 | setTimeout(() => { 17 | res({ 18 | content: 'promise fetched' 19 | }); 20 | }, 1500); 21 | }) 22 | } 23 | 24 | export default class Basic extends Component { 25 | render() { 26 | return ( 27 | 28 | 29 | Normal panel 30 | Callback fetch panel 31 | Promise fetch panel 32 | fetch without cache 33 | 34 | 35 | 36 | Normal tab. 37 | You can mix normal panel with async panel 38 | 39 | (
    {JSON.stringify(data)}
    )} 41 | renderLoading={() => ()} 42 | /> 43 | (
    {JSON.stringify(data)}
    )} 45 | renderLoading={() => ()} 46 | /> 47 | (
    {JSON.stringify(data)}
    )} 49 | renderLoading={() => ()} 50 | cache={false} 51 | /> 52 |
    53 |
    54 | ) 55 | } 56 | } -------------------------------------------------------------------------------- /docs/components/Basic.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Tabs, TabList, Tab, PanelList, Panel} from '../../src'; 3 | 4 | export default class Basic extends Component { 5 | render() { 6 | return ( 7 | 8 | 9 | Tab1 10 | Tab2 11 | Tab3 12 | 13 | 14 | 15 | Accusamus enim nisi itaque voluptas nesciunt repudiandae velit.
    16 | Ad molestiae magni quidem saepe et quia voluptatibus minima.
    17 | Omnis autem distinctio tempore. Qui omnis eum illum adipisci ab. 18 |
    19 | 20 | Officiis commodi facilis optio eum aliquam.
    21 | Tempore libero sit est architecto voluptate. Harum dolor modi deleniti animi qui similique facilis. Sit delectus voluptatem praesentium recusandae neque quo quod. 22 |
    23 | 24 | Ut voluptas a voluptas quo ut dolorum.
    25 | Dolorem sint velit explicabo sunt distinctio dolorem adipisci tempore.
    26 | Est repellat quis magnam quo nihil amet et. Iste consequatur architecto quam neque suscipit. 27 |
    28 |
    29 |
    30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /docs/components/Complicated.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Tabs, DragTabList, DragTab, PanelList, Panel, ExtraButton} from '../../src'; 3 | import Plus from 'react-icons/lib/fa/plus'; 4 | import {simpleSwitch} from '../../src/helpers/move'; 5 | import {makeData} from './utils'; 6 | 7 | export default class Complicated extends Component { 8 | constructor(props) { 9 | super(props); 10 | const tabs = makeData(100, 'Drag'); 11 | 12 | this.state = { 13 | tabs, 14 | activeIndex: 0, 15 | numberOfTabs: tabs.length, 16 | showExtra: true, 17 | showModal: true, 18 | showArrow: true 19 | }; 20 | } 21 | 22 | handleExtraButton = () => { 23 | const {tabs} = this.state; 24 | const newTabs = [...tabs, {title: 'New Draggable Tab', content: 'New Content'}]; 25 | this.setState({tabs: newTabs, activeIndex: newTabs.length - 1}); 26 | } 27 | 28 | handleTabChange = index => { 29 | this.setState({activeIndex: index}); 30 | } 31 | 32 | handleTabSequenceChange = ({oldIndex, newIndex}) => { 33 | const {tabs} = this.state; 34 | const updateTabs = simpleSwitch(tabs, oldIndex, newIndex); 35 | this.setState({tabs: updateTabs, activeIndex: newIndex}); 36 | } 37 | 38 | handleEdit = ({type, index}) => { 39 | this.setState((state) => { 40 | let {tabs, activeIndex} = state; 41 | if (type === 'delete') { 42 | tabs = [...tabs.slice(0, index), ...tabs.slice(index + 1)]; 43 | } 44 | if (index - 1 >= 0) { 45 | activeIndex = index - 1; 46 | } else { 47 | activeIndex = 0; 48 | } 49 | return {tabs, activeIndex}; 50 | }); 51 | } 52 | 53 | handleChangeTabsNumber = e => { 54 | let number = e.target.value; 55 | if (number <= 0 || !number) { 56 | number = 1; 57 | } 58 | if (number > 3000) { 59 | number = 3000; 60 | } 61 | const tabs = makeData(number, 'Drag'); 62 | this.setState({tabs, activeIndex: 0, numberOfTabs: number}); 63 | } 64 | 65 | handleToggleExtra = e => { 66 | const showExtra = e.target.checked; 67 | this.setState({showExtra}); 68 | } 69 | 70 | handleToggleModal = e => { 71 | const showModal = e.target.checked; 72 | this.setState({showModal}); 73 | } 74 | 75 | handleToggleArrow = e => { 76 | const showArrow = e.target.checked; 77 | this.setState({showArrow}); 78 | } 79 | 80 | render() { 81 | const {tabs, activeIndex, numberOfTabs, showArrow, showModal, showExtra} = this.state; 82 | const tabTemplate = []; 83 | const panelTemplate = []; 84 | tabs.forEach((tab, i) => { 85 | const closable = tabs.length > 1; 86 | tabTemplate.push({tab.title}); 87 | panelTemplate.push({tab.content}); 88 | }) 89 | 90 | return ( 91 |
    92 | 101 | 102 | 103 | }> 104 | 105 | {tabTemplate} 106 | 107 | 108 | {panelTemplate} 109 | 110 | 111 |
    112 |
    113 |
    114 | 115 | 118 |
    119 |
    120 | 121 | 122 |
    123 |
    124 | 125 | 126 |
    127 |
    128 | 129 | 130 |
    131 |
    132 |
    133 |
    134 | ) 135 | } 136 | } -------------------------------------------------------------------------------- /docs/components/Draggable.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Tabs, DragTabList, DragTab, PanelList, Panel} from '../../src'; 3 | import {simpleSwitch} from '../../src/helpers/move'; 4 | import {makeData} from './utils'; 5 | 6 | export default class Drag extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.handleTabChange = this.handleTabChange.bind(this); 10 | this.handleTabSequenceChange = this.handleTabSequenceChange.bind(this); 11 | const tabs = makeData(3, 'DragTab'); 12 | this.state = { 13 | activeIndex: 0, 14 | tabs 15 | } 16 | } 17 | 18 | handleTabChange(index) { 19 | this.setState({activeIndex: index}); 20 | } 21 | 22 | handleTabSequenceChange({oldIndex, newIndex}) { 23 | const {tabs} = this.state; 24 | const updateTabs = simpleSwitch(tabs, oldIndex, newIndex); 25 | this.setState({tabs: updateTabs, activeIndex: newIndex}); 26 | } 27 | 28 | render() { 29 | const {tabs, activeIndex} = this.state; 30 | const tabsTemplate = []; 31 | const panelTemplate = []; 32 | tabs.forEach((tab, index) => { 33 | tabsTemplate.push({tab.title}) 34 | panelTemplate.push({tab.content}) 35 | }) 36 | return ( 37 | 41 | 42 | {tabsTemplate} 43 | 44 | 45 | {panelTemplate} 46 | 47 | 48 | ) 49 | } 50 | } -------------------------------------------------------------------------------- /docs/components/IconTab.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Tabs, TabList, Tab, PanelList, Panel} from '../../src'; 3 | 4 | export default class Basic extends Component { 5 | render() { 6 | return ( 7 | 8 | 9 | High! 10 | YoYo 11 | 12 | 13 | 14 | 15 | Accusamus enim nisi itaque voluptas nesciunt repudiandae velit.
    16 | Ad molestiae magni quidem saepe et quia voluptatibus minima.
    17 | Omnis autem distinctio tempore. Qui omnis eum illum adipisci ab. 18 |
    19 | 20 | Officiis commodi facilis optio eum aliquam.
    21 | Tempore libero sit est architecto voluptate. Harum dolor modi deleniti animi qui similique facilis. Sit delectus voluptatem praesentium recusandae neque quo quod. 22 |
    23 | 24 | Ut voluptas a voluptas quo ut dolorum.
    25 | Dolorem sint velit explicabo sunt distinctio dolorem adipisci tempore.
    26 | Est repellat quis magnam quo nihil amet et. Iste consequatur architecto quam neque suscipit. 27 |
    28 |
    29 |
    30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /docs/components/Modal.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Tabs, TabList, Tab, PanelList, Panel} from '../../src'; 3 | import {makeData} from './utils'; 4 | 5 | export default class Modal extends Component { 6 | constructor(props) { 7 | super(props); 8 | const tabs = makeData(50); 9 | this.state = { 10 | tabs 11 | } 12 | } 13 | 14 | render() { 15 | const tabTemplate = []; 16 | const panelTemplate = []; 17 | this.state.tabs.forEach((tab, i) => { 18 | tabTemplate.push({tab.title}); 19 | panelTemplate.push({tab.content}); 20 | }) 21 | return ( 22 | 23 | 24 | {tabTemplate} 25 | 26 | 27 | {panelTemplate} 28 | 29 | 30 | ) 31 | } 32 | } -------------------------------------------------------------------------------- /docs/components/utils.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const makeData = (number, titlePrefix = 'Tab') => { 3 | const data = []; 4 | for (let i = 0; i < number; i++) { 5 | data.push({ 6 | title: `${titlePrefix} ${i}`, 7 | content: 8 |
    9 | Content {i}: Accusamus enim nisi itaque voluptas nesciunt repudiandae velit.
    10 | Ad molestiae magni quidem saepe et quia voluptatibus minima.
    11 | Omnis autem distinctio tempore. Qui omnis eum illum adipisci ab.
    12 |
    13 | }); 14 | } 15 | return data; 16 | } 17 | 18 | export {makeData}; -------------------------------------------------------------------------------- /docs/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
    15 | <% if (htmlWebpackPlugin.options.production) { %> 16 | 17 | 18 | <% } %> 19 | 20 | <% for (key in htmlWebpackPlugin.files.chunks) { %> 21 | <% if (htmlWebpackPlugin.files.jsIntegrity) { %> 22 | 27 | <% } else { %> 28 | 29 | <% } %> 30 | <% } %> 31 | 32 | <% if (htmlWebpackPlugin.options.googleAnalytics) { %> 33 | 42 | 43 | <% } %> 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctxhou/react-tabtab/02a9cf4c72cda89e00a18641fc08e8e023d504cd/docs/logo.png -------------------------------------------------------------------------------- /docs/root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import styled from 'styled-components'; 4 | import Select from 'react-select'; 5 | import Basic from './components/Basic'; 6 | import IconTab from './components/IconTab'; 7 | import Draggable from './components/Draggable'; 8 | import AddAndClose from './components/AddAndClose'; 9 | import Modal from './components/Modal'; 10 | import Complicated from './components/Complicated'; 11 | import Async from './components/Async'; 12 | import themes from './themes'; 13 | 14 | const themeOptions = Object.keys(themes).map(theme => { 15 | return {value: theme, label: themes[theme].name}; 16 | }); 17 | 18 | const Header = styled.div` 19 | text-align: center; 20 | `; 21 | 22 | const SelectWrapper = styled.div` 23 | display: inline-block; 24 | width: 300px; 25 | `; 26 | 27 | const Logo = styled.img` 28 | margin-top: 40px; 29 | max-height: 100px; 30 | `; 31 | 32 | const examples = [ 33 | { 34 | title: 'Basic Tabs', 35 | anchor: 'basic', 36 | description: null, 37 | Component: Basic, 38 | source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/Basic.js' 39 | }, 40 | { 41 | title: 'Icon Tabs', 42 | anchor: 'icon', 43 | description: 'Tab content is customizable. Icons, images, any content is available.', 44 | Component: IconTab, 45 | source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/IconTab.js' 46 | }, 47 | { 48 | title: 'Draggable Tabs', 49 | anchor: 'draggable', 50 | description: 'Try to drag and drop the tab.', 51 | Component: Draggable, 52 | source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/Draggable.js' 53 | }, 54 | { 55 | title: 'Add and Close Tabs', 56 | anchor: 'add-close', 57 | description: null, 58 | Component: AddAndClose, 59 | source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/AddAndClose.js' 60 | }, 61 | { 62 | title: 'Async tab', 63 | anchor: 'async', 64 | description: 'Lazy load the tab content', 65 | Component: Async, 66 | source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/Async.js' 67 | }, 68 | { 69 | title: 'Modal View', 70 | anchor: 'modal', 71 | description: 'With modal view, it\'s easier to select tab when there are lots of tabs and on mobile device', 72 | Component: Modal, 73 | source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/Modal.js' 74 | } 75 | ] 76 | 77 | export default class Root extends React.Component { 78 | constructor(props) { 79 | super(props); 80 | this.handleThemeChange = this.handleThemeChange.bind(this); 81 | this.state = { 82 | currentTheme: 'bootstrap' 83 | } 84 | } 85 | 86 | handleThemeChange(opt) { 87 | this.setState({currentTheme: opt.value}); 88 | } 89 | 90 | render() { 91 | const {currentTheme} = this.state; 92 | 93 | return ( 94 |
    95 |
    96 | 97 |

    98 | A mobile support, draggable, editable and api based Tab for ReactJS. 99 |

    100 | Star 101 | 110 |
    111 |
    112 |
    113 |
    Select a theme
    114 | 115 |