UNPKG

material-ui

Version:

Material Design UI components built with React

426 lines (367 loc) 13.2 kB
let React = require('react/addons'); let ColorManipulator = require('../utils/color-manipulator'); let StylePropable = require('../mixins/style-propable'); let Colors = require('../styles/colors'); let Transitions = require('../styles/transitions'); let Typography = require('../styles/typography'); let EnhancedButton = require('../enhanced-button'); let IconButton = require('../icon-button'); let OpenIcon = require('../svg-icons/navigation/arrow-drop-up'); let CloseIcon = require('../svg-icons/navigation/arrow-drop-down'); let ListNested = require('./list-nested'); let ListItem = React.createClass({ mixins: [StylePropable], contextTypes: { muiTheme: React.PropTypes.object, }, propTypes: { autoGenerateNestedIndicator: React.PropTypes.bool, disabled: React.PropTypes.bool, disableKeyboardFocus: React.PropTypes.bool, innerDivStyle: React.PropTypes.object, insetChildren: React.PropTypes.bool, innerStyle: React.PropTypes.object, leftAvatar: React.PropTypes.element, leftCheckbox: React.PropTypes.element, leftIcon: React.PropTypes.element, nestedLevel: React.PropTypes.number, onKeyboardFocus: React.PropTypes.func, onMouseOut: React.PropTypes.func, onMouseOver: React.PropTypes.func, onNestedListToggle: React.PropTypes.func, onTouchStart: React.PropTypes.func, open: React.PropTypes.bool, rightAvatar: React.PropTypes.element, rightIcon: React.PropTypes.element, rightIconButton: React.PropTypes.element, rightToggle: React.PropTypes.element, primaryText: React.PropTypes.node, secondaryText: React.PropTypes.node, secondaryTextLines: React.PropTypes.oneOf([1, 2]), }, getDefaultProps() { return { autoGenerateNestedIndicator: true, nestedLevel: 0, open: false, secondaryTextLines: 1, }; }, getInitialState() { return { hovered: false, isKeyboardFocused: false, open: this.props.open, rightIconButtonHovered: false, rightIconButtonKeyboardFocused: false, touch: false, }; }, render() { let { autoGenerateNestedIndicator, disabled, disableKeyboardFocus, innerDivStyle, insetChildren, leftAvatar, leftCheckbox, leftIcon, nestedLevel, onKeyboardFocus, onMouseOut, onMouseOver, onTouchStart, rightAvatar, rightIcon, rightIconButton, rightToggle, primaryText, secondaryText, secondaryTextLines, style, ...other } = this.props; let textColor = this.context.muiTheme.palette.textColor; let hoverColor = ColorManipulator.fade(textColor, 0.1); let singleAvatar = !secondaryText && (leftAvatar || rightAvatar); let singleNoAvatar = !secondaryText && !(leftAvatar || rightAvatar); let twoLine = secondaryText && secondaryTextLines === 1; let threeLine = secondaryText && secondaryTextLines > 1; let hasCheckbox = leftCheckbox || rightToggle; let styles = { root: { backgroundColor: (this.state.isKeyboardFocused || this.state.hovered) && !this.state.rightIconButtonHovered && !this.state.rightIconButtonKeyboardFocused ? hoverColor : null, color: textColor, display: 'block', fontSize: 16, lineHeight: '16px', position: 'relative', transition: Transitions.easeOut(), }, //This inner div is needed so that ripples will span the entire container innerDiv: { marginLeft: nestedLevel * this.context.muiTheme.component.listItem.nestedLevelDepth, paddingLeft: leftIcon || leftAvatar || leftCheckbox || insetChildren ? 72 : 16, paddingRight: rightIcon || rightAvatar || rightIconButton ? 56 : rightToggle ? 72 : 16, paddingBottom: singleAvatar ? 20 : 16, paddingTop: singleNoAvatar || threeLine ? 16 : 20, position: 'relative', }, label: { cursor: 'pointer', }, icons: { height: 24, width: 24, display: 'block', position: 'absolute', top: twoLine ? 12 : singleAvatar ? 4 : 0, padding: 12, }, leftIcon: { color: Colors.grey600, fill: Colors.grey600, left: 4, }, rightIcon: { color: Colors.grey400, fill: Colors.grey400, right: 4, }, avatars: { position: 'absolute', top: singleAvatar ? 8 : 16, }, leftAvatar: { left: 16, }, rightAvatar: { right: 16, }, leftCheckbox: { position: 'absolute', display: 'block', width: 24, top: twoLine ? 24 : singleAvatar ? 16 : 12, left: 16, }, primaryText: { margin: 0, }, rightIconButton: { position: 'absolute', display: 'block', top: twoLine ? 12 : singleAvatar ? 4 : 0, right: 4, }, rightToggle: { position: 'absolute', display: 'block', width: 54, top: twoLine ? 25 : singleAvatar ? 17 : 13, right: 8, }, secondaryText: { fontSize: 14, lineHeight: threeLine ? '18px' : '16px', height: threeLine ? 36 : 16, margin: 0, marginTop: 4, color: Typography.textLightBlack, //needed for 2 and 3 line ellipsis overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: threeLine ? null : 'nowrap', display: threeLine ? '-webkit-box' : null, WebkitLineClamp: threeLine ? 2 : null, WebkitBoxOrient: threeLine ? 'vertical' : null, }, }; let primaryTextIsAnElement = React.isValidElement(primaryText); let secondaryTextIsAnElement = React.isValidElement(secondaryText); let mergedRootStyles = this.mergeAndPrefix(styles.root, style); let mergedInnerDivStyles = this.mergeAndPrefix(styles.innerDiv, innerDivStyle); let mergedDivStyles = this.mergeAndPrefix(styles.root, mergedInnerDivStyles, style); let mergedLabelStyles = this.mergeAndPrefix(styles.root, mergedInnerDivStyles, styles.label, style); let mergedPrimaryTextStyles = primaryTextIsAnElement ? this.mergeStyles(styles.primaryText, primaryText.props.style) : null; let mergedSecondaryTextStyles = secondaryTextIsAnElement ? this.mergeStyles(styles.secondaryText, secondaryText.props.style) : null; let contentChildren = []; let nestedListItems = []; let nestedList; React.Children.forEach(this.props.children, (child) => { if (child === null) return; if (React.isValidElement(child) && child.type.displayName === 'ListItem') { nestedListItems.push(child); } else { contentChildren.push(child); } }); let rightIconButtonHandlers = { onKeyboardFocus: this._handleRightIconButtonKeyboardFocus, onMouseOver: this._handleRightIconButtonMouseOver, onMouseOut: this._handleRightIconButtonMouseOut, onTouchTap: this._handleRightIconButtonTouchTap, onMouseDown: this._handleRightIconButtonMouseUp, onMouseUp: this._handleRightIconButtonMouseUp, }; // Create a nested list indicator icon if we don't have an icon on the right if (nestedListItems.length > 0 && autoGenerateNestedIndicator && rightIcon === undefined && rightAvatar === undefined && rightIconButton === undefined) { if (this.state.open) { rightIconButton = <IconButton><OpenIcon /></IconButton>; } else { rightIconButton = <IconButton><CloseIcon /></IconButton>; } rightIconButtonHandlers.onTouchTap = this._handleNestedListToggle; } this._pushElement(contentChildren, leftIcon, this.mergeStyles(styles.icons, styles.leftIcon)); this._pushElement(contentChildren, rightIcon, this.mergeStyles(styles.icons, styles.rightIcon)); this._pushElement(contentChildren, leftAvatar, this.mergeStyles(styles.avatars, styles.leftAvatar)); this._pushElement(contentChildren, rightAvatar, this.mergeStyles(styles.avatars, styles.rightAvatar)); this._pushElement(contentChildren, leftCheckbox, this.mergeStyles(styles.leftCheckbox)); this._pushElement(contentChildren, rightIconButton, this.mergeStyles(styles.rightIconButton), rightIconButtonHandlers); this._pushElement(contentChildren, rightToggle, this.mergeStyles(styles.rightToggle)); if (nestedListItems.length) { nestedList = ( <ListNested nestedLevel={nestedLevel + 1} open={this.state.open}> {nestedListItems} </ListNested> ); } if (primaryText) { contentChildren.push( React.isValidElement(primaryText) ? React.cloneElement(primaryText, {key: 'primaryText', style: mergedPrimaryTextStyles}) : <div key="primaryText" style={styles.primaryText}>{primaryText}</div> ); } if (secondaryText) { contentChildren.push( React.isValidElement(secondaryText) ? React.cloneElement(secondaryText, {key: 'secondaryText', style: mergedSecondaryTextStyles}) : <div key="secondaryText" style={styles.secondaryText}>{secondaryText}</div> ); } return hasCheckbox || disabled ? React.createElement( hasCheckbox ? 'label' : 'div', { style: hasCheckbox ? mergedLabelStyles : mergedDivStyles }, contentChildren ) : ( <div> <EnhancedButton {...other} disabled={disabled} disableKeyboardFocus={disableKeyboardFocus || this.state.rightIconButtonKeyboardFocused} linkButton={true} onKeyboardFocus={this._handleKeyboardFocus} onMouseOut={this._handleMouseOut} onMouseOver={this._handleMouseOver} onTouchStart={this._handleTouchStart} ref="enhancedButton" style={mergedRootStyles}> <div style={mergedInnerDivStyles}> {contentChildren} </div> </EnhancedButton> {nestedList} </div> ); }, applyFocusState(focusState) { let button = this.refs.enhancedButton; let buttonEl = React.findDOMNode(button); if (button) { switch(focusState) { case 'none': buttonEl.blur(); break; case 'focused': buttonEl.focus(); break; case 'keyboard-focused': button.setKeyboardFocus(); buttonEl.focus(); break; } } }, _pushElement(children, element, baseStyles, additionalProps) { if (element) { let styles = this.mergeStyles(baseStyles, element.props.style); children.push( React.cloneElement(element, { key: children.length, style: styles, ...additionalProps, }) ); } }, _handleKeyboardFocus(e, isKeyboardFocused) { this.setState({isKeyboardFocused: isKeyboardFocused}); if (this.props.onKeyboardFocus) this.props.onKeyboardFocus(e, isKeyboardFocused); }, _handleMouseOver(e) { if (!this.state.touch) this.setState({hovered: true}); if (this.props.onMouseOver) this.props.onMouseOver(e); }, _handleMouseOut(e) { this.setState({hovered: false}); if (this.props.onMouseOut) this.props.onMouseOut(e); }, _handleRightIconButtonKeyboardFocus(e, isKeyboardFocused) { let iconButton = this.props.rightIconButton; let newState = {}; newState.rightIconButtonKeyboardFocused = isKeyboardFocused; if (isKeyboardFocused) newState.isKeyboardFocused = false; this.setState(newState); if (iconButton && iconButton.props.onKeyboardFocus) iconButton.props.onKeyboardFocus(e, isKeyboardFocused); }, _handleRightIconButtonMouseDown(e) { let iconButton = this.props.rightIconButton; e.stopPropagation(); if (iconButton && iconButton.props.onMouseDown) iconButton.props.onMouseDown(e); }, _handleRightIconButtonMouseOut(e) { let iconButton = this.props.rightIconButton; this.setState({rightIconButtonHovered: false}); if (iconButton && iconButton.props.onMouseOut) iconButton.props.onMouseOut(e); }, _handleRightIconButtonMouseOver(e) { let iconButton = this.props.rightIconButton; this.setState({rightIconButtonHovered: true}); if (iconButton && iconButton.props.onMouseOver) iconButton.props.onMouseOver(e); }, _handleRightIconButtonMouseUp(e) { let iconButton = this.props.rightIconButton; e.stopPropagation(); if (iconButton && iconButton.props.onMouseUp) iconButton.props.onMouseUp(e); }, _handleRightIconButtonTouchTap(e) { let iconButton = this.props.rightIconButton; //Stop the event from bubbling up to the list-item e.stopPropagation(); if (iconButton && iconButton.props.onTouchTap) iconButton.props.onTouchTap(e); }, _handleTouchStart(e) { this.setState({touch: true}); if (this.props.onTouchStart) this.props.onTouchStart(e); }, _handleNestedListToggle(e) { e.stopPropagation(); this.setState({open : !this.state.open}); if (this.props.onNestedListToggle) this.props.onNestedListToggle(this); }, }); module.exports = ListItem;