Skip to content

Commit

Permalink
Merge pull request #88 from ctxhou/async
Browse files Browse the repository at this point in the history
Support async panel
  • Loading branch information
ctxhou authored Apr 24, 2018
2 parents 1dfeeee + 7f78af3 commit fe4cc7d
Show file tree
Hide file tree
Showing 8 changed files with 436 additions and 15 deletions.
131 changes: 125 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@
* **Mobile supported** — Touch support. Easy to use on mobile device
* **Draggable tab** — Support drag and drop tab
* **Add & Delete** — Tab can be added and deleted
* **Async content** — Lazy load panel content
* **Customizable style** — Based on `styled-components`, super easy to customize tab style
* **API based** — All actions are controllable
* **ARIA accessible**

## Table of Contents

<!-- toc -->

- [Installation](#installation)
- [Usage](#usage)
* [Basic Example:](#basic-example)
* [Draggable Example](#draggable-example)
* [Minimal setup](#minimal-setup)
* [Draggable tab](#draggable-tab)
* [Async Panel](#async-panel)
* [Another Example](#another-example)
- [Components / Api](#components--api)
* [&lt;Tabs /&gt;](#lttabs-gt)
Expand All @@ -44,6 +47,7 @@
* [&lt;DragTab/ &gt;](#ltdragtab-gt)
* [&lt;PanelList/ &gt;](#ltpanellist-gt)
* [&lt;Panel /&gt;](#ltpanel-gt)
* [&lt;AsyncPanel /&gt;](#ltasyncpanel-gt)
- [Customize style](#customize-style)
* [Use current style](#use-current-style)
* [Make your own style](#make-your-own-style)
Expand Down Expand Up @@ -96,7 +100,10 @@ You can reference [standalone.html](https://github.com/ctxhou/react-tabtab/blob/

## Usage

### Basic Example:
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`.
All the actions are api based. It means there is `no state` in the component. Developers have full control.

### Minimal setup

```js
import React, {Component} from 'react';
Expand All @@ -123,9 +130,9 @@ class Basic extends Component {
ReactDOM.render(<Basic/>, document.getElementById('root'));
```

It's simple to use. Zero configuration and it works well !
It's simple to use. Zero configuration!

### Draggable Example
### Draggable tab

```js
import React, {Component} from 'react';
Expand Down Expand Up @@ -175,7 +182,7 @@ ReactDOM.render(<Basic/>, document.getElementById('root'));

Based on above example, the different to implement `normal tab` or `drag tab` is using different wrapper and child.

And all the actions is 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.
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.

**normal tab**

Expand Down Expand Up @@ -203,6 +210,70 @@ And all the actions is controllable. You can customize your switch action. But i
</Tabs>
```

### Async Panel

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.
Moreover, you can mix lazy load panel with normal panel!

```js
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {Tabs, TabList, Tab, PanelList, AsyncPanel, Panel} from 'react-tabtab';

function loadContentFunc(callback) {
setTimeout(() => {
callback(null, [
{product: 'json'},
{product: 'joseph'}
]);
}, 100);
}

// You also can provide promise as return function:
// function loadContentFunc() {
// return fetch('/products')
// .then(resp => resp.json())
// .then(data => data);
// }

class AsyncTab extends Component {
render() {
return (
<Tabs>
<TabList>
<Tab>Tab1</Tab>
<Tab>Tab2</Tab>
</TabList>
<PanelList>
<Panel>Content1</Panel>
<AsyncPanel loadContent={loadContentFunc}
render={data => (<div>{JSON.stringify(data)}</div>)}
renderLoading={() => (<div>Loading...</div>)}
cache={true}
/>
</PanelList>
</Tabs>
)
}
}

ReactDOM.render(<AsyncTab/>, document.getElementById('root'));
```

To implement lazy loading, use `AsyncPanel` to wrap your panel content. Remember to provide `loadContent`, `render`, `renderLoading` these 3 props.

In `loadContent` props, both `callback` and `promise` type are supported.

If you use `callback`, remember to call `callback` when finish async loading.

If you use `promise`, need to return promise action.

When data is loading, the panel content will show `renderLoading` component.

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.

Live example: [Link](https://ctxhou.github.io/react-tabtab/#async)

### Another Example

Except drag and drop tab, `react-tabtab` also support other usable application, like:
Expand Down Expand Up @@ -374,6 +445,54 @@ Use to wrap `<Panel/>`

Tab content.

### &lt;AsyncPanel /&gt;

Lazy loading panel content.

<table>
<tbody>
<tr>
<th>props</th>
<th>type</th>
<th>default</th>
<th></th>
</tr>
<tr>
<td>loadContent <b>*</b></td>
<td>
<code>(cb) => cb(error, data)</code> or <br/>
<code>(cb) => Promise
</td>
<td>null</td>
<td>when loadContent finish, call the callback or you can return promise</td>
</tr>
<tr>
<td>render <b>*</b></td>
<td>
<code>(data) => Component</code>
</td>
<td>null</td>
<td>when finish loading data, render this component</td>
</tr>
<tr>
<td>renderLoading <b>*</b></td>
<td>
<code>() => Component</code>
</td>
<td>null</td>
<td>when it is loading data, render this component</td>
</tr>
<tr>
<td>cache</td>
<td>
<code>boolean</code>
</td>
<td>true</td>
<td>should cache the data</td>
</tr>
</tbody>
</table>

## Customize style

`react-tabtab` is based on `styled-components`. Therefore, it's super easy to customize the tab style.
Expand Down
56 changes: 56 additions & 0 deletions docs/components/Async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, {Component} from 'react';
import {Tabs, TabList, Tab, PanelList, AsyncPanel, Panel} from '../../src';
import {Facebook} from 'react-content-loader'
const MyFacebookLoader = () => <Facebook />

function fakeCbFetch(cb) {
setTimeout(() => {
cb(null, {
content: 'hi'
});
}, 1500);
}

function fakePromiseFetch() {
return new Promise((res) => {
setTimeout(() => {
res({
content: 'promise fetched'
});
}, 1500);
})
}

export default class Basic extends Component {
render() {
return (
<Tabs customStyle={this.props.customStyle}>
<TabList>
<Tab>Normal panel</Tab>
<Tab>Callback fetch panel</Tab>
<Tab>Promise fetch panel</Tab>
<Tab>fetch without cache</Tab>
</TabList>
<PanelList>
<Panel>
Normal tab.
You can mix normal panel with async panel
</Panel>
<AsyncPanel loadContent={fakeCbFetch}
render={data => (<div>{JSON.stringify(data)}</div>)}
renderLoading={() => (<MyFacebookLoader/>)}
/>
<AsyncPanel loadContent={fakePromiseFetch}
render={data => (<div>{JSON.stringify(data)}</div>)}
renderLoading={() => (<MyFacebookLoader />)}
/>
<AsyncPanel loadContent={fakePromiseFetch}
render={data => (<div>{JSON.stringify(data)}</div>)}
renderLoading={() => (<MyFacebookLoader />)}
cache={false}
/>
</PanelList>
</Tabs>
)
}
}
8 changes: 8 additions & 0 deletions docs/root.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Draggable from './components/Draggable';
import AddAndClose from './components/AddAndClose';
import Modal from './components/Modal';
import Complicated from './components/Complicated';
import Async from './components/Async';
import themes from './themes';

const themeOptions = Object.keys(themes).map(theme => {
Expand Down Expand Up @@ -57,6 +58,13 @@ const examples = [
Component: AddAndClose,
source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/AddAndClose.js'
},
{
title: 'Async tab',
anchor: 'async',
description: 'Lazy load the tab content',
Component: Async,
source: 'https://github.com/ctxhou/react-tabtab/blob/master/docs/components/Async.js'
},
{
title: 'Modal View',
anchor: 'modal',
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"enzyme-adapter-react-15": "^1.0.5",
"invariant": "^2.2.2",
"prop-types": "^15.6.0",
"react-poppop": "^1.3.0",
"react-poppop": "^1.4.0",
"react-sortable-hoc": "^0.6.8",
"react-test-renderer": "^16.0.0",
"styled-components": "^2.2.3"
Expand Down Expand Up @@ -101,6 +101,7 @@
"precommit-hook-eslint": "^3.0.0",
"prettier": "^1.7.4",
"react": "^16.0.0",
"react-content-loader": "^3.1.1",
"react-dom": "^16.0.0",
"react-icons": "^2.2.7",
"react-select": "^1.0.0-rc.10",
Expand Down
95 changes: 95 additions & 0 deletions src/AsyncPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// @flow
import * as React from 'react'
import Panel from './Panel';

type Props = {
loadContent: (cb: Function) => void,
render: (data: any) => void,
renderLoading: () => void,
CustomPanelStyle: () => void,
active: boolean,
index: number,
cache: boolean
};

type State = {
isLoading: boolean,
data: any
};

export default class AsyncPanelComponent extends React.PureComponent<Props, State> {
static defaultProps = {
cache: true
};

cacheData: any;

constructor(props: Props) {
super(props);
(this: any).loadPanel = this.loadPanel.bind(this);
(this: any).cacheData = undefined;
this.state = {
isLoading: false,
data: undefined
};
}

componentDidMount() {
if (this.props.active)
this.loadPanel();
}

componentWillReceiveProps(nextProps: Props) {
if (nextProps.active)
this.loadPanel();
}

loadPanel() {
const {loadContent, cache} = this.props;
if (cache && this.cacheData) {
this.setState({
isLoading: false,
data: this.cacheData
});
return;
}
const callback = (err, data) => {
if (err) {
console.log('React-Tabtab async panel error:', err);
}
if (cache) {
this.cacheData = data;
}
this.setState({
isLoading: false,
data
});
}
const promise = loadContent(callback);
if (promise) {
promise.then(
(data) => callback(null, data),
(err) => callback(err)
);
}
if (!this.state.isLoading) {
this.setState({isLoading: true});
}
}

render() {
const {render, renderLoading, CustomPanelStyle, active, index} = this.props;
const {isLoading, data} = this.state;
let content;
if (isLoading) {
content = renderLoading();
} else {
content = render(data);
}
return (
<Panel {...{CustomPanelStyle, active, index}}>
{content}
</Panel>
)
}
}
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import DragTabList from './DragTabList';
import DragTab from './DragTab';
import PanelList from './PanelList';
import Panel, {PanelStyle} from './Panel';
import AsyncPanel from './AsyncPanel';
import ExtraButton from './ExtraButton';

const styled = {TabListStyle, ActionButtonStyle, TabStyle, PanelStyle};
Expand All @@ -17,6 +18,7 @@ const defaultOutput = {
DragTab,
PanelList,
Panel,
AsyncPanel,
ExtraButton,
styled
}
Expand Down
Loading

0 comments on commit fe4cc7d

Please sign in to comment.