UNPKG

@momentum-ui/react

Version:

Cisco Momentum UI framework for ReactJs applications

220 lines (186 loc) 5.45 kB
/** @component tabs */ import React from 'react'; import PropTypes from 'prop-types'; class TabList extends React.Component { componentDidMount() { this.determineInitialFocus(); } determineInitialFocus = () => { const { children } = this.props; const { focus } = this.context; const childrenList = React.Children.toArray(children); if (focus > childrenList.length - 1 || focus < 0) { throw new Error(`focusIndex ${focus} is out of bound.`); } const focusIndex = !childrenList[focus].props.disabled ? focus : null; this.setFocus(focusIndex); this.context.onActivate(focusIndex); }; setFocus = index => { // Update state with selected index return this.context.onFocus(index); }; handleClick = (event, index, disabled) => { if(disabled) { event.preventDefault(); event.stopPropagation(); } this.setFocus(index); return this.context.onActivate(index); }; getIncludesFirstCharacter = (str, char) => str .charAt(0) .toLowerCase() .includes(char); setFocusByFirstCharacter = (char, currentIdx, length) => { const { children } = this.props; const newIndex = React.Children.toArray(children).reduce( (agg, child, idx, arr) => { const index = currentIdx + idx + 1 > length ? Math.abs(currentIdx + idx - length) : currentIdx + idx + 1; const label = arr[index].props.role === 'menuItem' ? arr[index].props.label : arr[index].props.heading; return !agg.length && !arr[index].props.disabled && this.getIncludesFirstCharacter(label, char) ? agg.concat(index) : agg; }, [] ); this.setFocus(newIndex[0]); }; handleListKeyPress = (e, idx, length, disabled) => { if(disabled) { e.preventDefault(); e.stopPropagation(); } let newIndex, clickEvent; const tgt = e.currentTarget; const char = e.key; let flag = false; const getNewIndex = (currentIndex, change) => { const getPossibleIndex = () => { if (currentIndex + change < 0) { return length; } else if (currentIndex + change > length) { return 0; } return currentIndex + change; }; const possibleIndex = getPossibleIndex(); return React.Children.toArray(this.props.children)[possibleIndex].props.disabled ? getNewIndex(possibleIndex, change) : possibleIndex; }; const isPrintableCharacter = str => { return str.length === 1 && str.match(/\S/); }; switch (e.which) { case 32: case 13: try { clickEvent = new MouseEvent('click', { view: window, bubbles: true, cancelable: true, }); } catch (err) { if (document.createEvent) { // DOM Level 3 for IE 9+ clickEvent = document.createEvent('MouseEvents'); clickEvent.initEvent('click', true, true); } } tgt.dispatchEvent(clickEvent); flag = true; break; case 38: case 37: newIndex = getNewIndex(idx, -1); this.setFocus(newIndex); flag = true; break; case 39: case 40: newIndex = getNewIndex(idx, 1); this.setFocus(newIndex); flag = true; break; case 33: case 36: this.setFocus(0); flag = true; break; case 34: case 35: this.setFocus(length); flag = true; break; default: if (isPrintableCharacter(char)) { this.setFocusByFirstCharacter(char, idx, length); flag = true; } break; } if (flag) { e.stopPropagation(); e.preventDefault(); } }; render() { const { children, role } = this.props; const { activeIndex, focus, tabType } = this.context; const setTabs = () => React.Children.map(children, (child, idx) => { let arrLength = children.length - 1; const disabled = child.props.disabled; return React.cloneElement(child, { active: activeIndex === idx, focus: focus === idx, onPress: (e) => this.handleClick(e, idx, disabled), onKeyDown: e => this.handleListKeyPress(e, idx, arrLength, disabled), refName: 'navLink', isType: tabType, }); }); return ( <ul className='md-tab__list' role={role} > {setTabs()} </ul> ); } } TabList.propTypes = { /** @prop Children nodes to render inside TabList | null */ children: PropTypes.node, /** * @prop ARIA role for the Nav, in the context of a TabContainer, the default will * be set to "tablist", but can be overridden by the Nav when set explicitly. * * When the role is set to "tablist" NavItem focus is managed according to * the ARIA authoring practices for tabs: * https://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#tabpanel | 'TabList' */ role: PropTypes.string }; TabList.defaultProps = { children: null, role: 'tablist' }; TabList.contextTypes = { onFocus: PropTypes.func, focus: PropTypes.number, activeIndex: PropTypes.number, onActivate: PropTypes.func, tabType: PropTypes.string, }; TabList.displayName = 'TabList'; export default TabList;