chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
197 lines (194 loc) • 5.34 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
/**
* @component
*/
import classnames from 'clsx';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import insertStyle from '../../utils/insertStyle';
import isInteger from '../../utils/isInteger';
const CLOSED = 0;
const PRE_CLOSING = 4;
const CLOSING = 1;
const OPENING = 2;
const OPENED = 3;
const DEFAULT_OPEN_TIMEOUT = 500;
const DEFAULT_CLOSE_TIMEOUT = 500;
const DEFAULT_CLASSNAME = 'expandable_content';
const DEFAULT_CLASSNAMES = {
opened: 'animation__accordion--open',
opening: 'animation__accordion--open',
closed: 'animation__accordion--close',
closing: 'animation__accordion--close'
};
const DEFAULT_TIMEOUTS = {
opening: DEFAULT_OPEN_TIMEOUT,
closing: DEFAULT_CLOSE_TIMEOUT
};
/**
* A collapsible section that reveals its children with a height transition.
*/
export default class ExpandableContent extends Component {
static getMaxHeight(state, style) {
if (state === PRE_CLOSING) {
return null;
}
if (state === CLOSED || state === CLOSING) {
return '0px';
}
if (style && style.maxHeight) {
return style.maxHeight;
}
if (state === OPENING) {
return '9999px';
}
return 'initial';
}
static getClassNames(state, classNames) {
if (!classNames) {
return null;
}
if ((state === CLOSING || state === PRE_CLOSING) && classNames.closing) {
return classNames.closing;
}
if (state === CLOSED && classNames.closed) {
return classNames.closed;
}
if (state === OPENING && classNames.opening) {
return classNames.opening;
}
if (state === OPENED && classNames.opened) {
return classNames.opened;
}
return null;
}
constructor(props) {
super(props);
this.state = {
currentState: props.open ? OPENED : CLOSED
};
this.contentRendered = props.open;
}
componentDidMount() {
insertStyle('expandable_content', '.expandable_content { max-height: 9999px; }');
}
componentDidUpdate(prevProps) {
const {
open,
timeout
} = this.props;
if (open === prevProps.open) {
return;
}
if (open) {
this.open(timeout && timeout.opening);
} else {
this.close(timeout && timeout.closing);
}
}
open(timeout) {
clearTimeout(this.timeout);
this.setState({
currentState: OPENING
});
this.timeout = window.setTimeout(() => {
this.setState({
currentState: OPENED
});
}, isInteger(timeout) ? timeout : DEFAULT_OPEN_TIMEOUT);
this.contentRendered = true;
}
close(timeout) {
clearTimeout(this.timeout);
requestAnimationFrame(() => {
this.setState({
currentState: PRE_CLOSING
});
this.timeout = requestAnimationFrame(() => {
this.setState({
currentState: CLOSING
});
});
this.timeout = window.setTimeout(() => {
this.setState({
currentState: CLOSED
});
}, isInteger(timeout) ? timeout : DEFAULT_CLOSE_TIMEOUT);
});
}
render() {
const {
style,
className,
classNames,
open,
timeout,
children,
removeContentClosed,
...props
} = this.props;
const {
currentState
} = this.state;
const divStyle = {
...style,
overflow: style && style.overflow || 'hidden',
maxHeight: ExpandableContent.getMaxHeight(currentState, style)
};
const newClassNames = classnames(className, ExpandableContent.getClassNames(currentState, classNames), classNames === DEFAULT_CLASSNAMES && DEFAULT_CLASSNAME);
return /*#__PURE__*/React.createElement("div", _extends({
style: divStyle,
className: newClassNames
}, props), (currentState !== CLOSED || this.contentRendered && !removeContentClosed) && children);
}
}
ExpandableContent.propTypes = {
/**
* An object of classname strings that should be applied in the different
* states.
*/
classNames: PropTypes.shape({
opening: PropTypes.string,
opened: PropTypes.string,
closing: PropTypes.string,
closed: PropTypes.string
}),
/**
* This controls the animation timeouts for opening and closing.
*/
timeout: PropTypes.shape({
opening: PropTypes.number,
closing: PropTypes.number
}),
/**
* Wether the content should be visible. If changed, a height transition
* will start.
*/
open: PropTypes.bool.isRequired,
/**
* A React style object that is passed to the wrapper `<div>`-element.
*/
style: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
/**
* A classname string that is applied to the wrapper element.
*/
className: PropTypes.string,
/**
* The children of the component.
*/
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]).isRequired,
/**
* Wether the content should be removed when the component is closed and the
* content would not be visible anyways.
*/
removeContentClosed: PropTypes.bool
};
ExpandableContent.defaultProps = {
classNames: DEFAULT_CLASSNAMES,
timeout: DEFAULT_TIMEOUTS,
style: null,
className: null,
removeContentClosed: false
};
ExpandableContent.displayName = 'ExpandableContent';
//# sourceMappingURL=ExpandableContent.js.map