wix-style-react
Version:
wix-style-react
470 lines (421 loc) • 18.4 kB
JavaScript
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;