├── .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 | [](https://www.npmjs.com/package/react-tabtab/)
8 | [](https://travis-ci.org/ctxhou/react-tabtab)
9 | [](https://ci.appveyor.com/project/ctxhou/react-tabtab)
10 | 
11 | [](https://codecov.io/gh/ctxhou/react-tabtab)
12 | 
13 | 
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 | 
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 | props
305 | type
306 | default
307 |
308 |
309 |
310 | defaultIndex
311 | int
312 | null
313 | set the initial active key
314 |
315 |
316 | activeIndex
317 | int
318 | null
319 | control current activeIndex. You need to pass new activeIndex value if you want to show different tab.
320 |
321 |
322 | defaultIndex
323 | int
324 | null
325 | set the initial active key
326 |
327 |
328 | showModalButton
329 | boolean
number
330 | 4
331 |
332 |
333 | true : always show button
334 | false : always hide button
335 | [number] : when number of tab >= [number]
, show button
336 |
337 |
338 |
339 |
340 | showArrowButton
341 | auto
boolean
342 | auto
343 |
344 | auto : detect tab width, if they exceed container, show button
345 | true : always show button
346 | false : always hide button
347 |
348 |
349 |
350 | ExtraButton
351 | React Node
352 | null
353 |
354 | customize extra button content, example: `+` button
355 |
356 |
357 |
358 | onTabChange
359 | () => tabIndex
360 | null
361 |
362 | return tabIndex is clicked
363 | You can use this api with activeIndex
. When user click tab, update activeIndex
.
364 |
365 |
366 |
367 | onTabSequenceChange
368 | () => {oldIndex, newIndex}
369 | null
370 |
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 |
375 |
376 |
377 | onTabEdit
378 | () => {type: [delete], index}
379 | null
380 |
381 | When user click close button , this api will return the clicked close button index.
382 |
383 |
384 |
385 | customStyle
386 |
387 |
388 | {
389 | TabList: React.Element,
390 | Tab: React.Element,
391 | Panel: React.Element,
392 | ActionButton: React.Element
393 | }
394 |
395 | Bootstrap theme
396 |
397 | customized tab style component
398 |
399 |
400 |
401 |
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 | props
415 | type
416 | default
417 |
418 |
419 |
420 | closable
421 | boolean
422 | false
423 | whether to show close button
424 |
425 |
426 |
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 | props
461 | type
462 | default
463 |
464 |
465 |
466 | loadContent *
467 |
468 | (cb) => cb(error, data)
or
469 | (cb) => Promise
470 |
471 | null
472 | when loadContent finish, call the callback or you can return promise
473 |
474 |
475 | render *
476 |
477 | (data) => Component
478 |
479 | null
480 | when finish loading data, render this component
481 |
482 |
483 | renderLoading *
484 |
485 | () => Component
486 |
487 | null
488 | when it is loading data, render this component
489 |
490 |
491 | cache
492 |
493 | boolean
494 |
495 | true
496 | should cache the data
497 |
498 |
499 |
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 |
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 |
111 |
112 |
113 |
Select a theme
114 |
115 |
123 |
124 |
125 | {examples.map(item => (
126 |
127 |
128 | {item.title}
129 |
130 | {item.description ?
131 |
{item.description}
132 | : null}
133 |
134 | View Source
135 |
136 | ))}
137 |
138 |
139 |
Now, let's mix all feature together. Demo a complicated example!
140 |
Try to
141 |
142 | Drag and drop the tab
143 | Add new tab
144 | Close tab
145 | And last, do above actions on MODAL view!
146 |
147 |
148 |
152 |
153 |
Try it!
154 |
155 | Reference the document to use it.
156 |
157 |
164 |
165 |
166 |
167 | Maintained by @ctxhou
168 |
176 |
177 |
178 | )
179 | }
180 | }
181 |
182 | ReactDOM.render(
183 | ,
184 | document.getElementById('root')
185 | );
186 |
187 | if (module.hot) {
188 | module.hot.accept();
189 | }
190 |
--------------------------------------------------------------------------------
/docs/standalone.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React-tabtab umd example
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
60 |
--------------------------------------------------------------------------------
/docs/themes.js:
--------------------------------------------------------------------------------
1 | import * as bootstrap from 'react-tabtab/lib/themes/bootstrap';
2 | import * as md from 'react-tabtab/lib/themes/material-design';
3 | import * as bulma from 'react-tabtab/lib/themes/bulma';
4 |
5 | export default {
6 | bootstrap: {
7 | name: 'Bootstrap',
8 | style: bootstrap
9 | },
10 | md: {
11 | name: 'Material Design',
12 | style: md
13 | },
14 | bulma: {
15 | name: 'Bulma',
16 | style: bulma
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/flow-typed/npm/styled-components_v2.x.x.js:
--------------------------------------------------------------------------------
1 | // flow-typed signature: 2d5d2167b399d10e16ddc719ea6fc62e
2 | // flow-typed version: 1be5dad600/styled-components_v2.x.x/flow_>=v0.53.x
3 |
4 | // @flow
5 |
6 | type $npm$styledComponents$Interpolation = ((executionContext: C) => string) | string | number;
7 | type $npm$styledComponents$NameGenerator = (hash: number) => string;
8 |
9 | type $npm$styledComponents$TaggedTemplateLiteral = {| (Array, $npm$styledComponents$Interpolation): R |};
10 |
11 | // ---- FUNCTIONAL COMPONENT DEFINITIONS ----
12 | type $npm$styledComponents$ReactComponentFunctional =
13 | & { defaultProps: DefaultProps }
14 | & $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps
15 |
16 | type $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps =
17 | React$StatelessFunctionalComponent
18 |
19 | // ---- CLASS COMPONENT DEFINITIONS ----
20 | class $npm$styledComponents$ReactComponent extends React$Component {
21 | static defaultProps: DefaultProps
22 | }
23 | type $npm$styledComponents$ReactComponentClass = Class<$npm$styledComponents$ReactComponent>
24 | type $npm$styledComponents$ReactComponentClassUndefinedDefaultProps = Class>
25 |
26 | // ---- COMPONENT FUNCTIONS INPUT (UNION) & OUTPUT (INTERSECTION) ----
27 | type $npm$styledComponents$ReactComponentUnion =
28 | $npm$styledComponents$ReactComponentUnionWithDefaultProps
29 |
30 | type $npm$styledComponents$ReactComponentUnionWithDefaultProps =
31 | | $npm$styledComponents$ReactComponentFunctional
32 | | $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps
33 | | $npm$styledComponents$ReactComponentClass
34 | | $npm$styledComponents$ReactComponentClassUndefinedDefaultProps
35 |
36 | type $npm$styledComponents$ReactComponentIntersection =
37 | & $npm$styledComponents$ReactComponentFunctional
38 | & $npm$styledComponents$ReactComponentClass;
39 |
40 | // ---- WITHCOMPONENT ----
41 | type $npm$styledComponents$ReactComponentStyledWithComponent = <
42 | Props, DefaultProps,
43 | Input:
44 | | ComponentList
45 | | $npm$styledComponents$ReactComponentStyled
46 | | $npm$styledComponents$ReactComponentUnionWithDefaultProps
47 | >(Input) => $npm$styledComponents$ReactComponentStyled
48 |
49 | // ---- STATIC PROPERTIES ----
50 | type $npm$styledComponents$ReactComponentStyledStaticProps = {|
51 | attrs: (AdditionalProps) => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral,
52 | extend: $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral,
53 | |}
54 |
55 | type $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent = {|
56 | withComponent: $npm$styledComponents$ReactComponentStyledWithComponent,
57 | attrs: (AdditionalProps) => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent,
58 | extend: $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent,
59 | |}
60 |
61 | // ---- STYLED FUNCTION ----
62 | // Error: styled(CustomComponent).withComponent('a')
63 | // Ok: styled('div').withComponent('a')
64 | type $npm$styledComponents$Call =
65 | & (ComponentListKeys => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent<{}, ComponentListKeys>)
66 | & (($npm$styledComponents$ReactComponentUnion) => $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral)
67 |
68 | // ---- STYLED COMPONENT ----
69 | type $npm$styledComponents$ReactComponentStyled =
70 | & $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent
71 | & $npm$styledComponents$ReactComponentIntersection
72 |
73 | // ---- TAGGED TEMPLATE LITERAL ----
74 | type $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteral =
75 | & $npm$styledComponents$ReactComponentStyledStaticProps
76 | & $npm$styledComponents$TaggedTemplateLiteral<$npm$styledComponents$ReactComponentStyled>
77 |
78 | type $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent =
79 | & $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent
80 | & $npm$styledComponents$TaggedTemplateLiteral<$npm$styledComponents$ReactComponentStyled>
81 |
82 | // ---- WITHTHEME ----
83 | type $npm$styledComponents$WithThemeReactComponentClass = <
84 | InputProps: { theme: $npm$styledComponents$Theme },
85 | InputDefaultProps: {},
86 | OutputProps: $Diff,
87 | OutputDefaultProps: InputDefaultProps & { theme: $npm$styledComponents$Theme },
88 | >($npm$styledComponents$ReactComponentClass) => $npm$styledComponents$ReactComponentClass
89 |
90 | type $npm$styledComponents$WithThemeReactComponentClassUndefinedDefaultProps = <
91 | InputProps: { theme: $npm$styledComponents$Theme },
92 | OutputProps: $Diff,
93 | >($npm$styledComponents$ReactComponentClassUndefinedDefaultProps) => $npm$styledComponents$ReactComponentClass
94 |
95 | type $npm$styledComponents$WithThemeReactComponentFunctional = <
96 | InputProps: { theme: $npm$styledComponents$Theme },
97 | InputDefaultProps: {},
98 | OutputProps: $Diff,
99 | OutputDefaultProps: InputDefaultProps & { theme: $npm$styledComponents$Theme },
100 | >($npm$styledComponents$ReactComponentFunctional) => $npm$styledComponents$ReactComponentFunctional
101 |
102 | type $npm$styledComponents$WithThemeReactComponentFunctionalUndefinedDefaultProps = <
103 | InputProps: { theme: $npm$styledComponents$Theme },
104 | OutputProps: $Diff
105 | >($npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps) => $npm$styledComponents$ReactComponentFunctional
106 |
107 | type $npm$styledComponents$WithTheme =
108 | & $npm$styledComponents$WithThemeReactComponentClass
109 | & $npm$styledComponents$WithThemeReactComponentClassUndefinedDefaultProps
110 | & $npm$styledComponents$WithThemeReactComponentFunctional
111 | & $npm$styledComponents$WithThemeReactComponentFunctionalUndefinedDefaultProps
112 |
113 | // ---- MISC ----
114 | type $npm$styledComponents$Theme = {[key: string]: mixed};
115 | type $npm$styledComponents$ThemeProviderProps = {
116 | theme: $npm$styledComponents$Theme | ((outerTheme: $npm$styledComponents$Theme) => void)
117 | };
118 |
119 | class Npm$StyledComponents$ThemeProvider extends React$Component<$npm$styledComponents$ThemeProviderProps> {}
120 |
121 | class Npm$StyledComponents$StyleSheetManager extends React$Component<{ sheet: mixed }> {}
122 |
123 | class Npm$StyledComponents$ServerStyleSheet {
124 | instance: StyleSheet
125 | collectStyles: (children: any) => React$Node
126 | getStyleTags: () => string
127 | getStyleElement: () => React$Node
128 | }
129 |
130 | type $npm$styledComponents$StyledComponentsComponentListKeys =
131 | $Subtype<$Keys<$npm$styledComponents$StyledComponentsComponentList>>
132 |
133 | type $npm$styledComponents$StyledComponentsComponentListValue =
134 | $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent<{}, $npm$styledComponents$StyledComponentsComponentListKeys>
135 |
136 | // ---- COMPONENT LIST ----
137 | type $npm$styledComponents$StyledComponentsComponentList = {|
138 | a: $npm$styledComponents$StyledComponentsComponentListValue,
139 | abbr: $npm$styledComponents$StyledComponentsComponentListValue,
140 | address: $npm$styledComponents$StyledComponentsComponentListValue,
141 | area: $npm$styledComponents$StyledComponentsComponentListValue,
142 | article: $npm$styledComponents$StyledComponentsComponentListValue,
143 | aside: $npm$styledComponents$StyledComponentsComponentListValue,
144 | audio: $npm$styledComponents$StyledComponentsComponentListValue,
145 | b: $npm$styledComponents$StyledComponentsComponentListValue,
146 | base: $npm$styledComponents$StyledComponentsComponentListValue,
147 | bdi: $npm$styledComponents$StyledComponentsComponentListValue,
148 | bdo: $npm$styledComponents$StyledComponentsComponentListValue,
149 | big: $npm$styledComponents$StyledComponentsComponentListValue,
150 | blockquote: $npm$styledComponents$StyledComponentsComponentListValue,
151 | body: $npm$styledComponents$StyledComponentsComponentListValue,
152 | br: $npm$styledComponents$StyledComponentsComponentListValue,
153 | button: $npm$styledComponents$StyledComponentsComponentListValue,
154 | canvas: $npm$styledComponents$StyledComponentsComponentListValue,
155 | caption: $npm$styledComponents$StyledComponentsComponentListValue,
156 | cite: $npm$styledComponents$StyledComponentsComponentListValue,
157 | code: $npm$styledComponents$StyledComponentsComponentListValue,
158 | col: $npm$styledComponents$StyledComponentsComponentListValue,
159 | colgroup: $npm$styledComponents$StyledComponentsComponentListValue,
160 | data: $npm$styledComponents$StyledComponentsComponentListValue,
161 | datalist: $npm$styledComponents$StyledComponentsComponentListValue,
162 | dd: $npm$styledComponents$StyledComponentsComponentListValue,
163 | del: $npm$styledComponents$StyledComponentsComponentListValue,
164 | details: $npm$styledComponents$StyledComponentsComponentListValue,
165 | dfn: $npm$styledComponents$StyledComponentsComponentListValue,
166 | dialog: $npm$styledComponents$StyledComponentsComponentListValue,
167 | div: $npm$styledComponents$StyledComponentsComponentListValue,
168 | dl: $npm$styledComponents$StyledComponentsComponentListValue,
169 | dt: $npm$styledComponents$StyledComponentsComponentListValue,
170 | em: $npm$styledComponents$StyledComponentsComponentListValue,
171 | embed: $npm$styledComponents$StyledComponentsComponentListValue,
172 | fieldset: $npm$styledComponents$StyledComponentsComponentListValue,
173 | figcaption: $npm$styledComponents$StyledComponentsComponentListValue,
174 | figure: $npm$styledComponents$StyledComponentsComponentListValue,
175 | footer: $npm$styledComponents$StyledComponentsComponentListValue,
176 | form: $npm$styledComponents$StyledComponentsComponentListValue,
177 | h1: $npm$styledComponents$StyledComponentsComponentListValue,
178 | h2: $npm$styledComponents$StyledComponentsComponentListValue,
179 | h3: $npm$styledComponents$StyledComponentsComponentListValue,
180 | h4: $npm$styledComponents$StyledComponentsComponentListValue,
181 | h5: $npm$styledComponents$StyledComponentsComponentListValue,
182 | h6: $npm$styledComponents$StyledComponentsComponentListValue,
183 | head: $npm$styledComponents$StyledComponentsComponentListValue,
184 | header: $npm$styledComponents$StyledComponentsComponentListValue,
185 | hgroup: $npm$styledComponents$StyledComponentsComponentListValue,
186 | hr: $npm$styledComponents$StyledComponentsComponentListValue,
187 | html: $npm$styledComponents$StyledComponentsComponentListValue,
188 | i: $npm$styledComponents$StyledComponentsComponentListValue,
189 | iframe: $npm$styledComponents$StyledComponentsComponentListValue,
190 | img: $npm$styledComponents$StyledComponentsComponentListValue,
191 | input: $npm$styledComponents$StyledComponentsComponentListValue,
192 | ins: $npm$styledComponents$StyledComponentsComponentListValue,
193 | kbd: $npm$styledComponents$StyledComponentsComponentListValue,
194 | keygen: $npm$styledComponents$StyledComponentsComponentListValue,
195 | label: $npm$styledComponents$StyledComponentsComponentListValue,
196 | legend: $npm$styledComponents$StyledComponentsComponentListValue,
197 | li: $npm$styledComponents$StyledComponentsComponentListValue,
198 | link: $npm$styledComponents$StyledComponentsComponentListValue,
199 | main: $npm$styledComponents$StyledComponentsComponentListValue,
200 | map: $npm$styledComponents$StyledComponentsComponentListValue,
201 | mark: $npm$styledComponents$StyledComponentsComponentListValue,
202 | menu: $npm$styledComponents$StyledComponentsComponentListValue,
203 | menuitem: $npm$styledComponents$StyledComponentsComponentListValue,
204 | meta: $npm$styledComponents$StyledComponentsComponentListValue,
205 | meter: $npm$styledComponents$StyledComponentsComponentListValue,
206 | nav: $npm$styledComponents$StyledComponentsComponentListValue,
207 | noscript: $npm$styledComponents$StyledComponentsComponentListValue,
208 | object: $npm$styledComponents$StyledComponentsComponentListValue,
209 | ol: $npm$styledComponents$StyledComponentsComponentListValue,
210 | optgroup: $npm$styledComponents$StyledComponentsComponentListValue,
211 | option: $npm$styledComponents$StyledComponentsComponentListValue,
212 | output: $npm$styledComponents$StyledComponentsComponentListValue,
213 | p: $npm$styledComponents$StyledComponentsComponentListValue,
214 | param: $npm$styledComponents$StyledComponentsComponentListValue,
215 | picture: $npm$styledComponents$StyledComponentsComponentListValue,
216 | pre: $npm$styledComponents$StyledComponentsComponentListValue,
217 | progress: $npm$styledComponents$StyledComponentsComponentListValue,
218 | q: $npm$styledComponents$StyledComponentsComponentListValue,
219 | rp: $npm$styledComponents$StyledComponentsComponentListValue,
220 | rt: $npm$styledComponents$StyledComponentsComponentListValue,
221 | ruby: $npm$styledComponents$StyledComponentsComponentListValue,
222 | s: $npm$styledComponents$StyledComponentsComponentListValue,
223 | samp: $npm$styledComponents$StyledComponentsComponentListValue,
224 | script: $npm$styledComponents$StyledComponentsComponentListValue,
225 | section: $npm$styledComponents$StyledComponentsComponentListValue,
226 | select: $npm$styledComponents$StyledComponentsComponentListValue,
227 | small: $npm$styledComponents$StyledComponentsComponentListValue,
228 | source: $npm$styledComponents$StyledComponentsComponentListValue,
229 | span: $npm$styledComponents$StyledComponentsComponentListValue,
230 | strong: $npm$styledComponents$StyledComponentsComponentListValue,
231 | style: $npm$styledComponents$StyledComponentsComponentListValue,
232 | sub: $npm$styledComponents$StyledComponentsComponentListValue,
233 | summary: $npm$styledComponents$StyledComponentsComponentListValue,
234 | sup: $npm$styledComponents$StyledComponentsComponentListValue,
235 | table: $npm$styledComponents$StyledComponentsComponentListValue,
236 | tbody: $npm$styledComponents$StyledComponentsComponentListValue,
237 | td: $npm$styledComponents$StyledComponentsComponentListValue,
238 | textarea: $npm$styledComponents$StyledComponentsComponentListValue,
239 | tfoot: $npm$styledComponents$StyledComponentsComponentListValue,
240 | th: $npm$styledComponents$StyledComponentsComponentListValue,
241 | thead: $npm$styledComponents$StyledComponentsComponentListValue,
242 | time: $npm$styledComponents$StyledComponentsComponentListValue,
243 | title: $npm$styledComponents$StyledComponentsComponentListValue,
244 | tr: $npm$styledComponents$StyledComponentsComponentListValue,
245 | track: $npm$styledComponents$StyledComponentsComponentListValue,
246 | u: $npm$styledComponents$StyledComponentsComponentListValue,
247 | ul: $npm$styledComponents$StyledComponentsComponentListValue,
248 | var: $npm$styledComponents$StyledComponentsComponentListValue,
249 | video: $npm$styledComponents$StyledComponentsComponentListValue,
250 | wbr: $npm$styledComponents$StyledComponentsComponentListValue,
251 |
252 | // SVG
253 | circle: $npm$styledComponents$StyledComponentsComponentListValue,
254 | clipPath: $npm$styledComponents$StyledComponentsComponentListValue,
255 | defs: $npm$styledComponents$StyledComponentsComponentListValue,
256 | ellipse: $npm$styledComponents$StyledComponentsComponentListValue,
257 | g: $npm$styledComponents$StyledComponentsComponentListValue,
258 | image: $npm$styledComponents$StyledComponentsComponentListValue,
259 | line: $npm$styledComponents$StyledComponentsComponentListValue,
260 | linearGradient: $npm$styledComponents$StyledComponentsComponentListValue,
261 | mask: $npm$styledComponents$StyledComponentsComponentListValue,
262 | path: $npm$styledComponents$StyledComponentsComponentListValue,
263 | pattern: $npm$styledComponents$StyledComponentsComponentListValue,
264 | polygon: $npm$styledComponents$StyledComponentsComponentListValue,
265 | polyline: $npm$styledComponents$StyledComponentsComponentListValue,
266 | radialGradient: $npm$styledComponents$StyledComponentsComponentListValue,
267 | rect: $npm$styledComponents$StyledComponentsComponentListValue,
268 | stop: $npm$styledComponents$StyledComponentsComponentListValue,
269 | svg: $npm$styledComponents$StyledComponentsComponentListValue,
270 | text: $npm$styledComponents$StyledComponentsComponentListValue,
271 | tspan: $npm$styledComponents$StyledComponentsComponentListValue,
272 | |}
273 |
274 | declare module 'styled-components' {
275 | declare type Interpolation = $npm$styledComponents$Interpolation;
276 | declare type NameGenerator = $npm$styledComponents$NameGenerator;
277 | declare type Theme = $npm$styledComponents$Theme;
278 | declare type ThemeProviderProps = $npm$styledComponents$ThemeProviderProps;
279 | declare type TaggedTemplateLiteral = $npm$styledComponents$TaggedTemplateLiteral;
280 | declare type ComponentListKeys = $npm$styledComponents$StyledComponentsComponentListKeys;
281 |
282 | declare type ReactComponentFunctional = $npm$styledComponents$ReactComponentFunctional;
283 | declare type ReactComponentFunctionalUndefinedDefaultProps = $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps;
284 | declare type ReactComponentClass = $npm$styledComponents$ReactComponentClass;
285 | declare type ReactComponentClassUndefinedDefaultProps = $npm$styledComponents$ReactComponentClassUndefinedDefaultProps;
286 | declare type ReactComponentUnion = $npm$styledComponents$ReactComponentUnion;
287 | declare type ReactComponentIntersection = $npm$styledComponents$ReactComponentIntersection;
288 | declare type ReactComponentStyledStaticProps = $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent;
289 | declare type ReactComponentStyled = $npm$styledComponents$ReactComponentStyled;
290 | declare type ReactComponentStyledTaggedTemplateLiteral = $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent;
291 |
292 | declare module.exports: {
293 | $call: $npm$styledComponents$Call,
294 |
295 | injectGlobal: TaggedTemplateLiteral,
296 | css: TaggedTemplateLiteral>,
297 | keyframes: TaggedTemplateLiteral,
298 | withTheme: $npm$styledComponents$WithTheme,
299 | ServerStyleSheet: typeof Npm$StyledComponents$ServerStyleSheet,
300 | StyleSheetManager: typeof Npm$StyledComponents$StyleSheetManager,
301 | ThemeProvider: typeof Npm$StyledComponents$ThemeProvider,
302 |
303 | ...$npm$styledComponents$StyledComponentsComponentList,
304 | };
305 | }
306 |
307 | type $npm$styledComponents$StyledComponentsNativeComponentListKeys =
308 | $Subtype<$Keys<$npm$styledComponents$StyledComponentsNativeComponentList>>
309 |
310 | type $npm$styledComponents$StyledComponentsNativeComponentListValue =
311 | $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent<{}, $npm$styledComponents$StyledComponentsNativeComponentListKeys>
312 |
313 | type $npm$styledComponents$StyledComponentsNativeComponentList = {|
314 | ActivityIndicator: $npm$styledComponents$StyledComponentsNativeComponentListValue,
315 | ActivityIndicatorIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
316 | ART: $npm$styledComponents$StyledComponentsNativeComponentListValue,
317 | Button: $npm$styledComponents$StyledComponentsNativeComponentListValue,
318 | DatePickerIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
319 | DrawerLayoutAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue,
320 | FlatList: $npm$styledComponents$StyledComponentsNativeComponentListValue,
321 | Image: $npm$styledComponents$StyledComponentsNativeComponentListValue,
322 | ImageEditor: $npm$styledComponents$StyledComponentsNativeComponentListValue,
323 | ImageStore: $npm$styledComponents$StyledComponentsNativeComponentListValue,
324 | KeyboardAvoidingView: $npm$styledComponents$StyledComponentsNativeComponentListValue,
325 | ListView: $npm$styledComponents$StyledComponentsNativeComponentListValue,
326 | MapView: $npm$styledComponents$StyledComponentsNativeComponentListValue,
327 | Modal: $npm$styledComponents$StyledComponentsNativeComponentListValue,
328 | Navigator: $npm$styledComponents$StyledComponentsNativeComponentListValue,
329 | NavigatorIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
330 | Picker: $npm$styledComponents$StyledComponentsNativeComponentListValue,
331 | PickerIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
332 | ProgressBarAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue,
333 | ProgressViewIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
334 | RecyclerViewBackedScrollView: $npm$styledComponents$StyledComponentsNativeComponentListValue,
335 | RefreshControl: $npm$styledComponents$StyledComponentsNativeComponentListValue,
336 | ScrollView: $npm$styledComponents$StyledComponentsNativeComponentListValue,
337 | SectionList: $npm$styledComponents$StyledComponentsNativeComponentListValue,
338 | SegmentedControlIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
339 | Slider: $npm$styledComponents$StyledComponentsNativeComponentListValue,
340 | SliderIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
341 | SnapshotViewIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
342 | StatusBar: $npm$styledComponents$StyledComponentsNativeComponentListValue,
343 | SwipeableListView: $npm$styledComponents$StyledComponentsNativeComponentListValue,
344 | Switch: $npm$styledComponents$StyledComponentsNativeComponentListValue,
345 | SwitchAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue,
346 | SwitchIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
347 | TabBarIOS: $npm$styledComponents$StyledComponentsNativeComponentListValue,
348 | Text: $npm$styledComponents$StyledComponentsNativeComponentListValue,
349 | TextInput: $npm$styledComponents$StyledComponentsNativeComponentListValue,
350 | ToastAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue,
351 | ToolbarAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue,
352 | Touchable: $npm$styledComponents$StyledComponentsNativeComponentListValue,
353 | TouchableHighlight: $npm$styledComponents$StyledComponentsNativeComponentListValue,
354 | TouchableNativeFeedback: $npm$styledComponents$StyledComponentsNativeComponentListValue,
355 | TouchableOpacity: $npm$styledComponents$StyledComponentsNativeComponentListValue,
356 | TouchableWithoutFeedback: $npm$styledComponents$StyledComponentsNativeComponentListValue,
357 | View: $npm$styledComponents$StyledComponentsNativeComponentListValue,
358 | ViewPagerAndroid: $npm$styledComponents$StyledComponentsNativeComponentListValue,
359 | VirtualizedList: $npm$styledComponents$StyledComponentsNativeComponentListValue,
360 | WebView: $npm$styledComponents$StyledComponentsNativeComponentListValue,
361 | |}
362 |
363 | declare module 'styled-components/native' {
364 | declare type Interpolation = $npm$styledComponents$Interpolation;
365 | declare type NameGenerator = $npm$styledComponents$NameGenerator;
366 | declare type Theme = $npm$styledComponents$Theme;
367 | declare type ThemeProviderProps = $npm$styledComponents$ThemeProviderProps;
368 | declare type TaggedTemplateLiteral = $npm$styledComponents$TaggedTemplateLiteral;
369 | declare type NativeComponentListKeys = $npm$styledComponents$StyledComponentsNativeComponentListKeys;
370 |
371 | declare type ReactComponentFunctional = $npm$styledComponents$ReactComponentFunctional;
372 | declare type ReactComponentFunctionalUndefinedDefaultProps = $npm$styledComponents$ReactComponentFunctionalUndefinedDefaultProps;
373 | declare type ReactComponentClass = $npm$styledComponents$ReactComponentClass;
374 | declare type ReactComponentClassUndefinedDefaultProps = $npm$styledComponents$ReactComponentClassUndefinedDefaultProps;
375 | declare type ReactComponentUnion = $npm$styledComponents$ReactComponentUnion;
376 | declare type ReactComponentIntersection = $npm$styledComponents$ReactComponentIntersection;
377 | declare type ReactComponentStyledStaticProps = $npm$styledComponents$ReactComponentStyledStaticPropsWithComponent;
378 | declare type ReactComponentStyled = $npm$styledComponents$ReactComponentStyled;
379 | declare type ReactComponentStyledTaggedTemplateLiteral = $npm$styledComponents$ReactComponentStyledTaggedTemplateLiteralWithComponent;
380 |
381 | declare module.exports: {
382 | $call: $npm$styledComponents$Call,
383 |
384 | css: TaggedTemplateLiteral>,
385 | keyframes: TaggedTemplateLiteral,
386 | withTheme: $npm$styledComponents$WithTheme,
387 | ThemeProvider: typeof Npm$StyledComponents$ThemeProvider,
388 |
389 | ...$npm$styledComponents$StyledComponentsNativeComponentList,
390 | };
391 | }
392 |
--------------------------------------------------------------------------------
/flow-typed/react-icons.js:
--------------------------------------------------------------------------------
1 | declare module 'react-icons/lib/md/chevron-left' {
2 | declare module.exports: any;
3 | }
4 |
5 | declare module 'react-icons/lib/md/chevron-right' {
6 | declare module.exports: any;
7 | }
8 |
9 | declare module 'react-icons/lib/md/format-list-bulleted' {
10 | declare module.exports: any;
11 | }
12 |
13 | declare module 'react-icons/lib/md/clear' {
14 | declare module.exports: any;
15 | }
16 |
--------------------------------------------------------------------------------
/flow-typed/react-poppop.js:
--------------------------------------------------------------------------------
1 | declare module 'react-poppop' {
2 | declare module.exports: any;
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tabtab",
3 | "version": "2.0.0",
4 | "description": "A mobile support, draggable, editable and api based Tab for ReactJS",
5 | "main": "lib/",
6 | "scripts": {
7 | "start": "node devServer.js",
8 | "build": "npm run build:lib && npm run build:dist",
9 | "build:lib": "rimraf ./lib && cross-env BABEL_ENV=production babel src -d lib",
10 | "build:dist": "rimraf dist && rollup -c --environment ESBUNDLE && rollup -c --environment PRODUCTION",
11 | "lint": "eslint src example test",
12 | "test": "jest",
13 | "test:watch": "NODE_ENV=test npm test -- --watch",
14 | "flow": "flow check",
15 | "flow:watch": "flow-watch",
16 | "gh-pages": "rimraf _gh-pages && npm run gh-pages:build && npm run gh-pages:publish",
17 | "gh-pages:build": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js",
18 | "gh-pages:publish": "git-directory-deploy --directory _gh-pages --branch gh-pages",
19 | "prepublish": "npm run build",
20 | "validate": "npm ls"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/ctxhou/react-tabtab.git"
25 | },
26 | "keywords": [
27 | "react",
28 | "tabs",
29 | "react-tab",
30 | "react-tabtab",
31 | "react-component",
32 | "tab",
33 | "tabtab",
34 | "styled-components"
35 | ],
36 | "files": [
37 | "dist",
38 | "docs",
39 | "flow-typed",
40 | "lib",
41 | "src"
42 | ],
43 | "author": "ctxhou",
44 | "license": "MIT",
45 | "bugs": {
46 | "url": "https://github.com/ctxhou/react-tabtab/issues"
47 | },
48 | "homepage": "https://github.com/ctxhou/react-tabtab#readme",
49 | "dependencies": {
50 | "classnames": "^2.2.5",
51 | "invariant": "^2.2.2",
52 | "prop-types": "^15.6.0",
53 | "react-poppop": "^1.5.0",
54 | "react-sortable-hoc": "^0.6.8",
55 | "react-test-renderer": "^16.0.0"
56 | },
57 | "devDependencies": {
58 | "babel-cli": "^6.26.0",
59 | "babel-core": "^6.14.0",
60 | "babel-eslint": "^7.2.3",
61 | "babel-jest": "^20.0.3",
62 | "babel-loader": "^7.0.0",
63 | "babel-plugin-add-module-exports": "^0.2.1",
64 | "babel-plugin-dynamic-import-webpack": "^1.0.1",
65 | "babel-plugin-external-helpers": "^6.22.0",
66 | "babel-plugin-flow-react-proptypes": "^9.0.0",
67 | "babel-plugin-transform-class-properties": "^6.16.0",
68 | "babel-plugin-transform-flow-strip-types": "^6.22.0",
69 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
70 | "babel-plugin-transform-react-remove-prop-types": "^0.4.10",
71 | "babel-polyfill": "^6.26.0",
72 | "babel-preset-es2015": "^6.24.1",
73 | "babel-preset-flow": "^6.23.0",
74 | "babel-preset-react": "^6.24.1",
75 | "babel-preset-stage-0": "^6.24.1",
76 | "babel-runtime": "^6.11.6",
77 | "compression-webpack-plugin": "^1.0.1",
78 | "cross-env": "^5.1.1",
79 | "enzyme": "^3.7.0",
80 | "enzyme-adapter-react-15": "^1.0.5",
81 | "enzyme-adapter-react-16": "^1.13.1",
82 | "enzyme-to-json": "^3.2.2",
83 | "eslint": "^4.0.0",
84 | "eslint-config-google": "^0.4.0",
85 | "eslint-config-prettier": "^2.6.0",
86 | "eslint-plugin-flowtype": "^2.39.1",
87 | "eslint-plugin-flowtype-errors": "^3.3.6",
88 | "eslint-plugin-jest": "^21.2.0",
89 | "eslint-plugin-prettier": "^2.3.1",
90 | "eslint-plugin-react": "^7.5.1",
91 | "extract-text-webpack-plugin": "^3.0.1",
92 | "flow-bin": "^0.58.0",
93 | "flow-watch": "^1.1.4",
94 | "git-directory-deploy": "^1.5.1",
95 | "html-webpack-plugin": "^2.30.1",
96 | "husky": "^0.14.3",
97 | "jest": "^20.0.4",
98 | "jest-styled-components": "^6.3.0",
99 | "noop3": "^999.999.999",
100 | "precommit-hook-eslint": "^3.0.0",
101 | "prettier": "^1.7.4",
102 | "react": ">=16.3.0",
103 | "react-content-loader": "^3.1.1",
104 | "react-dom": ">=16.3.0",
105 | "react-icons": "^2.2.7",
106 | "react-select": "^1.0.0-rc.10",
107 | "rimraf": "^2.6.2",
108 | "rollup": "^0.52.1",
109 | "rollup-plugin-babel": "^3.0.1",
110 | "rollup-plugin-commonjs": "^8.2.6",
111 | "rollup-plugin-flow": "^1.1.1",
112 | "rollup-plugin-json": "^2.3.0",
113 | "rollup-plugin-node-resolve": "^3.0.0",
114 | "rollup-plugin-replace": "^2.0.0",
115 | "rollup-plugin-uglify": "^2.0.1",
116 | "rollup-plugin-visualizer": "^0.3.1",
117 | "styled-components": "^4.0.0",
118 | "webpack": "^3.3.0",
119 | "webpack-bundle-analyzer": "^3.3.2",
120 | "webpack-dev-middleware": "^1.12.0",
121 | "webpack-hot-middleware": "^2.18.2"
122 | },
123 | "peerDependencies": {
124 | "react": ">= 16.3.0 < 17.0.0-0",
125 | "styled-components": "^4.0.0"
126 | },
127 | "jest": {
128 | "setupFiles": [
129 | "./test/shim",
130 | "./test/enzyme-setup"
131 | ],
132 | "roots": [
133 | "/test/"
134 | ],
135 | "unmockedModulePathPatterns": [
136 | "node_modules/react/",
137 | "node_modules/enzyme/"
138 | ],
139 | "snapshotSerializers": [
140 | "enzyme-to-json/serializer"
141 | ]
142 | },
143 | "pre-commit": [
144 | "lint",
145 | "test"
146 | ]
147 | }
148 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | // rollup.config.js
2 | import resolve from 'rollup-plugin-node-resolve';
3 | import babel from 'rollup-plugin-babel';
4 | import flow from 'rollup-plugin-flow';
5 | import uglify from 'rollup-plugin-uglify';
6 | import json from 'rollup-plugin-json';
7 | import visualizer from 'rollup-plugin-visualizer';
8 | import replace from 'rollup-plugin-replace';
9 | import commonjs from 'rollup-plugin-commonjs'
10 |
11 | const path = 'dist/react-tabtab';
12 |
13 | const name = 'Tabtab';
14 | const globals = {
15 | 'prop-types': 'PropTypes',
16 | 'react-dom': 'ReactDOM',
17 | react: 'React',
18 | 'react-sortable-hoc': 'SortableHOC',
19 | 'classnames': 'classNames',
20 | 'styled-components': 'styled',
21 | 'react-poppop': 'PopPop'
22 | };
23 |
24 | const external = Object.keys(globals);
25 |
26 | const prod = process.env.PRODUCTION
27 | const esbundle = process.env.ESBUNDLE
28 |
29 | let output;
30 | if (prod) {
31 | console.log('Creating production UMD bundle...');
32 | output = {file: path + '.min.js', format: 'umd', name};
33 | } else if (esbundle) {
34 | console.log('Creating ES modules bundle...');
35 | output = {file: path + '.es.js', format: 'es'};
36 | }
37 |
38 | const plugins = [
39 | flow(),
40 | json(),
41 | resolve({
42 | browser: true
43 | }),
44 | commonjs({
45 | ignoreGlobal: true,
46 | exclude: 'src/**'
47 | }),
48 | babel({
49 | babelrc: false,
50 | presets: [
51 | ['es2015', { modules: false }],
52 | 'react'
53 | ],
54 | plugins: [
55 | 'transform-react-remove-prop-types',
56 | 'transform-flow-strip-types',
57 | 'transform-class-properties',
58 | 'transform-object-rest-spread',
59 | 'external-helpers'
60 | ]
61 | })
62 | ];
63 |
64 | if (prod) {
65 | plugins.push(
66 | uglify(),
67 | visualizer({filename: './bundle-stats.html'}),
68 | replace({
69 | 'process.env.NODE_ENV': JSON.stringify(prod ? 'production' : 'development'),
70 | })
71 | );
72 | }
73 | export default {
74 | input: 'src/index.js',
75 | name,
76 | external,
77 | exports: 'named',
78 | output,
79 | plugins,
80 | globals: globals,
81 | }
82 |
--------------------------------------------------------------------------------
/src/AsyncPanel.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react'
3 | import Panel from './Panel';
4 |
5 | type Props = {
6 | loadContent: (cb: Function) => void,
7 | render: (data: any) => void,
8 | renderLoading: () => void,
9 | CustomPanelStyle: () => void,
10 | active: boolean,
11 | index: number,
12 | cache: boolean
13 | };
14 |
15 | type State = {
16 | isLoading: boolean,
17 | data: any
18 | };
19 |
20 | export default class AsyncPanelComponent extends React.PureComponent {
21 | static defaultProps = {
22 | cache: true
23 | };
24 |
25 | cacheData: any;
26 |
27 | constructor(props: Props) {
28 | super(props);
29 | (this: any).loadPanel = this.loadPanel.bind(this);
30 | (this: any).cacheData = undefined;
31 | this.state = {
32 | isLoading: false,
33 | data: undefined
34 | };
35 | }
36 |
37 | componentDidMount() {
38 | if (this.props.active)
39 | this.loadPanel();
40 | }
41 |
42 | componentWillReceiveProps(nextProps: Props) {
43 | if (nextProps.active)
44 | this.loadPanel();
45 | }
46 |
47 | loadPanel() {
48 | const {loadContent, cache} = this.props;
49 | if (cache && this.cacheData) {
50 | this.setState({
51 | isLoading: false,
52 | data: this.cacheData
53 | });
54 | return;
55 | }
56 | const callback = (err, data) => {
57 | if (err) {
58 | console.log('React-Tabtab async panel error:', err);
59 | }
60 | if (cache) {
61 | this.cacheData = data;
62 | }
63 | this.setState({
64 | isLoading: false,
65 | data
66 | });
67 | }
68 | const promise = loadContent(callback);
69 | if (promise) {
70 | promise.then(
71 | (data) => callback(null, data),
72 | (err) => callback(err)
73 | );
74 | }
75 | if (!this.state.isLoading) {
76 | this.setState({isLoading: true});
77 | }
78 | }
79 |
80 | render() {
81 | const {render, renderLoading, CustomPanelStyle, active, index} = this.props;
82 | const {isLoading, data} = this.state;
83 | let content;
84 | if (isLoading) {
85 | content = renderLoading();
86 | } else {
87 | content = render(data);
88 | }
89 | return (
90 |
91 | {content}
92 |
93 | )
94 | }
95 | }
--------------------------------------------------------------------------------
/src/CloseButton.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import {CloseIcon} from './IconSvg';
4 | import styled from 'styled-components';
5 |
6 | const CloseWrapper = styled.button`
7 | display: inline-block;
8 | color: #777;
9 | margin-left: 5px;
10 | padding: 0;
11 | vertical-align: middle;
12 | border: 0;
13 | padding: 2px;
14 | outline: 0;
15 | &:hover {
16 | color: black;
17 | background-color: #eee;
18 | cursor: pointer;
19 | border-radius: 50%;
20 | }
21 | > svg {
22 | vertical-align: middle;
23 | }
24 | `;
25 |
26 | type Props = {
27 | handleDelete: (event: any) => void
28 | };
29 |
30 | export default class CloseButton extends React.PureComponent {
31 | render() {
32 | return (
33 |
34 |
35 |
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/DragTab.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import {SortableElement} from 'react-sortable-hoc';
4 | import Tab from './Tab';
5 |
6 | const DragTabElement = SortableElement(({children, ...props}) => {
7 | return (
8 |
9 | {children}
10 |
11 | )
12 | });
13 |
14 | type Props = {
15 | children: React.Node
16 | };
17 |
18 | class DragTab extends React.PureComponent {
19 |
20 | __DRAG_TAB_INTERNAL_NODE: React.ElementRef;
21 |
22 | render() {
23 | const {children, ...props} = this.props;
24 | return (
25 | this.__DRAG_TAB_INTERNAL_NODE = node} {...props}>
26 | {children}
27 |
28 | )
29 | }
30 | }
31 |
32 | DragTab.displayName = 'DragTab';
33 |
34 | export default DragTab;
--------------------------------------------------------------------------------
/src/DragTabList.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import SortMethod from './SortMethod';
4 | import {SortableContainer} from 'react-sortable-hoc';
5 | import TabList from './TabList';
6 |
7 | const DragTabContainer = SortableContainer(({children, ...props}) => {
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | });
14 |
15 | export default class DragTabList extends SortMethod {
16 | render() {
17 | const {children, ...props} = this.props;
18 | return (
19 |
26 | {children}
27 |
28 | );
29 | }
30 | }
31 |
32 | DragTabList.displayName = 'DragTabList';
--------------------------------------------------------------------------------
/src/ExtraButton.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import styled from 'styled-components';
4 |
5 | const Wrapper = styled.button`
6 | float: right;
7 | border: 1px solid #eee;
8 | border-radius: 2px;
9 | padding: 3px;
10 | margin-top: 10px;
11 | margin-left: 2px;
12 | display: inline-block;
13 | color: #777;
14 | vertical-align: middle;
15 | /* ${props => props.disabled ? `
16 | pointer-events: none;
17 | color: #AAA;
18 | background: #F5F5F5;
19 | `
20 | : null} */
21 | &:hover {
22 | color: black;
23 | cursor: pointer;
24 | }
25 | &:disabled,
26 | &[disabled]{
27 | border: 1px solid grey;
28 | background-color: #e7e7e7;
29 | cursor: not-allowed;
30 | }
31 | `;
32 |
33 | type Props = {
34 | onClick: (event: any) => void,
35 | disabled: boolean,
36 | children: React.Node
37 | };
38 |
39 | export default class ExtraButton extends React.PureComponent {
40 | static defaultProps = {
41 | disabled: false
42 | }
43 |
44 | render() {
45 | const {disabled, onClick} = this.props;
46 | return (
47 |
48 | {this.props.children}
49 |
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/IconSvg.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // The svg path is from react-icons: https://github.com/gorangajic/react-icons/
3 | const Svg = ({d}) => (
4 |
5 |
6 |
7 | );
8 |
9 | const CloseIcon = () => (
10 |
11 | );
12 |
13 | const LeftIcon = () => (
14 |
15 | );
16 |
17 | const RightIcon = () => (
18 |
19 | );
20 |
21 | const BulletIcon = () => (
22 |
23 | );
24 |
25 | export {
26 | CloseIcon,
27 | LeftIcon,
28 | RightIcon,
29 | BulletIcon
30 | };
--------------------------------------------------------------------------------
/src/Panel.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react'
3 | import styled from 'styled-components';
4 |
5 | const PanelStyle = styled.div`
6 | background-color: white;
7 | text-align: left;
8 | padding: 20px 15px;
9 | ${props => !props.active ? `display: none;` : null}
10 | `;
11 |
12 | type Props = {
13 | children: React.Node,
14 | CustomPanelStyle: () => void,
15 | active: boolean,
16 | index: number
17 | };
18 |
19 | export default class PanelComponent extends React.PureComponent {
20 | render() {
21 | const {active, index} = this.props;
22 | const Panel = this.props.CustomPanelStyle || PanelStyle;
23 | return (
24 |
29 | {active ?
30 | this.props.children
31 | : null}
32 |
33 | )
34 | }
35 | }
36 |
37 | export {
38 | PanelStyle
39 | }
--------------------------------------------------------------------------------
/src/PanelList.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 |
4 | type Props = {
5 | children: Array,
6 | activeIndex: number,
7 | customStyle: {
8 | Panel: () => void
9 | }
10 | };
11 |
12 | export default class PanelList extends React.PureComponent {
13 | render() {
14 | const {
15 | children,
16 | activeIndex,
17 | customStyle
18 | } = this.props;
19 | if (!children || activeIndex === undefined) {
20 | return null;
21 | }
22 |
23 | let props = {};
24 | if (customStyle && customStyle.Panel) {
25 | props = {...props, CustomPanelStyle: customStyle.Panel}
26 | }
27 |
28 | // to prevent the type of one children is object type
29 | const result = React.Children.toArray(children).map((child, index) => (
30 | React.cloneElement(child, {
31 | key: index,
32 | active: index === activeIndex,
33 | index,
34 | ...props
35 | })
36 | ));
37 | return {result}
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/SortMethod.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 |
4 | type Props = {
5 | handleTabChange: (event: any) => void,
6 | handleTabSequence: (event: any) => void,
7 | activeIndex: number,
8 | children: React.Node
9 | };
10 |
11 | export default class SortMethod extends React.PureComponent {
12 | constructor(props: Props) {
13 | super(props);
14 | (this: any).onSortEnd = this.onSortEnd.bind(this);
15 | }
16 |
17 | onSortEnd({oldIndex, newIndex}: {oldIndex: number, newIndex: number}) {
18 | const {activeIndex, handleTabChange, handleTabSequence} = this.props;
19 | if (oldIndex === newIndex) {
20 | if (activeIndex !== oldIndex) {
21 | handleTabChange(oldIndex);
22 | }
23 | } else {
24 | handleTabSequence({oldIndex, newIndex});
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/Tab.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import styled from 'styled-components';
4 | import CloseButton from './CloseButton';
5 |
6 | const TabStyle = styled.li`
7 | display: ${props => props.vertical ? 'block': 'inline-block'};
8 | ${props => props.vertical ?
9 | `
10 | background-color: white;
11 | color: black;
12 | padding: 10px 10px;
13 | z-index: 100000;
14 | `
15 | : props => props.closable ? 'padding: 10px 10px 10px 15px;' : 'padding: 10px 15px;'
16 | }
17 |
18 | user-select: none;
19 | &:hover {
20 | cursor: pointer;
21 | color: black;
22 | }
23 | `;
24 |
25 | const TabText = styled.span`
26 | vertical-align: middle;
27 | `;
28 |
29 | type Props = {
30 | CustomTabStyle: () => void,
31 | handleTabChange: (event: any) => void,
32 | handleEdit: (event: any) => void,
33 | index: number,
34 | active: boolean,
35 | closable: boolean,
36 | vertical: boolean,
37 | children: React.Element
38 | };
39 |
40 | export default class Tab extends React.PureComponent {
41 |
42 | __INTERNAL_NODE: React.ElementRef;
43 |
44 | constructor(props: Props) {
45 | super(props);
46 | (this: any).clickTab = this.clickTab.bind(this);
47 | (this: any).clickDelete = this.clickDelete.bind(this);
48 | }
49 |
50 | clickTab() {
51 | const {handleTabChange, index} = this.props;
52 | handleTabChange(index);
53 | }
54 |
55 | clickDelete(event: SyntheticEvent) {
56 | event.stopPropagation(); // prevent trigger clickTab event.
57 | const {handleEdit, index} = this.props;
58 | handleEdit({type: 'delete', index});
59 | }
60 |
61 | render() {
62 | const {CustomTabStyle, active, closable, vertical, index} = this.props;
63 | const TabComponent = CustomTabStyle || TabStyle;
64 | return (
65 | this.__INTERNAL_NODE = node}
66 | onClick={this.clickTab}
67 | active={active}
68 | vertical={vertical}
69 | closable={closable}
70 | role="tab"
71 | id={`react-tabtab-tab-${index}`}
72 | aria-controls={`react-tabtab-panel-${index}`}
73 | aria-selected={active}>
74 | {this.props.children}
75 | {closable ?
76 |
77 | : null}
78 |
79 | )
80 | }
81 | }
82 |
83 | Tab.displayName = 'Tab';
84 |
85 | export {
86 | TabStyle
87 | };
--------------------------------------------------------------------------------
/src/TabList.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import styled from 'styled-components';
4 | import invariant from 'invariant';
5 | import {LeftIcon, RightIcon, BulletIcon} from './IconSvg';
6 | import {isNumber} from './utils/isType';
7 | import TabModal from './TabModal';
8 |
9 | const buttonWidth = 35;
10 | const getPadding = ({showModalButton, showArrowButton}) => {
11 | let paddingLeft = 0;
12 | let paddingRight = 0;
13 | if (showModalButton) {
14 | paddingLeft += buttonWidth;
15 | }
16 | if (showArrowButton) {
17 | paddingLeft += buttonWidth;
18 | paddingRight += buttonWidth;
19 | if (showModalButton) {
20 | paddingLeft += 2;
21 | }
22 | }
23 | if (paddingLeft > 0) {
24 | paddingLeft += 3;
25 | }
26 | if (paddingRight > 0) {
27 | paddingRight += 3;
28 | }
29 | return `0 ${paddingRight}px 0 ${paddingLeft}px`;
30 | }
31 |
32 | const TabListStyle = styled.div`
33 | background-color: white;
34 | text-align: left;
35 | position: relative;
36 | white-space: nowrap;
37 | overflow: hidden;
38 | width: auto;
39 | padding: ${props => getPadding(props)};
40 | `;
41 |
42 | const ListInner = styled.div`
43 | overflow: hidden;
44 | `;
45 |
46 | const ListScroll = styled.ul`
47 | padding-left: 0;
48 | position: relative;
49 | margin: 0;
50 | list-style: none;
51 | display: inline-block;
52 | transition: transform .3s cubic-bezier(.42, 0, .58, 1);
53 | `;
54 |
55 | const ActionButtonStyle = styled.div`
56 | height: 100%;
57 | width ${buttonWidth}px;
58 | text-align: center;
59 | border: 1px solid #d9d9d9;
60 | border-bottom: 0;
61 | border-radius: 4px 4px 0 0;
62 | background: #f9f9f9;
63 | > svg {
64 | padding-top: 11px;
65 | }
66 | `;
67 |
68 | const makeScrollButton = ActionButton => styled(ActionButton)`
69 | display: inline-block;
70 | filter: none;
71 | position: absolute;
72 | ${props => props.left ?
73 | props.showModalButton ? `left: ${buttonWidth + 2}px`: `left: 0`
74 | : 'right: 0'
75 | };
76 | &:hover {
77 | cursor: pointer;
78 | }
79 | `;
80 |
81 | const makeFoldButton = ActionButton => styled(ActionButton)`
82 | display: inline-block;
83 | filter: none;
84 | position: absolute;
85 | left: 0;
86 | &:hover {
87 | cursor: pointer;
88 | }
89 | `;
90 |
91 | type Props = {
92 | customStyle: {
93 | TabList: () => void,
94 | Tab: () => void,
95 | ActionButton: () => void
96 | },
97 | activeIndex: number,
98 | showArrowButton: 'auto' | boolean,
99 | showModalButton: number | boolean,
100 | handleTabChange: (event: any) => void,
101 | handleTabSequence: (event: any) => void,
102 | handleEdit: (event: any) => void,
103 | ExtraButton: React.Element<*>,
104 | children: React.ChildrenArray<*>
105 | };
106 |
107 | type State = {
108 | modalIsOpen: boolean,
109 | showArrowButton: boolean,
110 | showModalButton: boolean | number
111 | };
112 |
113 | export default class TabListComponent extends React.Component {
114 |
115 | listContainer: React.ElementRef;
116 | rightArrowNode: React.ElementRef;
117 | leftArrowNode: React.ElementRef;
118 | listScroll: React.ElementRef;
119 | foldNode: React.ElementRef;
120 | tabRefs: Array;
121 | scrollPosition: number;
122 |
123 | constructor(props: Props) {
124 | super(props);
125 | (this: any).handleScroll = this.handleScroll.bind(this);
126 | (this: any).toggleModal = this.toggleModal.bind(this);
127 | (this: any).renderTabs = this.renderTabs.bind(this);
128 | (this: any).renderArrowButton = this.renderArrowButton.bind(this);
129 | (this: any).isShowModalButton = this.isShowModalButton.bind(this);
130 | (this: any).isShowArrowButton = this.isShowArrowButton.bind(this);
131 | (this: any).scrollPosition = 0;
132 | (this: any).tabRefs = [];
133 | (this: any).state = {
134 | modalIsOpen: false,
135 | showArrowButton: false,
136 | showModalButton: false
137 | }
138 | }
139 |
140 | componentDidMount() {
141 | this.isShowArrowButton();
142 | this.isShowModalButton();
143 | if(this.props.activeIndex > 0)
144 | this.scrollToIndex(this.props.activeIndex, 'left')
145 | }
146 |
147 | componentDidUpdate(prevProps: Props, prevState: State) {
148 | if (prevProps.children.length !== this.props.children.length) {
149 | this.isShowArrowButton();
150 | this.isShowModalButton();
151 | }
152 |
153 | if (prevProps.activeIndex !== this.props.activeIndex) {
154 | //if we scroll to the last tab, alignment is set to the right side of the tab
155 | const rectSide = this.props.activeIndex === this.props.children.length - 1 ? 'right' : 'left';
156 | this.scrollToIndex(this.props.activeIndex, rectSide);
157 | }
158 | // if prev state show arrow button, and current state doesn't show
159 | // need to reset the scroll position, or some tabs will be hided by container.
160 | if (prevState.showArrowButton && !this.state.showArrowButton) {
161 | this.scrollToZero();
162 | }
163 |
164 | if (prevProps.showModalButton !== this.props.showModalButton) {
165 | this.isShowModalButton();
166 | }
167 |
168 | if (prevProps.showArrowButton !== this.props.showArrowButton) {
169 | this.isShowArrowButton();
170 | }
171 | }
172 |
173 | getTabNode(tab: any): React.ElementRef {
174 | if (tab.__INTERNAL_NODE) { // normal tab
175 | return tab.__INTERNAL_NODE;
176 | } else if (tab.__DRAG_TAB_INTERNAL_NODE) { // drag tab
177 | return tab.__DRAG_TAB_INTERNAL_NODE.node;
178 | }
179 | }
180 |
181 | unifyScrollMax(width: number) {
182 | return parseFloat((width / 3) * 2);
183 | }
184 |
185 | handleScroll(direction: 'right' | 'left') {
186 | let leftMove = 0;
187 | const containerOffset = this.listContainer.getBoundingClientRect();
188 | const containerWidth = this.listContainer.offsetWidth;
189 | const tabFirstOffset = this.getTabNode(this.tabRefs[0]).getBoundingClientRect();
190 | const tabLastOffset = this.getTabNode(this.tabRefs[this.tabRefs.length - 1]).getBoundingClientRect();
191 |
192 | if (direction === 'right') {
193 | leftMove = tabLastOffset.right - containerOffset.right;
194 | if (leftMove > containerWidth) {
195 | leftMove = this.unifyScrollMax(containerWidth);
196 | }
197 | } else if (direction === 'left') {
198 | leftMove = tabFirstOffset.left - containerOffset.left;
199 | if (-leftMove > containerWidth) {
200 | leftMove = - this.unifyScrollMax(containerWidth);
201 | }
202 | }
203 | this.scrollPosition += leftMove;
204 | if (this.scrollPosition < 0) {
205 | this.scrollPosition = 0;
206 | }
207 |
208 | this.listScroll.style.transform = `translate3d(-${this.scrollPosition}px, 0, 0)`;
209 | }
210 |
211 | // $FlowFixMe
212 | scrollToIndex(index: number, rectSide: 'left' | 'right') {
213 | const tabOffset = this.getTabNode(this.tabRefs[index]).getBoundingClientRect();
214 | const containerOffset = this.listContainer.getBoundingClientRect();
215 | // Cancel scrolling if the tab is visible
216 | if(tabOffset.right < containerOffset.right &&
217 | tabOffset.left > containerOffset.left) return;
218 | const leftMove = tabOffset[rectSide] - containerOffset[rectSide];
219 | this.scrollPosition += leftMove;
220 | if (this.scrollPosition < 0 ) {
221 | this.scrollPosition = 0;
222 | }
223 | this.listScroll.style.transform = `translate3d(-${this.scrollPosition}px, 0, 0)`;
224 | }
225 |
226 | scrollToZero() {
227 | this.listScroll.style.transform = `translate3d(0, 0, 0)`;
228 | }
229 |
230 | toggleModal(open: boolean) {
231 | this.setState({modalIsOpen: open}, () => {
232 | if (!open) {
233 | // $FlowFixMe
234 | this.scrollToIndex(this.props.activeIndex, 'right');
235 | }
236 | });
237 | }
238 |
239 | isShowModalButton() {
240 | let {showModalButton} = this.props;
241 | if (isNumber(showModalButton)) {
242 | // $FlowFixMe, weired. currently set showModalButton as number | bool, but don't know why flow only can recognize it as bool
243 | showModalButton = this.props.children.length >= showModalButton;
244 | }
245 | this.setState({showModalButton});
246 | }
247 |
248 | isShowArrowButton() {
249 | let {showArrowButton} = this.props;
250 | if (showArrowButton === 'auto') {
251 | let tabWidth = 0;
252 | const containerWidth = this.listContainer.offsetWidth;
253 | showArrowButton = false;
254 | for (let index = 0; index < this.tabRefs.length; index++) {
255 | const tab = this.getTabNode(this.tabRefs[index]);
256 | tabWidth += tab.offsetWidth;
257 | if (tabWidth >= containerWidth) {
258 | showArrowButton = true;
259 | break;
260 | }
261 | }
262 | }
263 | // $FlowFixMe: flow will show 'auto' is not bool, but with this logic, showArrowButton will never be 'auto'
264 | this.setState({showArrowButton});
265 | }
266 |
267 | renderTabs(options?: any = {}, isModal?: boolean) {
268 | const {children, activeIndex, handleTabChange, handleEdit, customStyle} = this.props;
269 | const props = {
270 | handleTabChange,
271 | handleEdit,
272 | //$FlowFixMe
273 | CustomTabStyle: customStyle.Tab
274 | };
275 | if (!isModal) {
276 | this.tabRefs = [];
277 | }
278 | return React.Children.map(children, (child, index) => (
279 | React.cloneElement(child, {
280 | key: index,
281 | active: index === activeIndex,
282 | index,
283 | tabIndex: index,
284 | ref: node => {
285 | if (!isModal && node) {
286 | this.tabRefs.push(node)
287 | }
288 | },
289 | ...props,
290 | ...options
291 | })
292 | ));
293 | }
294 |
295 | renderArrowButton(ScrollButton: React.ComponentType<*>) {
296 | const {showArrowButton} = this.state;
297 | if (showArrowButton) {
298 | return (
299 |
300 | { this.handleScroll('left') }}
302 | ref={node => this.leftArrowNode = node}
303 | showModalButton={this.state.showModalButton}>
304 |
305 |
306 | { this.handleScroll('right') }}
307 | ref={node => this.rightArrowNode = node}>
308 |
309 |
310 |
311 | )
312 | }
313 | return null;
314 | }
315 |
316 | render() {
317 | const {
318 | customStyle,
319 | activeIndex,
320 | handleTabChange,
321 | handleTabSequence,
322 | ExtraButton
323 | } = this.props;
324 | const {modalIsOpen} = this.state;
325 | const TabList = customStyle.TabList || TabListStyle;
326 | const ActionButton = customStyle.ActionButton || ActionButtonStyle;
327 | const ScrollButton = makeScrollButton(ActionButton);
328 | const FoldButton = makeFoldButton(ActionButton);
329 | invariant(this.props.children, 'React-tabtab Error: You MUST pass at least one tab')
330 | return (
331 |
332 | {ExtraButton ? ExtraButton : null}
333 |
336 | {this.state.showModalButton ?
337 | this.foldNode = node}
338 | onClick={this.toggleModal.bind(this, true)}
339 | showArrowButton={this.state.showArrowButton}>
340 |
341 |
342 | : null}
343 | {this.renderArrowButton(ScrollButton)}
344 | this.listContainer = node}>
345 | this.listScroll = node} role="tablist">
346 | {this.renderTabs()}
347 |
348 |
349 |
350 | {modalIsOpen ?
351 |
355 | {this.renderTabs({vertical: true}, true)}
356 |
357 | : null}
358 |
359 | )
360 | }
361 | }
362 |
363 | TabListComponent.displayName = 'TabList';
364 |
365 | export {
366 | TabListStyle,
367 | ActionButtonStyle
368 | }
369 |
--------------------------------------------------------------------------------
/src/TabModal.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import Poppop from 'react-poppop';
4 | import {SortableContainer} from 'react-sortable-hoc';
5 | import SortMethod from './SortMethod';
6 |
7 | type Props = {
8 | closeModal: (event: any) => void,
9 | handleTabSequence: (event: any) => void,
10 | handleTabChange: (event: any) => void,
11 | activeIndex: number,
12 | children: React.Node
13 | };
14 |
15 | const DragTabContainer = SortableContainer(({children}) => {
16 | return (
17 |
18 | {children}
19 |
20 | );
21 | });
22 |
23 | class ModalTabListWrapper extends SortMethod {
24 | render() {
25 | return (
26 |
32 | {this.props.children}
33 |
34 | );
35 | }
36 | }
37 |
38 | export default class TabModal extends React.Component {
39 | render() {
40 | return (
41 |
45 |
48 | {this.props.children}
49 |
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Tabs.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 |
4 | type Props = {
5 | defaultIndex?: number,
6 | activeIndex?: number,
7 | showModalButton?: number | boolean,
8 | showArrowButton?: 'auto' | boolean ,
9 | ExtraButton?: React.Node,
10 | onTabChange?: (event: any) => void,
11 | onTabSequenceChange?: (event: any) => void,
12 | onTabEdit?: (event: any) => void,
13 | customStyle?: {
14 | TabList?: () => void,
15 | Tab?: () => void,
16 | Panel?: () => void,
17 | ActionButton?: () => void
18 | },
19 | children: React.Element<*>
20 | };
21 |
22 | type State = {
23 | activeIndex: number
24 | };
25 |
26 | export default class Tabs extends React.Component {
27 | constructor(props: Props) {
28 | super(props);
29 | (this: any).handleTabChange = this.handleTabChange.bind(this);
30 | (this: any).handleTabSequence = this.handleTabSequence.bind(this);
31 | (this: any).handleEdit = this.handleEdit.bind(this);
32 | (this: any).state = {
33 | activeIndex: this.getActiveIndex(props)
34 | };
35 | }
36 |
37 | static defaultProps = {
38 | showModalButton: 4,
39 | showArrowButton: 'auto',
40 | onTabChange: () => {},
41 | onTabSequenceChange: () => {},
42 | customStyle: {
43 | TabList: null,
44 | Tab: null,
45 | Panel: null,
46 | ActionButton: null
47 | }
48 | }
49 |
50 | getActiveIndex(props: Props) {
51 | const {defaultIndex, activeIndex} = props;
52 | if (activeIndex)
53 | return activeIndex;
54 | if (defaultIndex)
55 | return defaultIndex;
56 | return 0;
57 | }
58 |
59 | componentWillReceiveProps(nextProps: Props) {
60 | if (nextProps.activeIndex !== this.props.activeIndex) {
61 | this.setState({activeIndex: nextProps.activeIndex});
62 | }
63 | }
64 |
65 | handleTabChange(index: number) {
66 | const {activeIndex, onTabChange} = this.props;
67 | if (activeIndex !== 0 && !activeIndex) {
68 | this.setState({activeIndex: index});
69 | }
70 | if (onTabChange) {
71 | onTabChange(index);
72 | }
73 | }
74 |
75 | handleTabSequence({oldIndex, newIndex}: {oldIndex: number, newIndex: number}) {
76 | const {onTabSequenceChange} = this.props;
77 | if (onTabSequenceChange) {
78 | onTabSequenceChange({oldIndex, newIndex});
79 | }
80 | }
81 |
82 | handleEdit({type, index}: {type: string, index: number}) {
83 | const {onTabEdit} = this.props;
84 | if (onTabEdit) {
85 | onTabEdit({type, index});
86 | }
87 | }
88 |
89 | render() {
90 | const {children, ...extraProps} = this.props;
91 | const {activeIndex} = this.state;
92 | const props = {
93 | handleTabChange: this.handleTabChange,
94 | handleTabSequence: this.handleTabSequence,
95 | handleEdit: this.handleEdit,
96 | activeIndex,
97 | ...extraProps
98 | }
99 |
100 | return (
101 |
102 | {React.Children.map(children, (child) => {
103 | return React.cloneElement(child, props);
104 | })}
105 |
106 | )
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/helpers/delete.js:
--------------------------------------------------------------------------------
1 | function deleteHelper (sequence, deleteIndex) {
2 | return sequence.filter((_, i) => i !== deleteIndex);
3 | }
4 |
5 | export default deleteHelper;
--------------------------------------------------------------------------------
/src/helpers/move.js:
--------------------------------------------------------------------------------
1 | // arrayMove method is import from 'react-sortable-hoc' to unify api interface
2 | import {arrayMove} from 'react-sortable-hoc';
3 |
4 | export default {
5 | simpleSwitch: arrayMove
6 | };
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import Tabs from './Tabs';
3 | import TabList, {TabListStyle, ActionButtonStyle} from './TabList';
4 | import Tab, {TabStyle} from './Tab';
5 | import DragTabList from './DragTabList';
6 | import DragTab from './DragTab';
7 | import PanelList from './PanelList';
8 | import Panel, {PanelStyle} from './Panel';
9 | import AsyncPanel from './AsyncPanel';
10 | import ExtraButton from './ExtraButton';
11 |
12 | const styled = {TabListStyle, ActionButtonStyle, TabStyle, PanelStyle};
13 | const defaultOutput = {
14 | Tabs,
15 | TabList,
16 | Tab,
17 | DragTabList,
18 | DragTab,
19 | PanelList,
20 | Panel,
21 | AsyncPanel,
22 | ExtraButton,
23 | styled
24 | }
25 |
26 | export default defaultOutput;
--------------------------------------------------------------------------------
/src/themes/bootstrap/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import {styled as themeStyled} from '../../';
3 |
4 | let {TabListStyle, ActionButtonStyle, TabStyle, PanelStyle} = themeStyled;
5 |
6 | TabListStyle = styled(TabListStyle)`
7 | border-bottom: 1px solid #eee;
8 | `;
9 |
10 | TabStyle = styled(TabStyle)`
11 | border-top-left-radius: .25rem;
12 | border-top-right-radius: .25rem;
13 | transition: color .3s cubic-bezier(.645, .045, .355, 1);
14 | transition: background-color .3s cubic-bezier(.645, .045, .355, 1);
15 | color: ${props => props.active ? 'black' : '#007bff'};
16 | border: 1px solid transparent;
17 | ${props => props.vertical ?
18 | `
19 | border-top: 1px solid transparent;
20 | border-bottom: 1px solid #efefef;
21 | border-left: 1px solid #efefef;
22 | border-right: 1px solid #efefef;
23 | border-radius: 0;
24 | &:first-child {
25 | border-top: 1px solid #efefef;
26 | }
27 | `
28 | : `
29 | &:hover {
30 | border-color: #ddd #ddd #fff;
31 | }
32 | `}
33 | ${props => props.active && props.vertical ?
34 | `
35 | background-color: #eee;
36 | `
37 | : null}
38 | ${props => props.active && !props.vertical ?
39 | `
40 | border-color: #ddd #ddd #fff;
41 | `
42 | : null}
43 | `;
44 |
45 | export default {
46 | TabList: TabListStyle,
47 | ActionButton: ActionButtonStyle,
48 | Tab: TabStyle,
49 | Panel: PanelStyle
50 | }
51 |
--------------------------------------------------------------------------------
/src/themes/bulma/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import {styled as themeStyled} from '../../';
3 |
4 | let {TabListStyle, ActionButtonStyle, TabStyle, PanelStyle} = themeStyled;
5 |
6 | TabListStyle = styled(TabListStyle)`
7 | background-color: #fff;
8 | border-bottom: 1px solid #dbdbdb;
9 | `;
10 |
11 | TabStyle = styled(TabStyle)`
12 | position: relative;
13 | color: #4a4a4a;
14 | border: 0;
15 | padding: 13px 19px;
16 | margin-bottom: -1px;
17 | &::after {
18 | z-index: 10;
19 | content: '';
20 | position: absolute;
21 | left: 0;
22 | bottom: 0;
23 | right: 0;
24 | height: 2px;
25 | background: #dbdbdb;
26 | }
27 | ${props => props.active && !props.vertical ?
28 | `
29 | &::after {
30 | background: #3273dc;
31 | }
32 | `
33 | : null}
34 | &:hover::after {
35 | background: #3273dc;
36 | }
37 | `;
38 |
39 | ActionButtonStyle = styled(ActionButtonStyle)`
40 | background-color: transparent;
41 | border-radius: 0;
42 | &:hover {
43 | background-color: #eee;
44 | }
45 | `;
46 |
47 | PanelStyle = styled(PanelStyle)`
48 | `;
49 |
50 | export default {
51 | TabList: TabListStyle,
52 | ActionButton: ActionButtonStyle,
53 | Tab: TabStyle,
54 | Panel: PanelStyle
55 | }
56 |
--------------------------------------------------------------------------------
/src/themes/material-design/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import {styled as themeStyled} from '../../';
3 |
4 | let {TabListStyle, ActionButtonStyle, TabStyle, PanelStyle} = themeStyled;
5 |
6 | TabListStyle = styled(TabListStyle)`
7 | background-color: #fff;
8 | box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2);
9 | border: 0;
10 | `;
11 |
12 | TabStyle = styled(TabStyle)`
13 | color: rgba(238,110,115,0.7);
14 | transition: color .28s ease;
15 | border: 0;
16 | ${props => props.active && !props.vertical ?
17 | `
18 | border-bottom: 2px solid #f6b2b5;
19 | `
20 | : null}
21 | &:hover {
22 | background-color: transparent;
23 | color: #ee6e73;
24 | border-bottom: 2px solid #f6b2b5;
25 | }
26 | `;
27 |
28 | ActionButtonStyle = styled(ActionButtonStyle)`
29 | background-color: transparent;
30 | border-radius: 0;
31 | &:hover {
32 | background-color: #eee;
33 | }
34 | `;
35 |
36 | PanelStyle = styled(PanelStyle)`
37 | border-left: 1px solid rgba(0,0,0,0.12);
38 | border-right: 1px solid rgba(0,0,0,0.12);
39 | border-bottom: 1px solid rgba(0,0,0,0.12);
40 | padding: 30px 30px;
41 | transition: box-shadow .25s, -webkit-box-shadow .25s;
42 | border-radius: 2px;
43 | `;
44 |
45 | export default {
46 | TabList: TabListStyle,
47 | ActionButton: ActionButtonStyle,
48 | Tab: TabStyle,
49 | Panel: PanelStyle
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/countTab.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import * as React from 'react';
3 | import {isTab, isTabList} from './isType';
4 |
5 | function loopTabList(tabList, cb) {
6 | React.Children.forEach(tabList, tab => {
7 | if (isTab(tab)) {
8 | cb();
9 | }
10 | });
11 | }
12 |
13 | function deepLoop(children: React.ChildrenArray, cb) {
14 | React.Children.forEach(children, child => {
15 | if (isTabList(child)) {
16 | if (child.props && child.props.children) {
17 | return loopTabList(child.props.children, cb);
18 | } else {
19 | throw new Error('You need to provide `Tab` children');
20 | }
21 | } else if (child.props && child.props.children) {
22 | deepLoop(child.props.children, cb);
23 | }
24 | });
25 | }
26 |
27 |
28 | export default function countTab(children: React.ChildrenArray) {
29 | let count = 0
30 | deepLoop(children, () => count++);
31 | return count;
32 | }
--------------------------------------------------------------------------------
/src/utils/isType.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | export function isTabList(element: any) {
3 | return element.type &&
4 | (element.type.displayName === 'TabList' || element.type.displayName === 'DragTabList');
5 | }
6 |
7 | export function isTab(element: any) {
8 | return element.type &&
9 | (element.type.displayName === 'Tab' || element.type.displayName === 'DragTab');
10 | }
11 |
12 | export function isNumber(number: any) {
13 | return !isNaN(parseInt(number, 10));
14 | }
--------------------------------------------------------------------------------
/test/AsyncPanel.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AsyncPanel from '../src/AsyncPanel';
3 | import {mount} from 'enzyme';
4 |
5 | function fakePromiseFetch(type, shouldRej = false) {
6 | if (type === 'cb') {
7 | return function (cb) {
8 | setTimeout(() => {
9 | if (shouldRej) {
10 | cb(true);
11 | } else {
12 | cb(null, {
13 | content: 'promise fetched'
14 | });
15 | }
16 | }, 0);
17 | }
18 | } else if (type === 'promise') {
19 | return function () {
20 | return new Promise((res, rej) => {
21 | setTimeout(() => {
22 | if (shouldRej) {
23 | rej(true);
24 | } else {
25 | res({
26 | content: 'promise fetched'
27 | });
28 | }
29 | }, 0)
30 | })
31 | }
32 |
33 | }
34 | }
35 |
36 | function makeCallback(done, body) {
37 | return (...args) => {
38 | try {
39 | body(...args);
40 | done();
41 | } catch (error) {
42 | done.fail(error);
43 | }
44 | };
45 | }
46 |
47 | function testByLoadType(type) {
48 | describe(`${type} render`, () => {
49 | test('render loading content', () => {
50 | const loadContent = fakePromiseFetch(type);
51 | const component = mount(
52 | ({JSON.stringify(data)}
)}
54 | renderLoading={() => (loading
)}
55 | active={true} />
56 | );
57 | expect(component.text()).toEqual('loading');
58 | })
59 |
60 | test('render content when loadContent finished, by promise', (done) => {
61 | const loadContent = fakePromiseFetch(type);
62 | const component = mount(
63 | ({JSON.stringify(data)}
)}
65 | renderLoading={() => (loading
)}
66 | active={true} />
67 | );
68 | setTimeout(makeCallback(done, () => {
69 | expect(component.text()).toEqual('{"content":"promise fetched"}');
70 | }), 10);
71 | })
72 |
73 | test('show error when loadContent has error, by promise', (done) => {
74 | const loadContent = fakePromiseFetch(type, true);
75 | const component = mount(
76 | ({JSON.stringify(data)}
)}
78 | renderLoading={() => (loading
)}
79 | active={true} />
80 | );
81 | setTimeout(makeCallback(done, () => {
82 | expect(component.text()).toEqual('');
83 | }), 10);
84 | })
85 |
86 | test('if the panel become active, load content', (done) => {
87 | const loadContent = fakePromiseFetch(type);
88 | const component = mount(
89 | ({JSON.stringify(data)}
)}
91 | renderLoading={() => (loading
)} />
92 | );
93 | expect(component.text()).toEqual('');
94 | component.setProps({active: true});
95 | setTimeout(makeCallback(done, () => {
96 | expect(component.text()).toEqual(JSON.stringify({content: 'promise fetched'}));
97 | }), 10);
98 | })
99 |
100 | test('if cache, should not load again', (done) => {
101 | const loadContent = fakePromiseFetch(type);
102 | const component = mount(
103 | ({JSON.stringify(data)}
)}
105 | renderLoading={() => (loading
)}
106 | active={true}/>
107 | );
108 | setTimeout(makeCallback(done, () => {
109 | component.setProps({active: false});
110 | component.setProps({active: true});
111 | expect(component.text()).toEqual(JSON.stringify({content: 'promise fetched'}));
112 | }), 10);
113 | });
114 |
115 | test('if not cache, load again', (done) => {
116 | const loadContent = fakePromiseFetch(type);
117 | const component = mount(
118 | ({JSON.stringify(data)}
)}
120 | renderLoading={() => (loading
)}
121 | active={true}
122 | cache={false}/>
123 | );
124 | setTimeout(makeCallback(done, () => {
125 | component.setProps({active: false});
126 | component.setProps({active: true});
127 | expect(component.text()).toEqual('loading');
128 | }), 10);
129 | });
130 | })
131 | }
132 |
133 | testByLoadType('promise');
134 | testByLoadType('cb');
--------------------------------------------------------------------------------
/test/DragTabList.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {mount} from 'enzyme';
3 | import DragTabList from '../src/DragTabList';
4 | import DragTab from '../src/DragTab';
5 | import tabListTest from './tabListTest';
6 | import toJson from 'enzyme-to-json';
7 |
8 | describe('DragTabList', () => {
9 | tabListTest('DragTab', DragTabList, DragTab);
10 |
11 | test('DragTabList should have required props', () => {
12 | const component = mount(
13 |
14 | Tab1
15 |
16 | );
17 | expect(typeof toJson(component).node.rendered.props.pressDelay).toBe('number');
18 | })
19 | });
20 |
21 |
--------------------------------------------------------------------------------
/test/Panel.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Panel from '../src/Panel';
3 | import { shallow } from 'enzyme';
4 | import 'jest-styled-components'
5 |
6 | test('render Panel', () => {
7 | const component = shallow(
8 |
9 | panel content
10 |
11 | );
12 | expect(component).toMatchSnapshot();
13 | })
14 |
--------------------------------------------------------------------------------
/test/PanelList.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PanelList from '../src/PanelList';
3 | import Panel, {PanelStyle} from '../src/Panel';
4 | import {shallow, mount} from 'enzyme';
5 | import toJson from 'enzyme-to-json';
6 | import 'jest-styled-components';
7 |
8 | describe('render', () => {
9 | test('render one Panel', () => {
10 | const component = mount(
11 |
12 |
13 | panel content
14 |
15 |
16 | );
17 | expect(toJson(component)).toMatchSnapshot();
18 | });
19 |
20 | test('render multi Panel', () => {
21 | const component = mount(
22 |
23 |
24 | panel content
25 |
26 |
27 | panel content
28 |
29 |
30 | panel content
31 |
32 |
33 | );
34 | expect(toJson(component)).toMatchSnapshot();
35 | });
36 |
37 | test('return null if no child', () => {
38 | const component = shallow(
39 |
40 |
41 | );
42 | expect(component.html()).toEqual(null);
43 | })
44 | })
45 |
46 | test('custom style', () => {
47 | const component = shallow(
48 |
52 |
53 | panel content
54 |
55 |
56 | );
57 | expect(toJson(component)).toMatchSnapshot();
58 | })
--------------------------------------------------------------------------------
/test/SortMethod.test.js:
--------------------------------------------------------------------------------
1 | import SortMethod from '../src/SortMethod';
2 |
3 | describe('SortMethod test', () => {
4 | it('should call handleTabChange & handleTabSequence', () => {
5 | const handleTabChange = jest.fn();
6 | const handleTabSequence = jest.fn();
7 | const sortMethodFn = new SortMethod({handleTabChange, handleTabSequence});
8 | sortMethodFn.onSortEnd({oldIndex: 1, newIndex: 1});
9 | sortMethodFn.onSortEnd({oldIndex: 1, newIndex: 3});
10 | expect(handleTabChange).toBeCalled();
11 | expect(handleTabChange.mock.calls[0][0]).toEqual(1);
12 | expect(handleTabSequence).toBeCalled();
13 | expect(handleTabSequence.mock.calls[0][0]).toEqual({"newIndex": 3, "oldIndex": 1});
14 | })
15 | })
--------------------------------------------------------------------------------
/test/Tab.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import {mount} from 'enzyme';
4 | import 'jest-styled-components'
5 |
6 | import Tab, {TabStyle} from '../src/Tab';
7 |
8 | describe('Tab', () => {
9 | describe('render TabList', () => {
10 | it('render pure text', () => {
11 | const component = mount(
12 |
13 | tab
14 |
15 | );
16 | expect(component.html()).toMatchSnapshot();
17 | })
18 |
19 | it('render component.', () => {
20 | const component = mount(
21 |
22 |
23 |
24 | tab
25 |
26 |
27 | );
28 | expect(component.html()).toMatchSnapshot();
29 | })
30 | })
31 |
32 | describe('event', () => {
33 | it('onClick', () => {
34 | const mockHandleClick = jest.fn();
35 | const tabKey = 1;
36 | const component = mount(
37 |
39 | text
40 |
41 | );
42 | component.find('Tab').simulate('click');
43 | expect(mockHandleClick).toBeCalled();
44 | expect(mockHandleClick.mock.calls[0][0]).toEqual(tabKey);
45 | })
46 | })
47 |
48 | test('custom ListStyle style', () => {
49 | const CustomTabStyle = styled(TabStyle)`
50 | background-color: red;
51 | `;
52 | const component = mount(
53 |
54 | tab
55 |
56 | );
57 | expect(component).toHaveStyleRule('background-color', 'red');
58 | })
59 | })
60 |
61 |
--------------------------------------------------------------------------------
/test/TabList.test.js:
--------------------------------------------------------------------------------
1 | import TabList from '../src/TabList';
2 | import Tab from '../src/Tab';
3 | import tabListTest from './tabListTest';
4 |
5 | describe('TabList', () => {
6 | tabListTest('Tab', TabList, Tab);
7 | })
8 |
--------------------------------------------------------------------------------
/test/TabModal.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TabModal from '../src/TabModal';
3 | import {shallow} from 'enzyme';
4 | import noop from 'noop3';
5 |
6 | test('render Panel', () => {
7 | const component = shallow(
8 |
11 | fake child
12 |
13 | );
14 | expect(component).toMatchSnapshot();
15 | })
16 |
--------------------------------------------------------------------------------
/test/Tabs.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Tabs, DragTabList, DragTab, TabList, Tab, PanelList, Panel, ExtraButton} from '../src';
3 | import {shallow, mount} from 'enzyme';
4 |
5 | const normalComponent = (props = {}) => (
6 |
7 |
8 | Tab1
9 | Tab2
10 |
11 |
12 | Content1
13 | Content2
14 |
15 |
16 | );
17 |
18 | const dragComponent = (props = {}) => (
19 |
20 |
21 | DragTab1
22 | DragTab2
23 |
24 |
25 | Content1
26 | Content2
27 |
28 |
29 | );
30 |
31 | describe('render Tabs', () => {
32 | const testRender = (tabsComponent) => {
33 | const component = mount(tabsComponent);
34 | expect(component.html()).toMatchSnapshot();
35 | }
36 | it('normal tabs', () => {
37 | testRender(normalComponent())
38 | });
39 | it('normal drag tabs', () => {
40 | testRender(dragComponent());
41 | });
42 | it('normal tab with ExtraButton', () => {
43 | testRender(normalComponent({
44 | ExtraButton:
45 |
46 | +
47 |
48 | }))
49 | })
50 | it('normal drag with ExtraButton', () => {
51 | testRender(dragComponent({
52 | ExtraButton:
53 |
54 | +
55 |
56 | }))
57 | })
58 | });
59 |
60 | describe('props', () => {
61 | it('defaultIndex', () => {
62 | const defaultIndex = 1;
63 | const component = shallow(normalComponent({defaultIndex}));
64 | expect(component.state().activeIndex).toEqual(defaultIndex);
65 | })
66 |
67 | it('click tab, onTabChange callback', () => {
68 | const mockTabChange = jest.fn();
69 | const component = mount(normalComponent({onTabChange: mockTabChange}));
70 | component.find('Tab').at(1).simulate('click');
71 | expect(component.state().activeIndex).toEqual(1);
72 | expect(mockTabChange).toBeCalled();
73 | expect(mockTabChange.mock.calls[0][0]).toEqual(1);
74 | })
75 |
76 | describe('activeIndex', () => {
77 | it('show active index', () => {
78 | const mountComponent = mount(normalComponent({activeIndex: 1}));
79 | expect(mountComponent.state().activeIndex).toEqual(1);
80 | });
81 |
82 | it('do nothing when click tab', () => {
83 | const mockTabChange = jest.fn();
84 | const mountComponent = mount(normalComponent({onTabChange: mockTabChange, activeIndex: 1}));
85 | mountComponent.find('Tab').at(0).simulate('click');
86 | expect(mountComponent.state().activeIndex).toEqual(1);
87 | });
88 |
89 | it('update active tab when pass new activeKey', () => {
90 | const mockTabChange = jest.fn();
91 | const mountComponent = mount(normalComponent({onTabChange: mockTabChange, activeIndex: 1}));
92 | mountComponent.setProps({activeIndex: 0})
93 | expect(mountComponent.state().activeIndex).toEqual(0);
94 | })
95 | })
96 | })
97 |
--------------------------------------------------------------------------------
/test/__snapshots__/DragTabList.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`DragTabList custom ListStyle style 1`] = `
4 | .c0 {
5 | background-color: white;
6 | text-align: left;
7 | position: relative;
8 | white-space: nowrap;
9 | overflow: hidden;
10 | width: auto;
11 | padding: 0 0px 0 0px;
12 | }
13 |
14 | .c1 {
15 | overflow: hidden;
16 | }
17 |
18 | .c2 {
19 | padding-left: 0;
20 | position: relative;
21 | margin: 0;
22 | list-style: none;
23 | display: inline-block;
24 | -webkit-transition: -webkit-transform .3s cubic-bezier(.42,0,.58,1);
25 | -webkit-transition: transform .3s cubic-bezier(.42,0,.58,1);
26 | transition: transform .3s cubic-bezier(.42,0,.58,1);
27 | }
28 |
29 | .c3 {
30 | display: inline-block;
31 | padding: 10px 15px;
32 | -webkit-user-select: none;
33 | -moz-user-select: none;
34 | -ms-user-select: none;
35 | user-select: none;
36 | }
37 |
38 | .c3:hover {
39 | cursor: pointer;
40 | color: black;
41 | }
42 |
43 | .c4 {
44 | vertical-align: middle;
45 | }
46 |
47 |
84 |
134 |
171 |
208 |
209 |
212 |
247 |
250 |
251 |
277 |
280 |
283 |
315 |
319 |
325 |
331 |
335 |
340 |
348 |
391 |
399 |
400 |
426 |
429 | Tab1
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 | `;
455 |
456 | exports[`DragTabList render DragTab List 1`] = `
457 | .c0 {
458 | background-color: white;
459 | text-align: left;
460 | position: relative;
461 | white-space: nowrap;
462 | overflow: hidden;
463 | width: auto;
464 | padding: 0 0px 0 0px;
465 | }
466 |
467 | .c1 {
468 | overflow: hidden;
469 | }
470 |
471 | .c2 {
472 | padding-left: 0;
473 | position: relative;
474 | margin: 0;
475 | list-style: none;
476 | display: inline-block;
477 | -webkit-transition: -webkit-transform .3s cubic-bezier(.42,0,.58,1);
478 | -webkit-transition: transform .3s cubic-bezier(.42,0,.58,1);
479 | transition: transform .3s cubic-bezier(.42,0,.58,1);
480 | }
481 |
482 | .c3 {
483 | display: inline-block;
484 | padding: 10px 15px;
485 | -webkit-user-select: none;
486 | -moz-user-select: none;
487 | -ms-user-select: none;
488 | user-select: none;
489 | }
490 |
491 | .c3:hover {
492 | cursor: pointer;
493 | color: black;
494 | }
495 |
496 | .c4 {
497 | vertical-align: middle;
498 | }
499 |
500 |
503 |
519 |
522 |
525 |
526 |
529 |
564 |
567 |
568 |
594 |
597 |
600 |
632 |
636 |
642 |
648 |
652 |
657 |
665 |
708 |
716 |
717 |
743 |
746 | Tab1
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 | `;
772 |
--------------------------------------------------------------------------------
/test/__snapshots__/Panel.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`render Panel 1`] = `
4 |
10 | `;
11 |
--------------------------------------------------------------------------------
/test/__snapshots__/PanelList.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`custom style 1`] = `
4 |
5 |
38 |
39 | panel content
40 |
41 |
42 |
43 | `;
44 |
45 | exports[`render render multi Panel 1`] = `
46 | .c1 {
47 | background-color: white;
48 | text-align: left;
49 | padding: 20px 15px;
50 | }
51 |
52 | .c0 {
53 | background-color: white;
54 | text-align: left;
55 | padding: 20px 15px;
56 | display: none;
57 | }
58 |
59 |
62 |
63 |
68 |
75 |
111 |
118 |
119 |
120 |
121 |
126 |
133 |
169 |
176 |
177 | panel content
178 |
179 |
180 |
181 |
182 |
183 |
188 |
195 |
231 |
238 |
239 |
240 |
241 |
242 |
243 | `;
244 |
245 | exports[`render render one Panel 1`] = `
246 | .c0 {
247 | background-color: white;
248 | text-align: left;
249 | padding: 20px 15px;
250 | }
251 |
252 |
255 |
256 |
261 |
268 |
304 |
311 |
312 | panel content
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 | `;
321 |
--------------------------------------------------------------------------------
/test/__snapshots__/Tab.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Tab render TabList render component. 1`] = `" tab "`;
4 |
5 | exports[`Tab render TabList render pure text 1`] = `"tab "`;
6 |
--------------------------------------------------------------------------------
/test/__snapshots__/TabList.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`TabList custom ListStyle style 1`] = `
4 | .c0 {
5 | background-color: white;
6 | text-align: left;
7 | position: relative;
8 | white-space: nowrap;
9 | overflow: hidden;
10 | width: auto;
11 | padding: 0 0px 0 0px;
12 | }
13 |
14 | .c1 {
15 | overflow: hidden;
16 | }
17 |
18 | .c2 {
19 | padding-left: 0;
20 | position: relative;
21 | margin: 0;
22 | list-style: none;
23 | display: inline-block;
24 | -webkit-transition: -webkit-transform .3s cubic-bezier(.42,0,.58,1);
25 | -webkit-transition: transform .3s cubic-bezier(.42,0,.58,1);
26 | transition: transform .3s cubic-bezier(.42,0,.58,1);
27 | }
28 |
29 | .c3 {
30 | display: inline-block;
31 | padding: 10px 15px;
32 | -webkit-user-select: none;
33 | -moz-user-select: none;
34 | -ms-user-select: none;
35 | user-select: none;
36 | }
37 |
38 | .c3:hover {
39 | cursor: pointer;
40 | color: black;
41 | }
42 |
43 | .c4 {
44 | vertical-align: middle;
45 | }
46 |
47 |
84 |
85 |
88 |
123 |
126 |
127 |
153 |
156 |
159 |
191 |
195 |
201 |
209 |
252 |
260 |
261 |
287 |
290 | Tab1
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 | `;
310 |
311 | exports[`TabList render Tab List 1`] = `
312 | .c0 {
313 | background-color: white;
314 | text-align: left;
315 | position: relative;
316 | white-space: nowrap;
317 | overflow: hidden;
318 | width: auto;
319 | padding: 0 0px 0 0px;
320 | }
321 |
322 | .c1 {
323 | overflow: hidden;
324 | }
325 |
326 | .c2 {
327 | padding-left: 0;
328 | position: relative;
329 | margin: 0;
330 | list-style: none;
331 | display: inline-block;
332 | -webkit-transition: -webkit-transform .3s cubic-bezier(.42,0,.58,1);
333 | -webkit-transition: transform .3s cubic-bezier(.42,0,.58,1);
334 | transition: transform .3s cubic-bezier(.42,0,.58,1);
335 | }
336 |
337 | .c3 {
338 | display: inline-block;
339 | padding: 10px 15px;
340 | -webkit-user-select: none;
341 | -moz-user-select: none;
342 | -ms-user-select: none;
343 | user-select: none;
344 | }
345 |
346 | .c3:hover {
347 | cursor: pointer;
348 | color: black;
349 | }
350 |
351 | .c4 {
352 | vertical-align: middle;
353 | }
354 |
355 |
358 |
359 |
362 |
397 |
400 |
401 |
427 |
430 |
433 |
465 |
469 |
475 |
483 |
526 |
534 |
535 |
561 |
564 | Tab1
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 | `;
584 |
--------------------------------------------------------------------------------
/test/__snapshots__/TabModal.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`render Panel 1`] = `
4 |
13 |
17 |
18 | fake child
19 |
20 |
21 |
22 | `;
23 |
--------------------------------------------------------------------------------
/test/__snapshots__/Tabs.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`render Tabs normal drag tabs 1`] = `""`;
4 |
5 | exports[`render Tabs normal drag with ExtraButton 1`] = `""`;
6 |
7 | exports[`render Tabs normal tab with ExtraButton 1`] = `""`;
8 |
9 | exports[`render Tabs normal tabs 1`] = `""`;
10 |
--------------------------------------------------------------------------------
/test/enzyme-setup.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | Enzyme.configure({ adapter: new Adapter() });
5 |
6 | global.cancelAnimationFrame = (cb) => {
7 | setTimeout(cb, 0)
8 | }
9 |
10 | // Fail tests on any warning
11 | console.error = message => {
12 | throw new Error(message);
13 | };
14 |
15 |
--------------------------------------------------------------------------------
/test/helpers/delete.test.js:
--------------------------------------------------------------------------------
1 | import deleteHelper from '../../src/helpers/delete';
2 |
3 | it('case1', () => {
4 | expect(deleteHelper([0, 1, 2], 1)).toEqual([0, 2]);
5 | })
6 |
7 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | import index from '../src';
2 |
3 | test('Tabs need to be defined', () => {
4 | expect(index.Tabs).toBeDefined();
5 | });
6 |
7 | test('Tab need to be defined', () => {
8 | expect(index.Tab).toBeDefined();
9 | });
10 |
11 | test('DragTabList need to be defined', () => {
12 | expect(index.DragTabList).toBeDefined();
13 | });
14 |
15 | test('DragTab need to be defined', () => {
16 | expect(index.DragTab).toBeDefined();
17 | });
18 |
19 | test('Panel need to be defined', () => {
20 | expect(index.Panel).toBeDefined();
21 | });
22 |
23 | test('PanelList need to be defined', () => {
24 | expect(index.PanelList).toBeDefined();
25 | });
26 |
27 | test('ExtraButton need to be defined', () => {
28 | expect(index.ExtraButton).toBeDefined();
29 | });
30 |
31 | test('TabListStyle need to be defined', () => {
32 | expect(index.styled.TabListStyle).toBeDefined();
33 | });
34 |
35 | test('ActionButtonStyle need to be defined', () => {
36 | expect(index.styled.ActionButtonStyle).toBeDefined();
37 | });
38 |
39 | test('TabStyle need to be defined', () => {
40 | expect(index.styled.TabStyle).toBeDefined();
41 | });
42 |
43 | test('PanelStyle need to be defined', () => {
44 | expect(index.styled.PanelStyle).toBeDefined();
45 | });
46 |
--------------------------------------------------------------------------------
/test/shim.js:
--------------------------------------------------------------------------------
1 | global.requestAnimationFrame = (callback) => {
2 | setTimeout(callback, 0);
3 | };
4 |
--------------------------------------------------------------------------------
/test/tabListTest.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TabListStyle } from '../src/TabList';
3 | import { mount } from 'enzyme';
4 | import toJson from 'enzyme-to-json';
5 | import 'jest-styled-components';
6 | import styled from 'styled-components';
7 | import {BulletIcon, RightIcon} from '../src/IconSvg';
8 |
9 | const tabListTest = (type, TabListComponent, TabComponent) => {
10 | const shareComponent = props => (
11 |
12 | Tab1
13 | Tab2
14 |
15 | );
16 |
17 | test(`render ${type} List`, () => {
18 | const component = mount(
19 |
20 | Tab1
21 |
22 | );
23 | expect(toJson(component)).toMatchSnapshot();
24 | })
25 |
26 | test('custom ListStyle style', () => {
27 | const CustomListStyle = styled(TabListStyle)`
28 | background-color: red;
29 | `;
30 | const component = mount(
31 |
32 | Tab1
33 |
34 | );
35 | expect(toJson(component)).toMatchSnapshot();
36 | })
37 |
38 | describe('showModalButton', () => {
39 | const returnMountedButton = showModalButton => {
40 | const component = mount(shareComponent({showModalButton}));
41 | return component.find(BulletIcon);
42 | }
43 |
44 | it('default: true => show', () => {
45 | expect(returnMountedButton(true)).toHaveLength(1);
46 | })
47 | it('false', () => {
48 | expect(returnMountedButton(false)).toHaveLength(0);
49 | })
50 | describe('number', () => {
51 | it('2', () => {
52 | expect(returnMountedButton(2)).toHaveLength(1);
53 | })
54 | it('100', () => {
55 | expect(returnMountedButton(100)).toHaveLength(0);
56 | })
57 |
58 | it('show modal button when new tab is added', () => {
59 | const component = mount(shareComponent({showModalButton: 4}));
60 | expect(component.html().includes('svg')).toEqual(false);
61 | component.setProps({children: [
62 | Tab1 ,
63 | Tab2 ,
64 | Tab3 ,
65 | Tab4
66 | ]});
67 | expect(component.html().includes('svg')).toEqual(true);
68 | })
69 | })
70 | })
71 |
72 | describe('showArrowButton', () => {
73 | const returnMountedButton = showArrowButton => {
74 | const component = mount(shareComponent({showArrowButton}));
75 | return component.find(RightIcon);
76 | }
77 | // because in test env it's containerWidth = 0, it always show arrow
78 | it('auto', () => {
79 | expect(returnMountedButton('auto')).toHaveLength(1);
80 | })
81 |
82 | it('true', () => {
83 | expect(returnMountedButton(true)).toHaveLength(1);
84 | })
85 |
86 | it('false', () => {
87 | expect(returnMountedButton(false)).toHaveLength(0);
88 | })
89 | })
90 | // the reason to test closable button at TabList not at Tab component
91 | // is because `DragTab` need to be wrapped by `DragTabList`
92 | it('click close button', () => {
93 | const mockTabChange = jest.fn();
94 | const component = mount(
95 |
96 | Tab1
97 | Tab2
98 |
99 | );
100 | const btn1 = component.find('CloseButton').at(1);
101 | btn1.simulate('click');
102 | expect(mockTabChange).toBeCalled();
103 | expect(mockTabChange.mock.calls[0][0]).toEqual({type: 'delete', index: 1});
104 | })
105 | }
106 |
107 | export default tabListTest;
--------------------------------------------------------------------------------
/test/utils/countTab.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Tabs, DragTabList, DragTab, TabList, Tab, PanelList, Panel} from '../../src';
3 | import {mount} from 'enzyme';
4 | import countTab from '../../src/utils/countTab';
5 |
6 | test('count DragTab', () => {
7 | const count = countTab(mount(
8 |
9 |
10 | DragTab1
11 | DragTab2
12 | DragTab3
13 |
14 |
15 | Content1
16 | Content2
17 | Content3
18 |
19 |
20 | ));
21 | expect(count).toEqual(3);
22 | })
23 |
24 | test('count Tab', () => {
25 | const count = countTab(mount(
26 |
27 |
28 | Tab1
29 | Tab2
30 | Tab3
31 |
32 |
33 | ));
34 | expect(count).toEqual(3);
35 | })
36 |
--------------------------------------------------------------------------------
/test/utils/isType.test.js:
--------------------------------------------------------------------------------
1 | import {DragTabList, DragTab, TabList, Tab} from '../../src';
2 | import {isTabList, isTab, isNumber} from '../../src/utils/isType';
3 |
4 | describe('isTabList', () => {
5 | it('DragTabList', () => {
6 | expect(isTabList({type: DragTabList})).toEqual(true);
7 | });
8 | it('TabList', () => {
9 | expect(isTabList({type: TabList})).toEqual(true);
10 | });
11 | })
12 |
13 | describe('isTab', () => {
14 | it('DragTab', () => {
15 | expect(isTab({type: DragTab})).toEqual(true);
16 | });
17 | it('Tab', () => {
18 | expect(isTab({type: Tab})).toEqual(true);
19 | });
20 | })
21 |
22 | it('isNumber', () => {
23 | expect(isNumber(1)).toEqual(true);
24 | expect(isNumber('2')).toEqual(true);
25 | expect(isNumber('sdfsdf')).toEqual(false);
26 | })
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | devtool: 'eval-source-map',
7 | entry: {
8 | root: [
9 | 'babel-polyfill',
10 | 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
11 | './docs/root'
12 | ]
13 | },
14 | output: {
15 | path: path.join(__dirname, 'dist'),
16 | filename: '[name].js',
17 | publicPath: '/static/'
18 | },
19 | resolve: {
20 | extensions: ['.js'],
21 | alias: {
22 | 'react-tabtab/lib': path.resolve(__dirname, 'src/')
23 | }
24 | },
25 | plugins: [
26 | new webpack.HotModuleReplacementPlugin(),
27 | new webpack.NoEmitOnErrorsPlugin(),
28 | new webpack.DefinePlugin({
29 | 'process.env': {
30 | NODE_ENV: JSON.stringify("development")
31 | }
32 | }),
33 | new HtmlWebpackPlugin({
34 | inject: false,
35 | template: 'docs/index.ejs',
36 | title: 'react-tabtab'
37 | })
38 | ],
39 | module: {
40 | loaders: [
41 | {
42 | test: /\.js$/,
43 | loader: 'babel-loader',
44 | exclude: /node_modules/
45 | }
46 | ]
47 | }
48 | }
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: {
7 | root: [
8 | 'babel-polyfill',
9 | './docs/root'
10 | ]
11 | },
12 | output: {
13 | path: path.join(__dirname, '_gh-pages'),
14 | filename: '[name].[chunkhash].js'
15 | },
16 | resolve: {
17 | extensions: ['.js'],
18 | alias: {
19 | 'react-tabtab/lib': path.resolve(__dirname, 'src/')
20 | }
21 | },
22 | externals: {
23 | 'react': "React",
24 | 'react-dom': "ReactDOM"
25 | },
26 | plugins: [
27 | new webpack.DefinePlugin({
28 | 'process.env': {
29 | NODE_ENV: JSON.stringify("production")
30 | }
31 | }),
32 | new webpack.optimize.CommonsChunkPlugin({
33 | name: 'common',
34 | filename: 'common.js',
35 | minChunk: 2,
36 | }),
37 | new HtmlWebpackPlugin({
38 | inject: false,
39 | production: true,
40 | template: 'docs/index.ejs',
41 | title: 'React tabtab - Make your react tab dance',
42 | googleAnalytics: {
43 | trackingId: 'UA-54035195-6',
44 | pageViewOnLoad: true
45 | }
46 | }),
47 | new webpack.optimize.UglifyJsPlugin()
48 | ],
49 | module: {
50 | loaders: [
51 | {
52 | test: /\.js$/,
53 | loader: 'babel-loader',
54 | exclude: /node_modules/
55 | }
56 | ]
57 | }
58 | }
--------------------------------------------------------------------------------