@shopgate/engage
Version:
Shopgate's ENGAGE library.
185 lines (175 loc) • 6.03 kB
JavaScript
import _createClass from "@babel/runtime/helpers/createClass";
import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash/debounce';
import I18n from '@shopgate/pwa-common/components/I18n';
import RippleButton from '@shopgate/pwa-ui-shared/RippleButton';
import { themeConfig } from '@shopgate/pwa-common/helpers/config';
import styles from "./style";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const {
variables
} = themeConfig;
/**
* The height of one row.
* @type {number}
*/
export const CHIP_ROW_HEIGHT = 34;
/**
* The minimum width that a chip should have.
* @type {number}
*/
export const CHIP_MINIMUM_WIDTH = 60;
/**
* The ChipLayout component.
*/
let ChipLayout = /*#__PURE__*/function (_Component) {
function ChipLayout(...args) {
var _this;
_this = _Component.call.apply(_Component, [this].concat(args)) || this;
/**
* Debounced, eg. layout effect
*/
_this.processHiddenElementsDebounced = debounce(_this.processHiddenElements, 50);
return _this;
}
_inheritsLoose(ChipLayout, _Component);
var _proto = ChipLayout.prototype;
/**
* When the component mounts we need to initially process all children.
*/
_proto.componentDidMount = function componentDidMount() {
this.processHiddenElementsDebounced();
}
/**
* Eveyry time pathname or other prop changes this callback is called.
* This funtion will start processing hidden elements in order to check if "more" button
* should be rendered.
*
* It must be done on every prop change, including the pathname.
* Sometimes this component is rendered invisible, then since `.processHiddenElements` uses
* refs.clientHeight it would always be zero.
*/;
_proto.componentDidUpdate = function componentDidUpdate() {
this.processHiddenElementsDebounced();
}
/**
* Returns the maximum height the container should have.
* @returns {number}
*/;
/**
* Loops through all children to make sure the more button appears if there is too much content.
*/
_proto.processHiddenElements = function processHiddenElements() {
if (!this.containerRef) {
return;
}
// Find out if there are overflowing elements.
let lastVisibleElement = 0;
const showMoreButton = this.containerRef.scrollHeight > this.containerRef.clientHeight;
const containerHeight = this.containerRef.clientHeight;
const chips = Array.from(this.layoutRef.children);
this.moreButtonRef.style.display = showMoreButton ? 'block' : 'none';
this.layoutRef.style.minHeight = showMoreButton ? `${this.maxContentHeight}px` : '0px';
// If the more button is not visible we don't need to process anything.
if (!showMoreButton) {
return;
}
// Hide or show chips that are hidden due to overflow.
chips.forEach((child, index) => {
const isVisible = child.offsetTop + child.clientHeight < containerHeight;
child.setAttribute('style', `display: ${isVisible ? 'flex' : 'none'};`);
if (isVisible) {
lastVisibleElement = index;
}
});
// Hide the more button if previous assumption was incorrect.
if (lastVisibleElement === chips.length - 1) {
this.moreButtonRef.style.display = 'none';
return;
}
// Hide elements so that the 'more button' has enough space.
chips.slice(0, lastVisibleElement + 1).reverse().every(element => {
const offsetBottom = element.offsetTop + element.clientHeight;
if (this.moreButtonRef.offsetTop > offsetBottom) {
return true;
}
const buttonSpaceRequired = this.moreButtonRef.clientWidth + variables.gap.big;
const elementRight = this.containerRef.clientWidth - (element.offsetLeft + element.clientWidth);
const spaceDiff = buttonSpaceRequired - elementRight;
const remainingChipWidth = element.clientWidth - spaceDiff;
if (remainingChipWidth > CHIP_MINIMUM_WIDTH) {
element.setAttribute('style', `max-width: ${remainingChipWidth}px`);
return false;
}
if (element.offsetTop !== chips[lastVisibleElement].offsetTop) {
element.setAttribute('style', 'display: none');
return false;
}
element.setAttribute('style', 'display: none');
return true;
});
};
/**
* Renders the component.
* @returns {JSX}
*/
_proto.render = function render() {
return /*#__PURE__*/_jsxs("div", {
ref: element => {
this.containerRef = element;
},
className: `${styles.container(this.maxContentHeight)} engage__chip-layout`,
children: [/*#__PURE__*/_jsx("div", {
ref: element => {
this.layoutRef = element;
},
className: styles.layout,
children: this.props.children
}), /*#__PURE__*/_jsx("div", {
ref: element => {
this.moreButtonRef = element;
},
className: styles.moreButtonWrapper,
children: /*#__PURE__*/_jsx(RippleButton, {
fill: true,
type: "plain",
className: this.moreButtonStyles,
onClick: this.props.handleMoreButton,
children: /*#__PURE__*/_jsx(I18n.Text, {
string: this.props.moreLabel
})
})
})]
});
};
return _createClass(ChipLayout, [{
key: "maxContentHeight",
get: function () {
// 8 -> container padding.
return CHIP_ROW_HEIGHT * this.props.maxRows + 8;
}
/**
* Returns the more button styles.
* @return {string} The store button class name.
*/
}, {
key: "moreButtonStyles",
get: function () {
if (this.props.invertMoreButton) {
return styles.moreButtonInverted;
}
return styles.moreButton;
}
}]);
}(Component);
ChipLayout.defaultProps = {
children: null,
handleMoreButton: () => {},
invertMoreButton: false,
maxRows: 2,
moreLabel: 'more',
pathname: ''
};
export default ChipLayout;