UNPKG

11 kBJavaScriptView Raw
1import _extends from "@babel/runtime-corejs2/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime-corejs2/helpers/esm/objectWithoutPropertiesLoose";
3import _inheritsLoose from "@babel/runtime-corejs2/helpers/esm/inheritsLoose";
4import classNames from 'classnames';
5import keycode from 'keycode';
6import React, { cloneElement } from 'react';
7import PropTypes from 'prop-types';
8import ReactDOM from 'react-dom';
9import all from 'prop-types-extra/lib/all';
10import warning from 'warning';
11import { bsClass, bsStyles, getClassSet, prefix, splitBsProps } from './utils/bootstrapUtils';
12import createChainedFunction from './utils/createChainedFunction';
13import ValidComponentChildren from './utils/ValidComponentChildren'; // TODO: Should we expose `<NavItem>` as `<Nav.Item>`?
14// TODO: This `bsStyle` is very unlike the others. Should we rename it?
15// TODO: `pullRight` and `pullLeft` don't render right outside of `navbar`.
16// Consider renaming or replacing them.
17
18var propTypes = {
19 /**
20 * Marks the NavItem with a matching `eventKey` as active. Has a
21 * higher precedence over `activeHref`.
22 */
23 activeKey: PropTypes.any,
24
25 /**
26 * Marks the child NavItem with a matching `href` prop as active.
27 */
28 activeHref: PropTypes.string,
29
30 /**
31 * NavItems are be positioned vertically.
32 */
33 stacked: PropTypes.bool,
34 justified: all(PropTypes.bool, function (_ref) {
35 var justified = _ref.justified,
36 navbar = _ref.navbar;
37 return justified && navbar ? Error('justified navbar `Nav`s are not supported') : null;
38 }),
39
40 /**
41 * A callback fired when a NavItem is selected.
42 *
43 * ```js
44 * function (
45 * Any eventKey,
46 * SyntheticEvent event?
47 * )
48 * ```
49 */
50 onSelect: PropTypes.func,
51
52 /**
53 * ARIA role for the Nav, in the context of a TabContainer, the default will
54 * be set to "tablist", but can be overridden by the Nav when set explicitly.
55 *
56 * When the role is set to "tablist" NavItem focus is managed according to
57 * the ARIA authoring practices for tabs:
58 * https://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#tabpanel
59 */
60 role: PropTypes.string,
61
62 /**
63 * Apply styling an alignment for use in a Navbar. This prop will be set
64 * automatically when the Nav is used inside a Navbar.
65 */
66 navbar: PropTypes.bool,
67
68 /**
69 * Float the Nav to the right. When `navbar` is `true` the appropriate
70 * contextual classes are added as well.
71 */
72 pullRight: PropTypes.bool,
73
74 /**
75 * Float the Nav to the left. When `navbar` is `true` the appropriate
76 * contextual classes are added as well.
77 */
78 pullLeft: PropTypes.bool
79};
80var defaultProps = {
81 justified: false,
82 pullRight: false,
83 pullLeft: false,
84 stacked: false
85};
86var contextTypes = {
87 $bs_navbar: PropTypes.shape({
88 bsClass: PropTypes.string,
89 onSelect: PropTypes.func
90 }),
91 $bs_tabContainer: PropTypes.shape({
92 activeKey: PropTypes.any,
93 onSelect: PropTypes.func.isRequired,
94 getTabId: PropTypes.func.isRequired,
95 getPaneId: PropTypes.func.isRequired
96 })
97};
98
99var Nav =
100/*#__PURE__*/
101function (_React$Component) {
102 _inheritsLoose(Nav, _React$Component);
103
104 function Nav() {
105 return _React$Component.apply(this, arguments) || this;
106 }
107
108 var _proto = Nav.prototype;
109
110 _proto.componentDidUpdate = function componentDidUpdate() {
111 var _this = this;
112
113 if (!this._needsRefocus) {
114 return;
115 }
116
117 this._needsRefocus = false;
118 var children = this.props.children;
119
120 var _this$getActiveProps = this.getActiveProps(),
121 activeKey = _this$getActiveProps.activeKey,
122 activeHref = _this$getActiveProps.activeHref;
123
124 var activeChild = ValidComponentChildren.find(children, function (child) {
125 return _this.isActive(child, activeKey, activeHref);
126 });
127 var childrenArray = ValidComponentChildren.toArray(children);
128 var activeChildIndex = childrenArray.indexOf(activeChild);
129 var childNodes = ReactDOM.findDOMNode(this).children;
130 var activeNode = childNodes && childNodes[activeChildIndex];
131
132 if (!activeNode || !activeNode.firstChild) {
133 return;
134 }
135
136 activeNode.firstChild.focus();
137 };
138
139 _proto.getActiveProps = function getActiveProps() {
140 var tabContainer = this.context.$bs_tabContainer;
141
142 if (tabContainer) {
143 process.env.NODE_ENV !== "production" ? warning(this.props.activeKey == null && !this.props.activeHref, 'Specifying a `<Nav>` `activeKey` or `activeHref` in the context of ' + 'a `<TabContainer>` is not supported. Instead use `<TabContainer ' + ("activeKey={" + this.props.activeKey + "} />`.")) : void 0;
144 return tabContainer;
145 }
146
147 return this.props;
148 };
149
150 _proto.getNextActiveChild = function getNextActiveChild(offset) {
151 var _this2 = this;
152
153 var children = this.props.children;
154 var validChildren = children.filter(function (child) {
155 return child.props.eventKey != null && !child.props.disabled;
156 });
157
158 var _this$getActiveProps2 = this.getActiveProps(),
159 activeKey = _this$getActiveProps2.activeKey,
160 activeHref = _this$getActiveProps2.activeHref;
161
162 var activeChild = ValidComponentChildren.find(children, function (child) {
163 return _this2.isActive(child, activeKey, activeHref);
164 }); // This assumes the active child is not disabled.
165
166 var activeChildIndex = validChildren.indexOf(activeChild);
167
168 if (activeChildIndex === -1) {
169 // Something has gone wrong. Select the first valid child we can find.
170 return validChildren[0];
171 }
172
173 var nextIndex = activeChildIndex + offset;
174 var numValidChildren = validChildren.length;
175
176 if (nextIndex >= numValidChildren) {
177 nextIndex = 0;
178 } else if (nextIndex < 0) {
179 nextIndex = numValidChildren - 1;
180 }
181
182 return validChildren[nextIndex];
183 };
184
185 _proto.getTabProps = function getTabProps(child, tabContainer, navRole, active, onSelect) {
186 var _this3 = this;
187
188 if (!tabContainer && navRole !== 'tablist') {
189 // No tab props here.
190 return null;
191 }
192
193 var _child$props = child.props,
194 id = _child$props.id,
195 controls = _child$props['aria-controls'],
196 eventKey = _child$props.eventKey,
197 role = _child$props.role,
198 onKeyDown = _child$props.onKeyDown,
199 tabIndex = _child$props.tabIndex;
200
201 if (tabContainer) {
202 process.env.NODE_ENV !== "production" ? warning(!id && !controls, 'In the context of a `<TabContainer>`, `<NavItem>`s are given ' + 'generated `id` and `aria-controls` attributes for the sake of ' + 'proper component accessibility. Any provided ones will be ignored. ' + 'To control these attributes directly, provide a `generateChildId` ' + 'prop to the parent `<TabContainer>`.') : void 0;
203 id = tabContainer.getTabId(eventKey);
204 controls = tabContainer.getPaneId(eventKey);
205 }
206
207 if (navRole === 'tablist') {
208 role = role || 'tab';
209 onKeyDown = createChainedFunction(function (event) {
210 return _this3.handleTabKeyDown(onSelect, event);
211 }, onKeyDown);
212 tabIndex = active ? tabIndex : -1;
213 }
214
215 return {
216 id: id,
217 role: role,
218 onKeyDown: onKeyDown,
219 'aria-controls': controls,
220 tabIndex: tabIndex
221 };
222 };
223
224 _proto.handleTabKeyDown = function handleTabKeyDown(onSelect, event) {
225 var nextActiveChild;
226
227 switch (event.keyCode) {
228 case keycode.codes.left:
229 case keycode.codes.up:
230 nextActiveChild = this.getNextActiveChild(-1);
231 break;
232
233 case keycode.codes.right:
234 case keycode.codes.down:
235 nextActiveChild = this.getNextActiveChild(1);
236 break;
237
238 default:
239 // It was a different key; don't handle this keypress.
240 return;
241 }
242
243 event.preventDefault();
244
245 if (onSelect && nextActiveChild && nextActiveChild.props.eventKey != null) {
246 onSelect(nextActiveChild.props.eventKey);
247 }
248
249 this._needsRefocus = true;
250 };
251
252 _proto.isActive = function isActive(_ref2, activeKey, activeHref) {
253 var props = _ref2.props;
254
255 if (props.active || activeKey != null && props.eventKey === activeKey || activeHref && props.href === activeHref) {
256 return true;
257 }
258
259 return props.active;
260 };
261
262 _proto.render = function render() {
263 var _extends2,
264 _this4 = this;
265
266 var _this$props = this.props,
267 stacked = _this$props.stacked,
268 justified = _this$props.justified,
269 onSelect = _this$props.onSelect,
270 propsRole = _this$props.role,
271 propsNavbar = _this$props.navbar,
272 pullRight = _this$props.pullRight,
273 pullLeft = _this$props.pullLeft,
274 className = _this$props.className,
275 children = _this$props.children,
276 props = _objectWithoutPropertiesLoose(_this$props, ["stacked", "justified", "onSelect", "role", "navbar", "pullRight", "pullLeft", "className", "children"]);
277
278 var tabContainer = this.context.$bs_tabContainer;
279 var role = propsRole || (tabContainer ? 'tablist' : null);
280
281 var _this$getActiveProps3 = this.getActiveProps(),
282 activeKey = _this$getActiveProps3.activeKey,
283 activeHref = _this$getActiveProps3.activeHref;
284
285 delete props.activeKey; // Accessed via this.getActiveProps().
286
287 delete props.activeHref; // Accessed via this.getActiveProps().
288
289 var _splitBsProps = splitBsProps(props),
290 bsProps = _splitBsProps[0],
291 elementProps = _splitBsProps[1];
292
293 var classes = _extends({}, getClassSet(bsProps), (_extends2 = {}, _extends2[prefix(bsProps, 'stacked')] = stacked, _extends2[prefix(bsProps, 'justified')] = justified, _extends2));
294
295 var navbar = propsNavbar != null ? propsNavbar : this.context.$bs_navbar;
296 var pullLeftClassName;
297 var pullRightClassName;
298
299 if (navbar) {
300 var navbarProps = this.context.$bs_navbar || {
301 bsClass: 'navbar'
302 };
303 classes[prefix(navbarProps, 'nav')] = true;
304 pullRightClassName = prefix(navbarProps, 'right');
305 pullLeftClassName = prefix(navbarProps, 'left');
306 } else {
307 pullRightClassName = 'pull-right';
308 pullLeftClassName = 'pull-left';
309 }
310
311 classes[pullRightClassName] = pullRight;
312 classes[pullLeftClassName] = pullLeft;
313 return React.createElement("ul", _extends({}, elementProps, {
314 role: role,
315 className: classNames(className, classes)
316 }), ValidComponentChildren.map(children, function (child) {
317 var active = _this4.isActive(child, activeKey, activeHref);
318
319 var childOnSelect = createChainedFunction(child.props.onSelect, onSelect, navbar && navbar.onSelect, tabContainer && tabContainer.onSelect);
320 return cloneElement(child, _extends({}, _this4.getTabProps(child, tabContainer, role, active, childOnSelect), {
321 active: active,
322 activeKey: activeKey,
323 activeHref: activeHref,
324 onSelect: childOnSelect
325 }));
326 }));
327 };
328
329 return Nav;
330}(React.Component);
331
332Nav.propTypes = propTypes;
333Nav.defaultProps = defaultProps;
334Nav.contextTypes = contextTypes;
335export default bsClass('nav', bsStyles(['tabs', 'pills'], Nav));
\No newline at end of file