UNPKG

material-ui

Version:

React Components that Implement Google's Material Design.

756 lines (656 loc) 27 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _simpleAssign = require('simple-assign'); var _simpleAssign2 = _interopRequireDefault(_simpleAssign); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _shallowEqual = require('recompose/shallowEqual'); var _shallowEqual2 = _interopRequireDefault(_shallowEqual); var _colorManipulator = require('../utils/colorManipulator'); var _transitions = require('../styles/transitions'); var _transitions2 = _interopRequireDefault(_transitions); var _EnhancedButton = require('../internal/EnhancedButton'); var _EnhancedButton2 = _interopRequireDefault(_EnhancedButton); var _IconButton = require('../IconButton'); var _IconButton2 = _interopRequireDefault(_IconButton); var _expandLess = require('../svg-icons/navigation/expand-less'); var _expandLess2 = _interopRequireDefault(_expandLess); var _expandMore = require('../svg-icons/navigation/expand-more'); var _expandMore2 = _interopRequireDefault(_expandMore); var _NestedList = require('./NestedList'); var _NestedList2 = _interopRequireDefault(_NestedList); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function getStyles(props, context, state) { var autoGenerateNestedIndicator = props.autoGenerateNestedIndicator, insetChildren = props.insetChildren, leftAvatar = props.leftAvatar, leftCheckbox = props.leftCheckbox, leftIcon = props.leftIcon, nestedItems = props.nestedItems, nestedLevel = props.nestedLevel, rightAvatar = props.rightAvatar, rightIcon = props.rightIcon, rightIconButton = props.rightIconButton, rightToggle = props.rightToggle, secondaryText = props.secondaryText, secondaryTextLines = props.secondaryTextLines; var muiTheme = context.muiTheme; var listItem = muiTheme.listItem; var textColor = muiTheme.baseTheme.palette.textColor; var hoverColor = props.hoverColor || (0, _colorManipulator.fade)(textColor, 0.1); var singleAvatar = !secondaryText && (leftAvatar || rightAvatar); var singleNoAvatar = !secondaryText && !(leftAvatar || rightAvatar); var twoLine = secondaryText && secondaryTextLines === 1; var threeLine = secondaryText && secondaryTextLines > 1; var isKeyboardFocused = (props.isKeyboardFocused !== undefined ? props : state).isKeyboardFocused; var styles = { root: { backgroundColor: (isKeyboardFocused || state.hovered) && !state.rightIconButtonHovered && !state.rightIconButtonKeyboardFocused ? hoverColor : null, color: textColor, display: 'block', fontSize: 16, lineHeight: '16px', position: 'relative', transition: _transitions2.default.easeOut() }, // This inner div is needed so that ripples will span the entire container innerDiv: { marginLeft: nestedLevel * listItem.nestedLevelDepth, paddingLeft: leftIcon || leftAvatar || leftCheckbox || insetChildren ? 72 : 16, paddingRight: rightIcon || rightAvatar || rightIconButton || nestedItems.length && autoGenerateNestedIndicator ? 56 : rightToggle ? 72 : 16, paddingBottom: singleAvatar ? 20 : 16, paddingTop: singleNoAvatar || threeLine ? 16 : 20, position: 'relative' }, icons: { height: 24, width: 24, display: 'block', position: 'absolute', top: twoLine ? 12 : singleAvatar ? 4 : 0, margin: 12 }, leftIcon: { left: 4 }, rightIcon: { right: 4 }, avatars: { position: 'absolute', top: singleAvatar ? 8 : 16 }, label: { cursor: 'pointer' }, leftAvatar: { left: 16 }, rightAvatar: { right: 16 }, leftCheckbox: { position: 'absolute', display: 'block', width: 24, top: twoLine ? 24 : singleAvatar ? 16 : 12, left: 16 }, primaryText: {}, 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: listItem.secondaryTextColor, // 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 } }; return styles; } var ListItem = function (_Component) { (0, _inherits3.default)(ListItem, _Component); function ListItem() { var _ref; var _temp, _this, _ret; (0, _classCallCheck3.default)(this, ListItem); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_ref = ListItem.__proto__ || (0, _getPrototypeOf2.default)(ListItem)).call.apply(_ref, [this].concat(args))), _this), _this.state = { hovered: false, isKeyboardFocused: false, open: false, rightIconButtonHovered: false, rightIconButtonKeyboardFocused: false, touch: false }, _this.handleKeyboardFocus = function (event, isKeyboardFocused) { _this.setState({ isKeyboardFocused: isKeyboardFocused }); _this.props.onKeyboardFocus(event, isKeyboardFocused); }, _this.handleMouseEnter = function (event) { if (!_this.state.touch) _this.setState({ hovered: true }); _this.props.onMouseEnter(event); }, _this.handleMouseLeave = function (event) { _this.setState({ hovered: false }); _this.props.onMouseLeave(event); }, _this.handleClick = function (event) { if (_this.props.onClick) { _this.props.onClick(event); } if (_this.props.primaryTogglesNestedList) { _this.handleNestedListToggle(event); } }, _this.handleNestedListToggle = function (event) { if (_this.props.leftCheckbox) { event.preventDefault(); } event.stopPropagation(); if (_this.props.open === null) { _this.setState({ open: !_this.state.open }, function () { _this.props.onNestedListToggle(_this); }); } else { // Exposing `this` in the callback is quite a bad API. // I'm doing a one level deep clone to expose a fake state.open. _this.props.onNestedListToggle((0, _extends3.default)({}, _this, { state: { open: !_this.state.open } })); } }, _this.handleRightIconButtonKeyboardFocus = function (event, isKeyboardFocused) { if (isKeyboardFocused) { _this.setState({ isKeyboardFocused: false, rightIconButtonKeyboardFocused: isKeyboardFocused }); } var iconButton = _this.props.rightIconButton; if (iconButton && iconButton.props.onKeyboardFocus) iconButton.props.onKeyboardFocus(event, isKeyboardFocused); }, _this.handleRightIconButtonMouseLeave = function (event) { var iconButton = _this.props.rightIconButton; _this.setState({ rightIconButtonHovered: false }); if (iconButton && iconButton.props.onMouseLeave) iconButton.props.onMouseLeave(event); }, _this.handleRightIconButtonMouseEnter = function (event) { var iconButton = _this.props.rightIconButton; _this.setState({ rightIconButtonHovered: true }); if (iconButton && iconButton.props.onMouseEnter) iconButton.props.onMouseEnter(event); }, _this.handleRightIconButtonMouseUp = function (event) { var iconButton = _this.props.rightIconButton; event.stopPropagation(); if (iconButton && iconButton.props.onMouseUp) iconButton.props.onMouseUp(event); }, _this.handleRightIconButtonClick = function (event) { var iconButton = _this.props.rightIconButton; // Stop the event from bubbling up to the list-item event.stopPropagation(); if (iconButton && iconButton.props.onClick) iconButton.props.onClick(event); }, _this.handleTouchStart = function (event) { _this.setState({ touch: true }); _this.props.onTouchStart(event); }, _this.handleTouchEnd = function (event) { _this.setState({ touch: true }); _this.props.onTouchEnd(event); }, _temp), (0, _possibleConstructorReturn3.default)(_this, _ret); } (0, _createClass3.default)(ListItem, [{ key: 'componentWillMount', value: function componentWillMount() { this.setState({ open: this.props.open === null ? this.props.initiallyOpen === true : this.props.open }); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { // update the state when the component is controlled. if (nextProps.open !== null) this.setState({ open: nextProps.open }); if (nextProps.disabled && this.state.hovered) this.setState({ hovered: false }); } }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps, nextState, nextContext) { return !(0, _shallowEqual2.default)(this.props, nextProps) || !(0, _shallowEqual2.default)(this.state, nextState) || !(0, _shallowEqual2.default)(this.context, nextContext); } // This method is needed by the `MenuItem` component. }, { key: 'applyFocusState', value: function applyFocusState(focusState) { if (this.button) { var buttonEl = _reactDom2.default.findDOMNode(this.button); switch (focusState) { case 'none': buttonEl.blur(); break; case 'focused': buttonEl.focus(); break; case 'keyboard-focused': this.button.setKeyboardFocus(); buttonEl.focus(); break; } } } }, { key: 'createDisabledElement', value: function createDisabledElement(styles, contentChildren, additionalProps) { var _props = this.props, innerDivStyle = _props.innerDivStyle, style = _props.style; var mergedDivStyles = (0, _simpleAssign2.default)({}, styles.root, styles.innerDiv, innerDivStyle, style); return _react2.default.createElement( 'div', (0, _extends3.default)({}, additionalProps, { style: this.context.muiTheme.prepareStyles(mergedDivStyles) }), contentChildren ); } }, { key: 'createLabelElement', value: function createLabelElement(styles, contentChildren, additionalProps) { var _props2 = this.props, innerDivStyle = _props2.innerDivStyle, style = _props2.style; var mergedLabelStyles = (0, _simpleAssign2.default)({}, styles.root, styles.innerDiv, innerDivStyle, styles.label, style); return _react2.default.createElement( 'label', (0, _extends3.default)({}, additionalProps, { style: this.context.muiTheme.prepareStyles(mergedLabelStyles) }), contentChildren ); } }, { key: 'createTextElement', value: function createTextElement(styles, data, key) { var prepareStyles = this.context.muiTheme.prepareStyles; if (_react2.default.isValidElement(data)) { var style = (0, _simpleAssign2.default)({}, styles, data.props.style); if (typeof data.type === 'string') { // if element is a native dom node style = prepareStyles(style); } return _react2.default.cloneElement(data, { key: key, style: style }); } return _react2.default.createElement( 'div', { key: key, style: prepareStyles(styles) }, data ); } }, { key: 'pushElement', value: function pushElement(children, element, baseStyles, additionalProps) { if (element) { var styles = (0, _simpleAssign2.default)({}, baseStyles, element.props.style); children.push(_react2.default.cloneElement(element, (0, _extends3.default)({ key: children.length, style: styles }, additionalProps))); } } }, { key: 'render', value: function render() { var _this2 = this; var _props3 = this.props, autoGenerateNestedIndicator = _props3.autoGenerateNestedIndicator, children = _props3.children, containerElement = _props3.containerElement, disabled = _props3.disabled, disableKeyboardFocus = _props3.disableKeyboardFocus, hoverColor = _props3.hoverColor, initiallyOpen = _props3.initiallyOpen, innerDivStyle = _props3.innerDivStyle, insetChildren = _props3.insetChildren, leftAvatar = _props3.leftAvatar, leftCheckbox = _props3.leftCheckbox, leftIcon = _props3.leftIcon, nestedItems = _props3.nestedItems, nestedLevel = _props3.nestedLevel, nestedListStyle = _props3.nestedListStyle, onKeyboardFocus = _props3.onKeyboardFocus, isKeyboardFocused = _props3.isKeyboardFocused, onMouseEnter = _props3.onMouseEnter, onMouseLeave = _props3.onMouseLeave, onNestedListToggle = _props3.onNestedListToggle, onTouchStart = _props3.onTouchStart, onClick = _props3.onClick, rightAvatar = _props3.rightAvatar, rightIcon = _props3.rightIcon, rightIconButton = _props3.rightIconButton, rightToggle = _props3.rightToggle, primaryText = _props3.primaryText, primaryTogglesNestedList = _props3.primaryTogglesNestedList, secondaryText = _props3.secondaryText, secondaryTextLines = _props3.secondaryTextLines, style = _props3.style, other = (0, _objectWithoutProperties3.default)(_props3, ['autoGenerateNestedIndicator', 'children', 'containerElement', 'disabled', 'disableKeyboardFocus', 'hoverColor', 'initiallyOpen', 'innerDivStyle', 'insetChildren', 'leftAvatar', 'leftCheckbox', 'leftIcon', 'nestedItems', 'nestedLevel', 'nestedListStyle', 'onKeyboardFocus', 'isKeyboardFocused', 'onMouseEnter', 'onMouseLeave', 'onNestedListToggle', 'onTouchStart', 'onClick', 'rightAvatar', 'rightIcon', 'rightIconButton', 'rightToggle', 'primaryText', 'primaryTogglesNestedList', 'secondaryText', 'secondaryTextLines', 'style']); var prepareStyles = this.context.muiTheme.prepareStyles; var styles = getStyles(this.props, this.context, this.state); var contentChildren = [children]; if (leftIcon) { var additionalProps = { color: leftIcon.props.color || this.context.muiTheme.listItem.leftIconColor }; this.pushElement(contentChildren, leftIcon, (0, _simpleAssign2.default)({}, styles.icons, styles.leftIcon), additionalProps); } if (rightIcon) { var _additionalProps = { color: rightIcon.props.color || this.context.muiTheme.listItem.rightIconColor }; this.pushElement(contentChildren, rightIcon, (0, _simpleAssign2.default)({}, styles.icons, styles.rightIcon), _additionalProps); } if (leftAvatar) { this.pushElement(contentChildren, leftAvatar, (0, _simpleAssign2.default)({}, styles.avatars, styles.leftAvatar)); } if (rightAvatar) { this.pushElement(contentChildren, rightAvatar, (0, _simpleAssign2.default)({}, styles.avatars, styles.rightAvatar)); } if (leftCheckbox) { this.pushElement(contentChildren, leftCheckbox, (0, _simpleAssign2.default)({}, styles.leftCheckbox)); } // RightIconButtonElement var hasNestListItems = nestedItems.length; var hasRightElement = rightAvatar || rightIcon || rightIconButton || rightToggle; var needsNestedIndicator = hasNestListItems && autoGenerateNestedIndicator && !hasRightElement; if (rightIconButton || needsNestedIndicator) { var rightIconButtonElement = rightIconButton; var rightIconButtonHandlers = { onKeyboardFocus: this.handleRightIconButtonKeyboardFocus, onMouseEnter: this.handleRightIconButtonMouseEnter, onMouseLeave: this.handleRightIconButtonMouseLeave, onClick: this.handleRightIconButtonClick, onMouseDown: this.handleRightIconButtonMouseUp, onMouseUp: this.handleRightIconButtonMouseUp }; // Create a nested list indicator icon if we don't have an icon on the right if (needsNestedIndicator) { rightIconButtonElement = this.state.open ? _react2.default.createElement( _IconButton2.default, null, _react2.default.createElement(_expandLess2.default, null) ) : _react2.default.createElement( _IconButton2.default, null, _react2.default.createElement(_expandMore2.default, null) ); rightIconButtonHandlers.onClick = this.handleNestedListToggle; } this.pushElement(contentChildren, rightIconButtonElement, (0, _simpleAssign2.default)({}, styles.rightIconButton), rightIconButtonHandlers); } if (rightToggle) { this.pushElement(contentChildren, rightToggle, (0, _simpleAssign2.default)({}, styles.rightToggle)); } if (primaryText) { var primaryTextElement = this.createTextElement(styles.primaryText, primaryText, 'primaryText'); contentChildren.push(primaryTextElement); } if (secondaryText) { var secondaryTextElement = this.createTextElement(styles.secondaryText, secondaryText, 'secondaryText'); contentChildren.push(secondaryTextElement); } var nestedList = nestedItems.length ? _react2.default.createElement( _NestedList2.default, { nestedLevel: nestedLevel, open: this.state.open, style: nestedListStyle }, nestedItems ) : undefined; var simpleLabel = !primaryTogglesNestedList && (leftCheckbox || rightToggle); return _react2.default.createElement( 'div', null, simpleLabel ? this.createLabelElement(styles, contentChildren, other) : disabled ? this.createDisabledElement(styles, contentChildren, other) : _react2.default.createElement( _EnhancedButton2.default, (0, _extends3.default)({ containerElement: containerElement }, other, { disableKeyboardFocus: disableKeyboardFocus || this.state.rightIconButtonKeyboardFocused, onKeyboardFocus: this.handleKeyboardFocus, onMouseLeave: this.handleMouseLeave, onMouseEnter: this.handleMouseEnter, onTouchStart: this.handleTouchStart, onTouchEnd: this.handleTouchEnd, onClick: this.handleClick, disabled: disabled, ref: function ref(node) { return _this2.button = node; }, style: (0, _simpleAssign2.default)({}, styles.root, style) }), _react2.default.createElement( 'div', { style: prepareStyles((0, _simpleAssign2.default)(styles.innerDiv, innerDivStyle)) }, contentChildren ) ), nestedList ); } }]); return ListItem; }(_react.Component); ListItem.muiName = 'ListItem'; ListItem.defaultProps = { autoGenerateNestedIndicator: true, containerElement: 'span', disableKeyboardFocus: false, disabled: false, initiallyOpen: false, insetChildren: false, nestedItems: [], nestedLevel: 0, onKeyboardFocus: function onKeyboardFocus() {}, onMouseEnter: function onMouseEnter() {}, onMouseLeave: function onMouseLeave() {}, onNestedListToggle: function onNestedListToggle() {}, onTouchEnd: function onTouchEnd() {}, onTouchStart: function onTouchStart() {}, open: null, primaryTogglesNestedList: false, secondaryTextLines: 1 }; ListItem.contextTypes = { muiTheme: _propTypes2.default.object.isRequired }; ListItem.propTypes = process.env.NODE_ENV !== "production" ? { /** * If true, generate a nested-list-indicator icon when nested list * items are detected. Note that an indicator will not be created * if a `rightIcon` or `rightIconButton` has been provided to * the element. */ autoGenerateNestedIndicator: _propTypes2.default.bool, /** * Children passed into the `ListItem`. */ children: _propTypes2.default.node, /** * The element to use as the container for the ListItem. Either a string to * use a DOM element or a ReactElement. This is useful for wrapping the * ListItem in a custom Link component. If a ReactElement is given, ensure * that it passes all of its given props through to the underlying DOM * element and renders its children prop for proper integration. */ containerElement: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.element]), /** * If true, the element will not be able to be focused by the keyboard. */ disableKeyboardFocus: _propTypes2.default.bool, /** * If true, the element will not be clickable * and will not display hover effects. * This is automatically disabled if either `leftCheckbox` * or `rightToggle` is set. */ disabled: _propTypes2.default.bool, /** * Override the hover background color. */ hoverColor: _propTypes2.default.string, /** * If true, the nested `ListItem`s are initially displayed. */ initiallyOpen: _propTypes2.default.bool, /** * Override the inline-styles of the inner div element. */ innerDivStyle: _propTypes2.default.object, /** * If true, the children will be indented by 72px. * This is useful if there is no left avatar or left icon. */ insetChildren: _propTypes2.default.bool, /** * Use to control if the list item should render as keyboard focused. If * undefined (default), this will be automatically managed. If provided, * it will change the components style. Note that this will not change the * actual focus - and should only be used when you want to simulate * keyboard focus (eg. in a rich text input autocomplete). */ isKeyboardFocused: _propTypes2.default.bool, /** * This is the `Avatar` element to be displayed on the left side. */ leftAvatar: _propTypes2.default.element, /** * This is the `Checkbox` element to be displayed on the left side. */ leftCheckbox: _propTypes2.default.element, /** * This is the `SvgIcon` or `FontIcon` to be displayed on the left side. */ leftIcon: _propTypes2.default.element, /** * An array of `ListItem`s to nest underneath the current `ListItem`. */ nestedItems: _propTypes2.default.arrayOf(_propTypes2.default.element), /** * Controls how deep a `ListItem` appears. * This property is automatically managed, so modify at your own risk. */ nestedLevel: _propTypes2.default.number, /** * Override the inline-styles of the nested items' `NestedList`. */ nestedListStyle: _propTypes2.default.object, /** * Callback function fired when the list item is clicked. * * @param {object} event Click event targeting the list item. */ onClick: _propTypes2.default.func, /** * Callback function fired when the `ListItem` is focused or blurred by the keyboard. * * @param {object} event `focus` or `blur` event targeting the `ListItem`. * @param {boolean} isKeyboardFocused If true, the `ListItem` is focused. */ onKeyboardFocus: _propTypes2.default.func, /** @ignore */ onMouseEnter: _propTypes2.default.func, /** @ignore */ onMouseLeave: _propTypes2.default.func, /** * Callback function fired when the `ListItem` toggles its nested list. * * @param {object} listItem The `ListItem`. */ onNestedListToggle: _propTypes2.default.func, /** @ignore */ onTouchEnd: _propTypes2.default.func, /** @ignore */ onTouchStart: _propTypes2.default.func, /** * Control toggle state of nested list. */ open: _propTypes2.default.bool, /** * This is the block element that contains the primary text. * If a string is passed in, a div tag will be rendered. */ primaryText: _propTypes2.default.node, /** * If true, clicking or tapping the primary text of the `ListItem` * toggles the nested list. */ primaryTogglesNestedList: _propTypes2.default.bool, /** * This is the `Avatar` element to be displayed on the right side. */ rightAvatar: _propTypes2.default.element, /** * This is the `SvgIcon` or `FontIcon` to be displayed on the right side. */ rightIcon: _propTypes2.default.element, /** * This is the `IconButton` to be displayed on the right side. * Hovering over this button will remove the `ListItem` hover. * Also, clicking on this button will not trigger a * ripple on the `ListItem`; the event will be stopped and prevented * from bubbling up to cause a `ListItem` click. */ rightIconButton: _propTypes2.default.element, /** * This is the `Toggle` element to display on the right side. */ rightToggle: _propTypes2.default.element, /** * This is the block element that contains the secondary text. * If a string is passed in, a div tag will be rendered. */ secondaryText: _propTypes2.default.node, /** * Can be 1 or 2. This is the number of secondary * text lines before ellipsis will show. */ secondaryTextLines: _propTypes2.default.oneOf([1, 2]), /** * Override the inline-styles of the root element. */ style: _propTypes2.default.object } : {}; exports.default = ListItem;