UNPKG

wix-style-react

Version:
470 lines (421 loc) • 18.4 kB
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; var _class, _temp; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { ResizeSensor } from 'css-element-queries'; import s from './Page.scss'; import WixComponent from '../BaseComponents/WixComponent'; import PageHeader from '../PageHeader'; import Content from './Content'; import Tail from './Tail'; import { SCROLL_TOP_THRESHOLD, SHORT_SCROLL_TOP_THRESHOLD, TAIL_TOP_PADDING_PX } from './constants'; /** * A page container which contains a header and scrollable content * * Page structure is as follows: * @example * +-- FixedContainer -------- * | +-- HeaderContainer -------- * | | header-content:padding-top * | | +-- Page.Header -------- * | | | * | | | * | | +----------------------- * | | tail:padding-top * | | +-- Page.Tail ---------- * | | | * | | | * | | +----------------------- * | | header-content:padding-bottom * | +------------------------- * | +-- Page.FixedContent ---- ==+ * | | | * | +------------------------- | * +--------------------------- | Content (Virtual) * +-- ScrollableContainer --- | * | +-- Page.Content ---------- | | * | | | * | +-------------------------- | * +--------------------------- ==+ * * - ScrollableContainer is called in the code scrollable-content, and should NOT be renamed, since * Tooltip is hard-coded-ly using a selector like this: [data-class="page-scrollable-content"] */ var Page = (_temp = _class = function (_WixComponent) { _inherits(Page, _WixComponent); function Page(props) { _classCallCheck(this, Page); var _this = _possibleConstructorReturn(this, (Page.__proto__ || Object.getPrototypeOf(Page)).call(this, props)); _this._setContainerScrollTopThreshold(false); _this._handleScroll = _this._handleScroll.bind(_this); _this._handleResize = _this._handleResize.bind(_this); _this.state = { fixedContainerHeight: 0, tailHeight: 0, fixedContentHeight: 0, scrollBarWidth: 0, minimized: false }; return _this; } _createClass(Page, [{ key: 'componentDidMount', value: function componentDidMount() { _get(Page.prototype.__proto__ || Object.getPrototypeOf(Page.prototype), 'componentDidMount', this).call(this); this.contentResizeListener = new ResizeSensor(this._getScrollContainer().childNodes[0], this._handleResize); this._calculateComponentsHeights(); this._handleResize(); } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { _get(Page.prototype.__proto__ || Object.getPrototypeOf(Page.prototype), 'componentDidUpdate', this).call(this, prevProps); // Do not trigger height calculation if the component is minimized if (!this.state.minimized) { this._calculateComponentsHeights(); } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { _get(Page.prototype.__proto__ || Object.getPrototypeOf(Page.prototype), 'componentWillUnmount', this).call(this); this.contentResizeListener.detach(this._handleResize); } }, { key: '_calculateComponentsHeights', value: function _calculateComponentsHeights() { var _state = this.state, fixedContainerHeight = _state.fixedContainerHeight, tailHeight = _state.tailHeight, fixedContentHeight = _state.fixedContentHeight; var newFixedContainerHeight = this.fixedContainerRef ? this.fixedContainerRef.offsetHeight : 0; var newTailHeight = this.pageHeaderTailRef ? this.pageHeaderTailRef.offsetHeight : 0; var newFixedContentHeight = this.pageHeaderFixedContentRef ? this.pageHeaderFixedContentRef.offsetHeight : 0; if (fixedContainerHeight !== newFixedContainerHeight || tailHeight !== newTailHeight || fixedContentHeight !== newFixedContentHeight) { this.setState({ fixedContainerHeight: newFixedContainerHeight, tailHeight: newTailHeight, fixedContentHeight: newFixedContentHeight }); } } }, { key: '_setContainerScrollTopThreshold', value: function _setContainerScrollTopThreshold(_ref) { var shortThreshold = _ref.shortThreshold; this.containerScrollTopThreshold = shortThreshold ? SHORT_SCROLL_TOP_THRESHOLD : SCROLL_TOP_THRESHOLD; } }, { key: '_setScrollContainer', value: function _setScrollContainer(scrollContainer) { this.scrollableContentRef = scrollContainer; this.props.scrollableContentRef && this.props.scrollableContentRef(scrollContainer); } }, { key: '_getScrollContainer', value: function _getScrollContainer() { return this.scrollableContentRef; } }, { key: '_shouldBeMinimized', value: function _shouldBeMinimized(containerScrollTop) { return containerScrollTop > this.containerScrollTopThreshold; } }, { key: '_handleScroll', value: function _handleScroll() { var scrollContainer = this._getScrollContainer(); var containerScrollTop = scrollContainer.scrollTop; var nextMinimized = this._shouldBeMinimized(containerScrollTop); var minimized = this.state.minimized; if (minimized !== nextMinimized) { this.setState({ minimized: nextMinimized }); } } }, { key: '_handleResize', value: function _handleResize() { // Fixes width issues when scroll bar is present in windows var scrollContainer = this._getScrollContainer(); var scrollBarWidth = scrollContainer && scrollContainer.offsetWidth - scrollContainer.clientWidth; if (this.state.scrollBarWidth !== scrollBarWidth) { this.setState({ scrollBarWidth: scrollBarWidth }); } } }, { key: '_safeGetChildren', value: function _safeGetChildren(element) { if (!element || !element.props || !element.props.children) { return []; } return element.props.children; } }, { key: '_calculatePageDimensionsStyle', value: function _calculatePageDimensionsStyle() { var _props = this.props, maxWidth = _props.maxWidth, sidePadding = _props.sidePadding; if (!maxWidth && !sidePadding && sidePadding !== 0) { return null; } var styles = {}; if (maxWidth) { styles.maxWidth = maxWidth + 'px'; } if (sidePadding || sidePadding === 0) { styles.paddingLeft = sidePadding + 'px'; styles.paddingRight = sidePadding + 'px'; } return styles; } }, { key: '_fixedContainerStyle', value: function _fixedContainerStyle() { var scrollBarWidth = this.state.scrollBarWidth; if (scrollBarWidth) { return { width: 'calc(100% - ' + scrollBarWidth + 'px' }; } return null; } /** * See diagram in class documentation to better understand this method. */ }, { key: '_calculateHeaderMeasurements', value: function _calculateHeaderMeasurements(_ref2) { var PageTail = _ref2.PageTail; var gradientCoverTail = this.props.gradientCoverTail; // fixedContainerHeight (and other heights) are calculated only when the Page is NOT minimized var _state2 = this.state, fixedContainerHeight = _state2.fixedContainerHeight, tailHeight = _state2.tailHeight, fixedContentHeight = _state2.fixedContentHeight; var minimizedFixedContainerHeight = PageTail ? fixedContainerHeight - 78 : fixedContainerHeight - (78 - TAIL_TOP_PADDING_PX); var headerContainerHeight = fixedContainerHeight - fixedContentHeight; var imageHeight = headerContainerHeight + (PageTail ? -tailHeight : 39) + 'px'; var gradientHeight = gradientCoverTail ? headerContainerHeight + (PageTail ? -SCROLL_TOP_THRESHOLD : 39) + 'px' : imageHeight; return { imageHeight: imageHeight, gradientHeight: gradientHeight, fixedContainerHeight: fixedContainerHeight, minimizedFixedContainerHeight: minimizedFixedContainerHeight }; } }, { key: 'render', value: function render() { var _this2 = this, _classNames2; var _props2 = this.props, backgroundImageUrl = _props2.backgroundImageUrl, gradientClassName = _props2.gradientClassName, className = _props2.className, children = _props2.children; var minimized = this.state.minimized; var hasBackgroundImage = !!backgroundImageUrl; var hasGradientClassName = !!gradientClassName && !backgroundImageUrl; var childrenObject = getChildrenObject(children); var PageContent = childrenObject.PageContent, PageFixedContent = childrenObject.PageFixedContent, PageTail = childrenObject.PageTail; this._setContainerScrollTopThreshold({ shortThreshold: PageTail && hasGradientClassName }); var contentFullScreen = PageContent && PageContent.props.fullScreen; var pageDimensionsStyle = this._calculatePageDimensionsStyle(); var _calculateHeaderMeasu = this._calculateHeaderMeasurements({ PageTail: PageTail }), imageHeight = _calculateHeaderMeasu.imageHeight, gradientHeight = _calculateHeaderMeasu.gradientHeight, fixedContainerHeight = _calculateHeaderMeasu.fixedContainerHeight, minimizedFixedContainerHeight = _calculateHeaderMeasu.minimizedFixedContainerHeight; var contentLayoutProps = { className: classNames(s.content, _defineProperty({}, s.contentFullScreen, contentFullScreen)), style: contentFullScreen ? null : pageDimensionsStyle }; return React.createElement( 'div', { className: classNames(s.page, className) }, React.createElement( 'div', { 'data-hook': 'page-fixed-container', style: this._fixedContainerStyle(), className: classNames(s.fixedContainer), ref: function ref(r) { return _this2.fixedContainerRef = r; }, onWheel: function onWheel(event) { _this2._getScrollContainer().scrollTop = _this2._getScrollContainer().scrollTop + event.deltaY; } }, React.createElement( 'div', { className: classNames(s.pageHeaderContainer, (_classNames2 = {}, _defineProperty(_classNames2, s.minimized, minimized), _defineProperty(_classNames2, s.withoutBottomPadding, PageTail && minimized), _classNames2)) }, childrenObject.PageHeader && React.createElement( 'div', { className: s.pageHeader, style: pageDimensionsStyle }, React.cloneElement(childrenObject.PageHeader, { minimized: minimized, hasBackgroundImage: hasBackgroundImage }) ), PageTail && React.createElement( 'div', { 'data-hook': 'page-tail', className: classNames(s.tail, _defineProperty({}, s.minimized, minimized)), style: pageDimensionsStyle, ref: function ref(r) { return _this2.pageHeaderTailRef = r; } }, React.cloneElement(PageTail, { minimized: minimized }) ) ), PageFixedContent && React.createElement( 'div', _extends({ 'data-hook': 'page-fixed-content' }, contentLayoutProps, { ref: function ref(r) { return _this2.pageHeaderFixedContentRef = r; } }), React.cloneElement(PageFixedContent) ) ), React.createElement( 'div', { className: s.scrollableContent, onScroll: this._handleScroll, 'data-hook': 'page-scrollable-content', 'data-class': 'page-scrollable-content', style: { paddingTop: fixedContainerHeight + 'px' }, ref: function ref(r) { return _this2._setScrollContainer(r); } }, hasBackgroundImage && React.createElement( 'div', { className: s.imageBackgroundContainer, style: { height: imageHeight }, 'data-hook': 'page-background-image' }, React.createElement('div', { className: s.imageBackground, style: { backgroundImage: 'url(' + backgroundImageUrl + ')' } }) ), hasGradientClassName && !hasBackgroundImage && React.createElement('div', { 'data-hook': 'page-gradient-class-name', className: s.gradientBackground + ' ' + gradientClassName, style: { height: gradientHeight } }), React.createElement( 'div', { className: s.contentContainer }, React.createElement( 'div', contentLayoutProps, this._safeGetChildren(PageContent) ), minimized ? React.createElement('div', { style: { height: fixedContainerHeight - minimizedFixedContainerHeight + 'px' } }) : null ) ) ); } }]); return Page; }(WixComponent), _class.defaultProps = { gradientCoverTail: true }, _temp); var FixedContent = function FixedContent(props) { return props.children; }; FixedContent.displayName = 'Page.FixedContent'; FixedContent.propTypes = { children: PropTypes.element.isRequired }; Page.displayName = 'Page'; Page.Header = PageHeader; Page.Content = Content; Page.FixedContent = FixedContent; Page.Tail = Tail; Page.propTypes = { /** Background image url of the header beackground */ backgroundImageUrl: PropTypes.string, /** Sets the max width of the header and the content */ maxWidth: PropTypes.number, /** Sets padding of the sides of the page */ sidePadding: PropTypes.number, /** A css class to be applied to the component's root element */ className: PropTypes.string, /** Header background color class name, allows to add a gradient to the header */ gradientClassName: PropTypes.string, /** If false Gradient will not cover Page.Tail */ gradientCoverTail: PropTypes.bool, /** Is called with the Page's scrollable content ref **/ scrollableContentRef: PropTypes.func, children: PropTypes.arrayOf(function (children, key) { var childrenObj = getChildrenObject(children); if (!childrenObj.PageHeader) { return new Error('Page: Invalid Prop children, must contain Page.Header'); } if (!childrenObj.PageContent) { return new Error('Page: Invalid Prop children, must contain Page.Content'); } if (children[key].type.displayName !== Page.Header.displayName && children[key].type.displayName !== Page.Content.displayName && children[key].type.displayName !== Page.FixedContent.displayName && children[key].type.displayName !== Page.Tail.displayName) { return new Error('Page: Invalid Prop children, unknown child ' + children[key].type); } }).isRequired }; function getChildrenObject(children) { return React.Children.toArray(children).reduce(function (acc, child) { switch (child.type.displayName) { case 'Page.Header': { acc.PageHeader = child; break; } case 'Page.Content': { acc.PageContent = child; break; } case 'Page.FixedContent': { acc.PageFixedContent = child; break; } case 'Page.Tail': { acc.PageTail = child; break; } default: { break; } } return acc; }, {}); } export default Page;