UNPKG

reliance-react-checkbox-tree

Version:

Fork of checkbox tree in React by Jake Zatecky: https://github.com/jakezatecky/react-checkbox-tree.

329 lines (277 loc) 8.96 kB
import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import Button from './Button'; import NativeCheckbox from './NativeCheckbox'; import iconsShape from './shapes/iconsShape'; import languageShape from './shapes/languageShape'; class TreeNode extends React.PureComponent { static propTypes = { checked: PropTypes.number.isRequired, disabled: PropTypes.bool.isRequired, expandDisabled: PropTypes.bool.isRequired, expanded: PropTypes.bool.isRequired, icons: iconsShape.isRequired, isLeaf: PropTypes.bool.isRequired, isParent: PropTypes.bool.isRequired, label: PropTypes.node.isRequired, lang: languageShape.isRequired, optimisticToggle: PropTypes.bool.isRequired, showNodeIcon: PropTypes.bool.isRequired, treeId: PropTypes.string.isRequired, value: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, ]).isRequired, onCheck: PropTypes.func.isRequired, onExpand: PropTypes.func.isRequired, children: PropTypes.node, className: PropTypes.string, expandOnClick: PropTypes.bool, icon: PropTypes.node, showCheckbox: PropTypes.bool, title: PropTypes.string, onClick: PropTypes.func, }; static defaultProps = { children: null, className: null, expandOnClick: false, icon: null, showCheckbox: true, title: null, onClick: () => {}, }; constructor(props) { super(props); this.onCheck = this.onCheck.bind(this); this.onCheckboxKeyPress = this.onCheckboxKeyPress.bind(this); this.onCheckboxKeyUp = this.onCheckboxKeyUp.bind(this); this.onClick = this.onClick.bind(this); this.onExpand = this.onExpand.bind(this); } onCheck() { const { value, onCheck } = this.props; onCheck({ value, checked: this.getCheckState({ toggle: true }) }); } onCheckboxKeyPress(event) { const { which } = event; // Prevent browser scroll when pressing space on the checkbox if (which === 32) { event.preventDefault(); } } onCheckboxKeyUp(event) { const { keyCode } = event; if ([13, 32].includes(keyCode)) { this.onCheck(); } } onClick() { const { expandOnClick, isParent, value, onClick, } = this.props; // Auto expand if enabled if (isParent && expandOnClick) { this.onExpand(); } onClick({ value, checked: this.getCheckState({ toggle: false }) }); } onExpand() { const { expanded, value, onExpand } = this.props; onExpand({ value, expanded: !expanded }); } getCheckState({ toggle }) { const { checked, optimisticToggle } = this.props; // Toggle off state to checked if (checked === 0 && toggle) { return true; } // Node is already checked and we are not toggling if (checked === 1 && !toggle) { return true; } // Get/toggle partial state based on cascade model if (checked === 2) { return optimisticToggle; } return false; } renderCollapseButton() { const { expandDisabled, isLeaf, lang } = this.props; if (isLeaf) { return ( <span className="rct-collapse"> <span className="rct-icon" /> </span> ); } return ( <Button className="rct-collapse rct-collapse-btn" disabled={expandDisabled} title={lang.toggle} onClick={this.onExpand} > {this.renderCollapseIcon()} </Button> ); } renderCollapseIcon() { const { expanded, icons: { expandClose, expandOpen } } = this.props; if (!expanded) { return expandClose; } return expandOpen; } renderCheckboxIcon() { const { checked, icons: { uncheck, check, halfCheck } } = this.props; if (checked === 0) { return uncheck; } if (checked === 1) { return check; } return halfCheck; } renderNodeIcon() { const { expanded, icon, icons: { leaf, parentClose, parentOpen }, isLeaf, } = this.props; if (icon !== null) { return icon; } if (isLeaf) { return leaf; } if (!expanded) { return parentClose; } return parentOpen; } renderBareLabel(children) { const { onClick, title } = this.props; const clickable = onClick !== null; return ( <span className="rct-bare-label" title={title}> {clickable ? ( <span className="rct-node-clickable" onClick={this.onClick} onKeyPress={this.onClick} role="button" tabIndex={0} > {children} </span> ) : children} </span> ); } renderCheckboxLabel(children) { const { checked, disabled, title, treeId, value, onClick, } = this.props; const clickable = onClick !== null; const inputId = `${treeId}-${String(value).split(' ').join('_')}`; const render = [( <label key={0} htmlFor={inputId} title={title}> <NativeCheckbox checked={checked === 1} disabled={disabled} id={inputId} indeterminate={checked === 2} onClick={this.onCheck} onChange={() => {}} /> <span aria-checked={checked === 1} aria-disabled={disabled} className="rct-checkbox" role="checkbox" tabIndex={0} onKeyPress={this.onCheckboxKeyPress} onKeyUp={this.onCheckboxKeyUp} > {this.renderCheckboxIcon()} </span> {!clickable ? children : null} </label> )]; if (clickable) { render.push(( <span key={1} className="rct-node-clickable" onClick={this.onClick} onKeyPress={this.onClick} role="link" tabIndex={0} > {children} </span> )); } return render; } renderLabel() { const { label, showCheckbox, showNodeIcon } = this.props; const labelChildren = [ showNodeIcon ? ( <span key={0} className="rct-node-icon"> {this.renderNodeIcon()} </span> ) : null, <span key={1} className="rct-title"> {label} </span>, ]; if (!showCheckbox) { return this.renderBareLabel(labelChildren); } return this.renderCheckboxLabel(labelChildren); } renderChildren() { if (!this.props.expanded) { return null; } return this.props.children; } render() { const { className, disabled, expanded, isLeaf, } = this.props; const nodeClass = classNames({ 'rct-node': true, 'rct-node-leaf': isLeaf, 'rct-node-parent': !isLeaf, 'rct-node-expanded': !isLeaf && expanded, 'rct-node-collapsed': !isLeaf && !expanded, 'rct-disabled': disabled, }, className); return ( <li className={nodeClass}> <span className="rct-text"> {this.renderCollapseButton()} {this.renderLabel()} </span> {this.renderChildren()} </li> ); } } export default TreeNode;