UNPKG

@material-ui/core

Version:

React components that implement Google's Material Design.

553 lines (451 loc) 19.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = exports.styles = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf3 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = _interopRequireDefault(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _warning = _interopRequireDefault(require("warning")); var _classnames = _interopRequireDefault(require("classnames")); var _reactEventListener = _interopRequireDefault(require("react-event-listener")); var _debounce = _interopRequireDefault(require("debounce")); var _normalizeScrollLeft = require("normalize-scroll-left"); var _animate = _interopRequireDefault(require("../internal/animate")); var _ScrollbarSize = _interopRequireDefault(require("./ScrollbarSize")); var _withStyles = _interopRequireDefault(require("../styles/withStyles")); var _TabIndicator = _interopRequireDefault(require("./TabIndicator")); var _TabScrollButton = _interopRequireDefault(require("./TabScrollButton")); /* eslint-disable no-restricted-globals */ // < 1kb payload overhead when lodash/debounce is > 3kb. var styles = function styles(theme) { return { /* Styles applied to the root element. */ root: { overflow: 'hidden', minHeight: 48, WebkitOverflowScrolling: 'touch' // Add iOS momentum scrolling. }, /* Styles applied to the flex container element. */ flexContainer: { display: 'flex' }, /* Styles applied to the flex container element if `centered={true}` & `scrollable={false}`. */ centered: { justifyContent: 'center' }, /* Styles applied to the tablist element. */ scroller: { position: 'relative', display: 'inline-block', flex: '1 1 auto', whiteSpace: 'nowrap' }, /* Styles applied to the tablist element if `scrollable={false}`. */ fixed: { overflowX: 'hidden', width: '100%' }, /* Styles applied to the tablist element if `scrollable={true}`. */ scrollable: { overflowX: 'scroll' }, /* Styles applied to the `ScrollButtonComponent` component. */ scrollButtons: {}, /* Styles applied to the `ScrollButtonComponent` component if `scrollButtons="auto"`. */ scrollButtonsAuto: (0, _defineProperty2.default)({}, theme.breakpoints.down('xs'), { display: 'none' }), /* Styles applied to the `TabIndicator` component. */ indicator: {} }; }; exports.styles = styles; var Tabs = /*#__PURE__*/ function (_React$Component) { (0, _inherits2.default)(Tabs, _React$Component); function Tabs() { var _getPrototypeOf2; var _this; (0, _classCallCheck2.default)(this, Tabs); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = (0, _possibleConstructorReturn2.default)(this, (_getPrototypeOf2 = (0, _getPrototypeOf3.default)(Tabs)).call.apply(_getPrototypeOf2, [this].concat(args))); _this.valueToIndex = new Map(); _this.handleResize = (0, _debounce.default)(function () { _this.updateIndicatorState(_this.props); _this.updateScrollButtonState(); }, 166); _this.handleTabsScroll = (0, _debounce.default)(function () { _this.updateScrollButtonState(); }, 166); _this.state = { indicatorStyle: {}, scrollerStyle: { marginBottom: 0 }, showLeftScroll: false, showRightScroll: false, mounted: false }; _this.getConditionalElements = function () { var _this$props = _this.props, classes = _this$props.classes, scrollable = _this$props.scrollable, ScrollButtonComponent = _this$props.ScrollButtonComponent, scrollButtons = _this$props.scrollButtons, theme = _this$props.theme; var conditionalElements = {}; conditionalElements.scrollbarSizeListener = scrollable ? _react.default.createElement(_ScrollbarSize.default, { onLoad: _this.handleScrollbarSizeChange, onChange: _this.handleScrollbarSizeChange }) : null; var showScrollButtons = scrollable && (scrollButtons === 'auto' || scrollButtons === 'on'); conditionalElements.scrollButtonLeft = showScrollButtons ? _react.default.createElement(ScrollButtonComponent, { direction: theme && theme.direction === 'rtl' ? 'right' : 'left', onClick: _this.handleLeftScrollClick, visible: _this.state.showLeftScroll, className: (0, _classnames.default)(classes.scrollButtons, (0, _defineProperty2.default)({}, classes.scrollButtonsAuto, scrollButtons === 'auto')) }) : null; conditionalElements.scrollButtonRight = showScrollButtons ? _react.default.createElement(ScrollButtonComponent, { direction: theme && theme.direction === 'rtl' ? 'left' : 'right', onClick: _this.handleRightScrollClick, visible: _this.state.showRightScroll, className: (0, _classnames.default)(classes.scrollButtons, (0, _defineProperty2.default)({}, classes.scrollButtonsAuto, scrollButtons === 'auto')) }) : null; return conditionalElements; }; _this.getTabsMeta = function (value, direction) { var tabsMeta; if (_this.tabsRef) { var rect = _this.tabsRef.getBoundingClientRect(); // create a new object with ClientRect class props + scrollLeft tabsMeta = { clientWidth: _this.tabsRef.clientWidth, scrollLeft: _this.tabsRef.scrollLeft, scrollLeftNormalized: (0, _normalizeScrollLeft.getNormalizedScrollLeft)(_this.tabsRef, direction), scrollWidth: _this.tabsRef.scrollWidth, left: rect.left, right: rect.right }; } var tabMeta; if (_this.tabsRef && value !== false) { var children = _this.tabsRef.children[0].children; if (children.length > 0) { var tab = children[_this.valueToIndex.get(value)]; process.env.NODE_ENV !== "production" ? (0, _warning.default)(tab, "Material-UI: the value provided `".concat(value, "` is invalid")) : void 0; tabMeta = tab ? tab.getBoundingClientRect() : null; } } return { tabsMeta: tabsMeta, tabMeta: tabMeta }; }; _this.handleLeftScrollClick = function () { _this.moveTabsScroll(-_this.tabsRef.clientWidth); }; _this.handleRightScrollClick = function () { _this.moveTabsScroll(_this.tabsRef.clientWidth); }; _this.handleScrollbarSizeChange = function (_ref) { var scrollbarHeight = _ref.scrollbarHeight; _this.setState({ scrollerStyle: { marginBottom: -scrollbarHeight } }); }; _this.moveTabsScroll = function (delta) { var theme = _this.props.theme; var multiplier = theme.direction === 'rtl' ? -1 : 1; var nextScrollLeft = _this.tabsRef.scrollLeft + delta * multiplier; // Fix for Edge var invert = theme.direction === 'rtl' && (0, _normalizeScrollLeft.detectScrollType)() === 'reverse' ? -1 : 1; _this.scroll(invert * nextScrollLeft); }; _this.scrollSelectedIntoView = function () { var _this$props2 = _this.props, theme = _this$props2.theme, value = _this$props2.value; var _this$getTabsMeta = _this.getTabsMeta(value, theme.direction), tabsMeta = _this$getTabsMeta.tabsMeta, tabMeta = _this$getTabsMeta.tabMeta; if (!tabMeta || !tabsMeta) { return; } if (tabMeta.left < tabsMeta.left) { // left side of button is out of view var nextScrollLeft = tabsMeta.scrollLeft + (tabMeta.left - tabsMeta.left); _this.scroll(nextScrollLeft); } else if (tabMeta.right > tabsMeta.right) { // right side of button is out of view var _nextScrollLeft = tabsMeta.scrollLeft + (tabMeta.right - tabsMeta.right); _this.scroll(_nextScrollLeft); } }; _this.scroll = function (value) { (0, _animate.default)('scrollLeft', _this.tabsRef, value); }; _this.updateScrollButtonState = function () { var _this$props3 = _this.props, scrollable = _this$props3.scrollable, scrollButtons = _this$props3.scrollButtons, theme = _this$props3.theme; if (scrollable && scrollButtons !== 'off') { var _this$tabsRef = _this.tabsRef, scrollWidth = _this$tabsRef.scrollWidth, clientWidth = _this$tabsRef.clientWidth; var scrollLeft = (0, _normalizeScrollLeft.getNormalizedScrollLeft)(_this.tabsRef, theme.direction); var showLeftScroll = theme.direction === 'rtl' ? scrollWidth > clientWidth + scrollLeft : scrollLeft > 0; var showRightScroll = theme.direction === 'rtl' ? scrollLeft > 0 : scrollWidth > clientWidth + scrollLeft; if (showLeftScroll !== _this.state.showLeftScroll || showRightScroll !== _this.state.showRightScroll) { _this.setState({ showLeftScroll: showLeftScroll, showRightScroll: showRightScroll }); } } }; return _this; } (0, _createClass2.default)(Tabs, [{ key: "componentDidMount", value: function componentDidMount() { // eslint-disable-next-line react/no-did-mount-set-state this.setState({ mounted: true }); this.updateIndicatorState(this.props); this.updateScrollButtonState(); if (this.props.action) { this.props.action({ updateIndicator: this.handleResize }); } } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { // The index might have changed at the same time. // We need to check again the right indicator position. this.updateIndicatorState(this.props); this.updateScrollButtonState(); if (this.state.indicatorStyle !== prevState.indicatorStyle) { this.scrollSelectedIntoView(); } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this.handleResize.clear(); this.handleTabsScroll.clear(); } }, { key: "updateIndicatorState", value: function updateIndicatorState(props) { var theme = props.theme, value = props.value; var _this$getTabsMeta2 = this.getTabsMeta(value, theme.direction), tabsMeta = _this$getTabsMeta2.tabsMeta, tabMeta = _this$getTabsMeta2.tabMeta; var left = 0; if (tabMeta && tabsMeta) { var correction = theme.direction === 'rtl' ? tabsMeta.scrollLeftNormalized + tabsMeta.clientWidth - tabsMeta.scrollWidth : tabsMeta.scrollLeft; left = Math.round(tabMeta.left - tabsMeta.left + correction); } var indicatorStyle = { left: left, // May be wrong until the font is loaded. width: tabMeta ? Math.round(tabMeta.width) : 0 }; if ((indicatorStyle.left !== this.state.indicatorStyle.left || indicatorStyle.width !== this.state.indicatorStyle.width) && !isNaN(indicatorStyle.left) && !isNaN(indicatorStyle.width)) { this.setState({ indicatorStyle: indicatorStyle }); } } }, { key: "render", value: function render() { var _classNames4, _this2 = this; var _this$props4 = this.props, action = _this$props4.action, centered = _this$props4.centered, childrenProp = _this$props4.children, classes = _this$props4.classes, classNameProp = _this$props4.className, Component = _this$props4.component, fullWidth = _this$props4.fullWidth, indicatorColor = _this$props4.indicatorColor, onChange = _this$props4.onChange, scrollable = _this$props4.scrollable, ScrollButtonComponent = _this$props4.ScrollButtonComponent, scrollButtons = _this$props4.scrollButtons, _this$props4$TabIndic = _this$props4.TabIndicatorProps, TabIndicatorProps = _this$props4$TabIndic === void 0 ? {} : _this$props4$TabIndic, textColor = _this$props4.textColor, theme = _this$props4.theme, value = _this$props4.value, other = (0, _objectWithoutProperties2.default)(_this$props4, ["action", "centered", "children", "classes", "className", "component", "fullWidth", "indicatorColor", "onChange", "scrollable", "ScrollButtonComponent", "scrollButtons", "TabIndicatorProps", "textColor", "theme", "value"]); process.env.NODE_ENV !== "production" ? (0, _warning.default)(!centered || !scrollable, 'Material-UI: you can not use the `centered={true}` and `scrollable={true}` properties ' + 'at the same time on a `Tabs` component.') : void 0; var className = (0, _classnames.default)(classes.root, classNameProp); var flexContainerClassName = (0, _classnames.default)(classes.flexContainer, (0, _defineProperty2.default)({}, classes.centered, centered && !scrollable)); var scrollerClassName = (0, _classnames.default)(classes.scroller, (_classNames4 = {}, (0, _defineProperty2.default)(_classNames4, classes.fixed, !scrollable), (0, _defineProperty2.default)(_classNames4, classes.scrollable, scrollable), _classNames4)); var indicator = _react.default.createElement(_TabIndicator.default, (0, _extends2.default)({ className: classes.indicator, color: indicatorColor }, TabIndicatorProps, { style: (0, _extends2.default)({}, this.state.indicatorStyle, TabIndicatorProps.style) })); this.valueToIndex = new Map(); var childIndex = 0; var children = _react.default.Children.map(childrenProp, function (child) { if (!_react.default.isValidElement(child)) { return null; } process.env.NODE_ENV !== "production" ? (0, _warning.default)(child.type !== _react.default.Fragment, ["Material-UI: the Tabs component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n')) : void 0; var childValue = child.props.value === undefined ? childIndex : child.props.value; _this2.valueToIndex.set(childValue, childIndex); var selected = childValue === value; childIndex += 1; return _react.default.cloneElement(child, { fullWidth: fullWidth, indicator: selected && !_this2.state.mounted && indicator, selected: selected, onChange: onChange, textColor: textColor, value: childValue }); }); var conditionalElements = this.getConditionalElements(); return _react.default.createElement(Component, (0, _extends2.default)({ className: className }, other), _react.default.createElement(_reactEventListener.default, { target: "window", onResize: this.handleResize }), conditionalElements.scrollbarSizeListener, _react.default.createElement("div", { className: classes.flexContainer }, conditionalElements.scrollButtonLeft, _react.default.createElement("div", { className: scrollerClassName, style: this.state.scrollerStyle, ref: function ref(_ref2) { _this2.tabsRef = _ref2; }, role: "tablist", onScroll: this.handleTabsScroll }, _react.default.createElement("div", { className: flexContainerClassName }, children), this.state.mounted && indicator), conditionalElements.scrollButtonRight)); } }]); return Tabs; }(_react.default.Component); Tabs.propTypes = process.env.NODE_ENV !== "production" ? { /** * Callback fired when the component mounts. * This is useful when you want to trigger an action programmatically. * It currently only supports `updateIndicator()` action. * * @param {object} actions This object contains all possible actions * that can be triggered programmatically. */ action: _propTypes.default.func, /** * If `true`, the tabs will be centered. * This property is intended for large views. */ centered: _propTypes.default.bool, /** * The content of the component. */ children: _propTypes.default.node, /** * Override or extend the styles applied to the component. * See [CSS API](#css-api) below for more details. */ classes: _propTypes.default.object.isRequired, /** * @ignore */ className: _propTypes.default.string, /** * The component used for the root node. * Either a string to use a DOM element or a component. */ component: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.func, _propTypes.default.object]), /** * If `true`, the tabs will grow to use all the available space. * This property is intended for small views, like on mobile. */ fullWidth: _propTypes.default.bool, /** * Determines the color of the indicator. */ indicatorColor: _propTypes.default.oneOf(['secondary', 'primary']), /** * Callback fired when the value changes. * * @param {object} event The event source of the callback * @param {number} value We default to the index of the child */ onChange: _propTypes.default.func, /** * True invokes scrolling properties and allow for horizontally scrolling * (or swiping) the tab bar. */ scrollable: _propTypes.default.bool, /** * The component used to render the scroll buttons. */ ScrollButtonComponent: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.func, _propTypes.default.object]), /** * Determine behavior of scroll buttons when tabs are set to scroll * `auto` will only present them on medium and larger viewports * `on` will always present them * `off` will never present them */ scrollButtons: _propTypes.default.oneOf(['auto', 'on', 'off']), /** * Properties applied to the `TabIndicator` element. */ TabIndicatorProps: _propTypes.default.object, /** * Determines the color of the `Tab`. */ textColor: _propTypes.default.oneOf(['secondary', 'primary', 'inherit']), /** * @ignore */ theme: _propTypes.default.object.isRequired, /** * The value of the currently selected `Tab`. * If you don't want any selected `Tab`, you can set this property to `false`. */ value: _propTypes.default.any } : {}; Tabs.defaultProps = { centered: false, component: 'div', fullWidth: false, indicatorColor: 'secondary', scrollable: false, ScrollButtonComponent: _TabScrollButton.default, scrollButtons: 'auto', textColor: 'inherit' }; var _default = (0, _withStyles.default)(styles, { name: 'MuiTabs', withTheme: true })(Tabs); exports.default = _default;