@momentum-ui/react
Version:
Cisco Momentum UI framework for ReactJs applications
234 lines (198 loc) • 5.98 kB
JavaScript
/**@component accordion */
import React from 'react';
import PropTypes from 'prop-types';
class Accordion extends React.Component {
static displayName = 'Accordion';
state = {
activeIndices: this.props.initialActive || [],
focusIndicies: this.props.initialActiveFocus,
focus: false,
}
componentDidMount () {
const { focusIndicies } = this.state;
if(!this.verifyChildren()) {
throw new Error('Accordion should contain one or more AccordionGroup as children.');
}
if (focusIndicies) {
this.determineInitialFocus();
}
}
verifyChildren = () => {
const { children } = this.props;
const childrenArr = React.Children.toArray(children);
const status = childrenArr.reduce((status, child) => {
return status && child.type.displayName === 'AccordionGroup';
}, true);
return children && childrenArr.length && status;
}
determineInitialFocus = () => {
const nonDisabledIndex = React.Children.toArray(this.props.children).reduceRight((agg, child, idx) => {
return !child.props.disabled
? idx
: agg;
}, null);
this.setFocus(nonDisabledIndex);
}
handleClick = index => {
return this.props.multipleVisible
? this.setMultiple(index)
: this.setSelected(index);
}
setMultiple = index => {
let newValues;
const { onSelect } = this.props;
const { activeIndices, focusIndicies } = this.state;
const isActive = activeIndices.includes(index);
if (isActive) {
newValues = activeIndices.filter(v => v !== index);
} else {
newValues = activeIndices.concat(index);
}
if (focusIndicies) {
this.setFocus(index);
}
this.setState(() => {
onSelect && onSelect(newValues);
return { activeIndices: newValues };
});
}
setSelected = index => {
const { activeIndices, focusIndicies } = this.state;
const { children, onSelect } = this.props;
// Don't do anything if index is the same or outside of the bounds
if (activeIndices.includes(index) || index < 0 || index >= children.length)
return;
// Keep reference to last index for event handler
const last = activeIndices[0];
// Update state with selected index
this.setState(() => ({ activeIndices: [index] }));
if (focusIndicies) {
this.setFocus(index);
}
onSelect && onSelect(index, last);
}
handleKeyPress = (e, idx, length, disabled) => {
if(disabled) {
e.preventDefault();
e.stopPropagation();
return;
}
let newIndex, clickEvent;
const tgt = e.currentTarget;
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;
};
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:
break;
}
if (flag) {
e.stopPropagation();
e.preventDefault();
}
};
setFocus = index => {
this.setState({ focus: index });
};
render() {
const { children, className, showSeparator } = this.props;
const { activeIndices } = this.state;
const setAccordionGroups = React.Children.map(children, (child, idx) => {
return React.cloneElement(child, {
isExpanded: !child.props.disabled && activeIndices.includes(idx),
onClick: () => this.handleClick(idx),
onKeyDown: e => this.handleKeyPress(e, idx, children.length - 1 , child.props.disabled),
focus: this.state.focus === idx,
showSeparator,
});
});
return (
<div
className={
'md-accordion' +
`${(className && ` ${className}`) || ''}`
}
>
{setAccordionGroups}
</div>
);
}
}
Accordion.propTypes = {
/** @prop Children nodes to render inside Accordion | null */
children: PropTypes.node,
/** @prop Set to allow expansion of multiple AccordionGroups | false */
multipleVisible: PropTypes.bool,
/** @prop Handler to be called when the user selects Accordion | null */
onSelect: PropTypes.func,
/** @prop An array of indexes that are preselected | [] */
initialActive: PropTypes.array,
/** @prop Optional css class string | '' */
initialActiveFocus: PropTypes.bool,
/** @prop Set to disallow focus upon opening Accordion on load | true */
className: PropTypes.string,
/** @prop Optional underline under Accordion menu item | false */
showSeparator: PropTypes.bool,
};
Accordion.defaultProps = {
children: null,
multipleVisible: false,
onSelect: null,
initialActive: [],
initialActiveFocus: true,
className: '',
showSeparator: false,
};
export default Accordion;