1 |
|
2 |
|
3 |
|
4 |
|
5 | import React from "react";
|
6 | import KeyCode from "./KeyCode";
|
7 | import TabPane from "./TabPane";
|
8 | import classnames from "classnames";
|
9 | import TabContent from "./TabContent";
|
10 | import ScrollableInkTabBar from "./ScrollableInkTabBar";
|
11 | import PropTypes from 'prop-types';
|
12 | import createClass from 'create-react-class';
|
13 | import Icon from 'bee-icon';
|
14 |
|
15 | function noop() {}
|
16 |
|
17 | function getDefaultActiveKey(props) {
|
18 | let activeKey;
|
19 | React.Children.forEach(props.children, child => {
|
20 | if (child && !activeKey && !child.props.disabled) {
|
21 | activeKey = child.key;
|
22 | }
|
23 | });
|
24 | return activeKey;
|
25 | }
|
26 |
|
27 | const Tabs = createClass({
|
28 | propTypes: {
|
29 | destroyInactiveTabPane: PropTypes.bool,
|
30 | renderTabBar: PropTypes.func.isRequired,
|
31 | renderTabContent: PropTypes.func.isRequired,
|
32 | onChange: PropTypes.func,
|
33 | children: PropTypes.any,
|
34 | clsPrefix: PropTypes.string,
|
35 | className: PropTypes.string,
|
36 | tabBarPosition: PropTypes.string,
|
37 | style: PropTypes.object,
|
38 | tabBarStyle: PropTypes.oneOf([
|
39 | "simple",
|
40 | "fill",
|
41 | "primary",
|
42 | "upborder",
|
43 | "fade",
|
44 | "downborder",
|
45 | "trapezoid",
|
46 | "editable-card"
|
47 | ])
|
48 | },
|
49 |
|
50 | getDefaultProps() {
|
51 | return {
|
52 | clsPrefix: "u-tabs",
|
53 | destroyInactiveTabPane: false,
|
54 | onChange: noop,
|
55 | tabBarPosition: "top",
|
56 | style: {},
|
57 | renderTabContent: () => <TabContent />,
|
58 | renderTabBar: () => <ScrollableInkTabBar />,
|
59 | tabBarStyle: "simple",
|
60 | animated: true
|
61 | };
|
62 | },
|
63 |
|
64 | getInitialState() {
|
65 | const props = this.props;
|
66 | let activeKey;
|
67 | if ("activeKey" in props) {
|
68 | activeKey = props.activeKey;
|
69 | } else if ("defaultActiveKey" in props) {
|
70 | activeKey = props.defaultActiveKey;
|
71 | } else {
|
72 | activeKey = getDefaultActiveKey(props);
|
73 | }
|
74 | return {
|
75 | activeKey
|
76 | };
|
77 | },
|
78 |
|
79 | componentWillReceiveProps(nextProps) {
|
80 | if ("activeKey" in nextProps) {
|
81 | this.setState({
|
82 | activeKey: nextProps.activeKey
|
83 | });
|
84 | }
|
85 | },
|
86 |
|
87 | onTabClick(activeKey) {
|
88 | this.props.onTabClick && this.props.onTabClick(activeKey);
|
89 | if (this.tabBar.props.onTabClick) {
|
90 | this.tabBar.props.onTabClick(activeKey);
|
91 | }
|
92 | this.setActiveKey(activeKey);
|
93 | },
|
94 |
|
95 | onNavKeyDown(e) {
|
96 | const eventKeyCode = e.keyCode;
|
97 | if (eventKeyCode === KeyCode.RIGHT || eventKeyCode === KeyCode.DOWN) {
|
98 | e.preventDefault();
|
99 | const nextKey = this.getNextActiveKey(true);
|
100 | this.onTabClick(nextKey);
|
101 | } else if (eventKeyCode === KeyCode.LEFT || eventKeyCode === KeyCode.UP) {
|
102 | e.preventDefault();
|
103 | const previousKey = this.getNextActiveKey(false);
|
104 | this.onTabClick(previousKey);
|
105 | }
|
106 | },
|
107 |
|
108 | setActiveKey(activeKey) {
|
109 | if (this.state.activeKey !== activeKey) {
|
110 | if (!("activeKey" in this.props)) {
|
111 | this.setState({
|
112 | activeKey
|
113 | });
|
114 | }
|
115 | this.props.onChange(activeKey);
|
116 | }
|
117 | },
|
118 |
|
119 | getNextActiveKey(next) {
|
120 | const activeKey = this.state.activeKey;
|
121 | const children = [];
|
122 | React.Children.forEach(this.props.children, c => {
|
123 | if (c && !c.props.disabled) {
|
124 | if (next) {
|
125 | children.push(c);
|
126 | } else {
|
127 | children.unshift(c);
|
128 | }
|
129 | }
|
130 | });
|
131 | const length = children.length;
|
132 | let ret = length && children[0].key;
|
133 | children.forEach((child, i) => {
|
134 | if (child.key === activeKey) {
|
135 | if (i === length - 1) {
|
136 | ret = children[0].key;
|
137 | } else {
|
138 | ret = children[i + 1].key;
|
139 | }
|
140 | }
|
141 | });
|
142 | return ret;
|
143 | },
|
144 | onPrevClick(e){
|
145 | this.props.onPrevClick && this.props.onPrevClick(e)
|
146 | },
|
147 | onNextClick(e){
|
148 | this.props.onNextClick && this.props.onNextClick(e)
|
149 | },
|
150 | createNewTab(targetKey){
|
151 | const { onEdit } = this.props;
|
152 | if (onEdit) {
|
153 | onEdit(targetKey, 'add');
|
154 | }
|
155 | },
|
156 | removeTab(targetKey, e){
|
157 | e.stopPropagation();
|
158 | if (!targetKey) {
|
159 | return;
|
160 | }
|
161 |
|
162 | const { onEdit } = this.props;
|
163 | if (onEdit) {
|
164 | onEdit(targetKey, 'remove');
|
165 | }
|
166 | },
|
167 |
|
168 | render() {
|
169 | const props = this.props;
|
170 | const {
|
171 | activeKey,
|
172 | defaultActiveKey,
|
173 | clsPrefix,
|
174 | tabBarPosition,
|
175 | className,
|
176 | renderTabContent,
|
177 | renderTabBar,
|
178 | tabBarStyle,
|
179 | extraContent,
|
180 | animated,
|
181 | tabIndex,
|
182 | children,
|
183 | hideAdd,
|
184 | scrollAnimated,
|
185 | inkBarAnimated,
|
186 | useTransform3d,
|
187 | destroyInactiveTabPane,
|
188 | onTabClick,
|
189 | onEdit,
|
190 | onNextClick,
|
191 | onPrevClick,
|
192 | onChange,
|
193 | ...others
|
194 | } = props;
|
195 |
|
196 | const cls = classnames({
|
197 | [clsPrefix]: true,
|
198 | [`${clsPrefix}-${tabBarPosition}`]: true,
|
199 | [className]: !!className,
|
200 | [`${clsPrefix}-${tabBarStyle}`]: true
|
201 | });
|
202 |
|
203 | const renderProps = {
|
204 | ...this.props,
|
205 | children: null,
|
206 | inkBarAnimated,
|
207 | extraContent: extraContent,
|
208 | className: cls,
|
209 | };
|
210 | if (renderTabBar) {
|
211 | this.tabBar = renderTabBar(renderProps, ScrollableInkTabBar);
|
212 | } else {
|
213 | this.tabBar = <ScrollableInkTabBar {...renderProps} />;
|
214 | }
|
215 |
|
216 | // only card type tabs can be added and closed
|
217 | let childrenWithClose = [],
|
218 | tabBarExtraContent = extraContent;
|
219 | if (tabBarStyle === 'editable-card') {
|
220 | childrenWithClose = [];
|
221 | React.Children.forEach(children, (child, index) => {
|
222 | if (!React.isValidElement(child)) return child;
|
223 | let { closable } = child.props;
|
224 | closable = typeof closable === 'undefined' ? true : closable;
|
225 | const closeIcon = closable ? (
|
226 | <Icon
|
227 | type="uf-close"
|
228 | className={`${clsPrefix}-close-x`}
|
229 | onClick={e => this.removeTab(child.key, e)}
|
230 | />
|
231 | ) : null;
|
232 | childrenWithClose.push(
|
233 | React.cloneElement(child, {
|
234 | tab: (
|
235 | <div className={closable ? undefined : `${clsPrefix}-tab-unclosable`}>
|
236 | {child.props.tab}
|
237 | {closeIcon}
|
238 | </div>
|
239 | ),
|
240 | key: child.key || index,
|
241 | }),
|
242 | );
|
243 | });
|
244 | // Add new tab handler
|
245 | if (!hideAdd) {
|
246 | tabBarExtraContent = (
|
247 | <span>
|
248 | <Icon type="uf-add-s-o" className={`${clsPrefix}-new-tab`} onClick={this.createNewTab} />
|
249 | {extraContent}
|
250 | </span>
|
251 | );
|
252 | }
|
253 | }
|
254 |
|
255 | const contents = [
|
256 | React.cloneElement(this.tabBar, {
|
257 | clsPrefix,
|
258 | key: "tabBar",
|
259 | onKeyDown: this.onNavKeyDown,
|
260 | tabBarPosition,
|
261 | extraContent: tabBarExtraContent,
|
262 | onTabClick: this.onTabClick,
|
263 | panels: childrenWithClose.length > 0 ? childrenWithClose : children,
|
264 | activeKey: this.state.activeKey,
|
265 | tabIndex,
|
266 | onPrevClick: this.onPrevClick,
|
267 | onNextClick: this.onNextClick,
|
268 | }),
|
269 | React.cloneElement(renderTabContent(), {
|
270 | clsPrefix,
|
271 | tabBarPosition,
|
272 | animated,
|
273 | activeKey: this.state.activeKey,
|
274 | destroyInactiveTabPane: props.destroyInactiveTabPane,
|
275 | children: props.children,
|
276 | // style: { height: '100%' },
|
277 | onChange: this.setActiveKey,
|
278 | key: "tabContent"
|
279 | })
|
280 | ];
|
281 | if (tabBarPosition === "bottom") {
|
282 | contents.reverse();
|
283 | }
|
284 | return (
|
285 | <div className={cls} style={props.style} {...others}>
|
286 | {contents}
|
287 | </div>
|
288 | );
|
289 | }
|
290 | });
|
291 |
|
292 | Tabs.TabPane = TabPane;
|
293 |
|
294 | export {Tabs};
|