chayns-components
Version:
A set of beautiful React components for developing chayns® applications.
511 lines (506 loc) • 16.8 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _clsx = _interopRequireDefault(require("clsx"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _react = _interopRequireWildcard(require("react"));
var _Icon = _interopRequireDefault(require("../../react-chayns-icon/component/Icon"));
var _is = require("../../utils/is");
var _requestAnimationFrame = _interopRequireDefault(require("../../utils/requestAnimationFrame"));
var _AccordionHeadRight = _interopRequireDefault(require("./AccordionHeadRight"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
/**
* @component
*/
const CLOSE = 1;
const OPEN = 2;
let rqAnimationFrame;
/**
* Accordions are collapsible sections that are toggled by interacting with a
* permanently visible header.
*/
class Accordion extends _react.PureComponent {
constructor(props) {
super(props);
this.handleAccordionClick = event => {
const {
fixed,
onClick,
disabled
} = this.props;
if (onClick) {
onClick(event);
}
if (fixed || disabled || event === null) {
return;
}
let trigger = true;
let node = event.target;
for (let i = 0; i < 15; i += 1) {
// look for up to 15 parent nodes
if (node.classList) {
if (node.classList.contains('accordion--no-trigger')) {
trigger = false;
break;
}
if (node.classList.contains('accordion__head')) {
break;
}
}
if (node.parentNode) {
node = node.parentNode;
} else {
trigger = false; // no parent node and no break at accordion__head -> portal (e.g. contextMenu) -> no trigger
break;
}
}
if (trigger) {
const {
currentState
} = this.state;
const {
dataGroup
} = this.props;
if (!dataGroup && currentState === OPEN || this.accordion.classList.contains('accordion--open')) {
this.accordionCloseListener(event);
} else {
this.accordionOpenListener(event);
}
}
};
const {
defaultOpened,
open,
className
} = props;
const _currentState = props && defaultOpened || open || className && className.indexOf('accordion--open') !== -1 ? OPEN : CLOSE;
this.state = {
currentState: _currentState,
showBody: _currentState === OPEN
};
this.ref = ref => {
this.body = ref;
};
}
componentDidMount() {
const {
className,
autogrow,
dataGroup
} = this.props;
const {
currentState
} = this.state;
if (className.indexOf('accordion--open') !== -1) {
this.accordion.classList.add('accordion--open');
}
if (dataGroup) {
if (!Accordion.dataGroups[dataGroup]) {
Accordion.dataGroups[dataGroup] = [];
}
Accordion.dataGroups[dataGroup].push(this);
}
if (currentState === OPEN && autogrow && this.body) {
this.body.style.setProperty('max-height', 'initial', 'important');
}
this.body.addEventListener('transitionend', e => {
if (autogrow && e.propertyName === 'max-height') {
// It's important that the state is accessed inside of the transitionend function
// eslint-disable-next-line no-shadow
const {
currentState
} = this.state;
if (currentState === OPEN && !this.isClosing) {
this.body.style.setProperty('max-height', 'initial', 'important');
}
}
});
}
componentDidUpdate(prevProps, prevState) {
const {
open
} = this.props;
const {
currentState
} = this.state;
if (currentState === CLOSE && prevState.currentState !== currentState) {
this.timeout = setTimeout(() => {
this.setState({
showBody: false
});
}, 500);
}
if (open !== undefined) {
if (open !== prevProps.open) {
if (open) {
this.accordionOpenListener(null, true, true);
} else {
this.accordionCloseListener(null, true, true);
}
}
}
}
componentWillUnmount() {
const {
dataGroup
} = this.props;
if (dataGroup && Accordion.dataGroups[dataGroup]) {
const elementIndex = Accordion.dataGroups[dataGroup].indexOf(this);
if (elementIndex !== -1) {
Accordion.dataGroups[dataGroup].splice(elementIndex, 1);
}
}
cancelAnimationFrame(rqAnimationFrame);
window.clearTimeout(this.timeout);
}
getBody() {
const {
renderClosed,
children,
removeContentClosed
} = this.props;
const {
currentState,
showBody
} = this.state;
if (currentState === OPEN || renderClosed || this.rendered && !removeContentClosed || showBody) {
this.rendered = true;
return children;
}
return null;
}
accordionCloseListener(event, preventOnClose, controlledChange) {
if (controlledChange === void 0) {
controlledChange = false;
}
const {
onClose,
autogrow,
controlled
} = this.props;
const {
body
} = this;
if (autogrow && body) {
rqAnimationFrame = (0, _requestAnimationFrame.default)(() => {
if (this.body) {
this.isClosing = true;
this.body.style.removeProperty('max-height');
}
rqAnimationFrame = (0, _requestAnimationFrame.default)(() => {
if (autogrow && body) {
this.setState({
currentState: CLOSE
}, () => {
this.isClosing = false;
});
if (onClose && !preventOnClose) {
onClose(event);
}
}
});
});
} else {
if (!controlled || controlledChange) {
this.setState({
currentState: CLOSE
});
}
if (onClose && !preventOnClose) {
onClose(event);
}
}
}
accordionOpenListener(event, preventOnOpen, controlledChange) {
if (controlledChange === void 0) {
controlledChange = false;
}
const {
onOpen,
dataGroup,
controlled
} = this.props;
if (!controlled || controlledChange) {
if (dataGroup && Accordion.dataGroups[dataGroup]) {
Accordion.dataGroups[dataGroup].forEach(accordion => {
if (accordion !== this && accordion.state && accordion.state.currentState === OPEN) {
accordion.accordionCloseListener();
}
});
}
this.setState({
currentState: OPEN,
showBody: true
});
}
if (onOpen && !preventOnOpen) {
onOpen(event);
}
}
render() {
const {
id,
style,
isWrapped,
className,
styleBody,
reference,
icon,
head,
headMultiline,
headClassNames,
headCustomAttributes,
noRotate,
noIcon,
disabled,
fixed,
right,
onSearch,
onSearchEnter,
searchPlaceholder,
searchValue
} = this.props;
const {
currentState
} = this.state;
let titleComponent = null;
if (head) {
if (head.open) {
titleComponent = currentState === OPEN ? head.open : head.close;
} else {
titleComponent = head;
}
}
return /*#__PURE__*/_react.default.createElement("div", {
className: (0, _clsx.default)("accordion react-accordion", className, isWrapped === true && 'accordion--wrapped', currentState === OPEN && 'accordion--open', disabled && 'accordion--disabled', fixed && 'accordion--fixed'),
ref: ref => {
this.accordion = ref;
if (reference && (0, _is.isFunction)(reference)) reference(ref);
},
id: id,
style: style
}, /*#__PURE__*/_react.default.createElement("div", (0, _extends2.default)({
className: (0, _clsx.default)('accordion__head', headClassNames, headMultiline && "accordion__head__multiline", fixed && 'accordion__head--no-padding'),
onClick: this.handleAccordionClick
}, headCustomAttributes), noIcon && !fixed ? null : /*#__PURE__*/_react.default.createElement("div", {
className: 'accordion__head__icon' + (noRotate || fixed ? " accordion__head__icon--no-rotate" : "")
}, (0, _is.isString)(icon) || icon.iconName ? /*#__PURE__*/_react.default.createElement(_Icon.default, {
icon: fixed && icon === 'ts-angle-right' ? 'fa fa-horizontal-rule' : icon,
className: fixed && icon === 'ts-angle-right' ? 'accordion--fixed__head__icon' : null
}) : icon), /*#__PURE__*/_react.default.createElement("div", {
className: "accordion__head__title",
style: {
...(noIcon && !fixed ? {
paddingLeft: '10px'
} : null),
...(head && !(0, _is.isString)(head.open) && (0, _is.isString)(head.close) && isWrapped ? {
fontWeight: 'inherit'
} : null)
}
}, titleComponent), /*#__PURE__*/_react.default.createElement(_AccordionHeadRight.default, {
right: right,
onSearch: onSearch,
onSearchEnter: onSearchEnter,
searchPlaceholder: searchPlaceholder,
searchValue: searchValue,
state: currentState
})), /*#__PURE__*/_react.default.createElement("div", {
className: "accordion__body",
ref: this.ref,
style: styleBody
}, this.getBody()));
}
}
exports.default = Accordion;
Accordion.dataGroups = {};
Accordion.propTypes = {
/**
* The component that should be displayed in the accordion head when it is
* closed. Can be a `string`, React node or object like this: `{ open:
* OpenComponent, close: CloseComponent }`. If an object is provided, the
* components will be swapped based on the opening state.
*/
head: _propTypes.default.oneOfType([_propTypes.default.node.isRequired, _propTypes.default.shape({
open: _propTypes.default.node.isRequired,
close: _propTypes.default.node.isRequired
}).isRequired]).isRequired,
/**
* Allows text to wrap inside of the head.
*/
headMultiline: _propTypes.default.bool,
/**
* Additional classnames to be applied to the head. Can be specified as a
* `string`, `string[]` or `{[key: string]: boolean}`, which will be passed
* to the [`classnames`](https://www.npmjs.com/package/classnames) function.
*/
headClassNames: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.arrayOf(_propTypes.default.string), _propTypes.default.object]),
/**
* Custom HTML attributes that will be added to the `<div>` that contains
* the head component.
*/
headCustomAttributes: _propTypes.default.object,
// eslint-disable-line react/forbid-prop-types
/**
* The content the Accordion reveals when it is open. To get proper spacing
* inside of the Accordion body, supply a `<div>` with the classname
* `accordion__content` applied to it.
*/
children: _propTypes.default.node.isRequired,
/**
* Component that will be shown on the right side of the component.
* Typically a badge. If you want different components for the open and
* closed state, supply an object: `{ open: ..., closed: ... }`.
*/
right: _propTypes.default.oneOfType([_propTypes.default.node.isRequired, _propTypes.default.shape({
open: _propTypes.default.node,
close: _propTypes.default.node
}).isRequired]),
/**
* Render the Accordion content, even if it is closed.
*/
renderClosed: _propTypes.default.bool,
/**
* Enables the wrapped Accordion style. Use this if the Accordion is nested
* inside of another Accordion.
*/
isWrapped: _propTypes.default.bool,
/**
* A string identifier for a group of Accordions. Only one Accordion of a
* group that have the same `dataGroup`-prop can be open.
*/
dataGroup: _propTypes.default.string,
/**
* A classname that will be applied to the outer most `<div>`-wrapper of the
* Accordion.
*/
className: _propTypes.default.string,
/**
* An HTML id that will be applied to the outer most `<div>`-wrapper.
*/
id: _propTypes.default.string,
/**
* A React style object that will be applied to the outer most
* `<div>`-wrapper of the Accordion.
*/
style: _propTypes.default.objectOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])),
/**
* A React style object that will be applied to the body of the Accordion.
*/
styleBody: _propTypes.default.objectOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])),
/**
* A callback that is called when the Accordion gets opened.
*/
onOpen: _propTypes.default.func,
/**
* A callback that is called when the Accordion gets closed.
*/
onClose: _propTypes.default.func,
/**
* Wether the Accordion should be opened by default (when it first gets
* rendered).
*/
defaultOpened: _propTypes.default.bool,
/**
* A function that receives the ref of the outer most `<div>`-element of the
* Accordion.
*/
reference: _propTypes.default.func,
/**
* Wether the Accordion should adjust its height in the opened state.
*/
autogrow: _propTypes.default.bool,
/**
* Control the open state.
*/
open: _propTypes.default.bool,
/**
* The icon that is displayed to the left of the Accordion head. Supply a
* FontAwesome string like `"fa fa-plane"` or a React component.
*/
icon: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.string, _propTypes.default.node]),
/**
* Disable the icon rotation.
*/
noRotate: _propTypes.default.bool,
/**
* Disable the opening and closing logic. The Accordion will be stuck in one
* state.
*/
fixed: _propTypes.default.bool,
/**
* Remove the Accordion to the left of the head.
*/
noIcon: _propTypes.default.bool,
/**
* A callback that will be called when the text in the search field on the
* right changes. This will also enable the search field.
*/
onSearch: _propTypes.default.func,
/**
* A callback that will be called when the enter-key is pressed in the
* search field.
*/
onSearchEnter: _propTypes.default.func,
/**
* The placeholder for the search field.
*/
searchPlaceholder: _propTypes.default.string,
/**
* The value for the search field (for making a controlled input).
*/
searchValue: _propTypes.default.string,
/**
* Remove content from the Accordion body when it is closing.
*/
removeContentClosed: _propTypes.default.bool,
/**
* Add a click listener for the Accordion head.
*/
onClick: _propTypes.default.func,
/**
* Disables the Accordion, which changes the style and removes any
* interactions.
*/
disabled: _propTypes.default.bool,
/**
* When set, the open-prop updates and onChange does not update the internal
* state.
*/
controlled: _propTypes.default.bool
};
Accordion.defaultProps = {
className: '',
headClassNames: null,
headMultiline: false,
headCustomAttributes: null,
dataGroup: null,
id: null,
style: null,
styleBody: null,
onOpen: null,
onClose: null,
defaultOpened: null,
reference: null,
isWrapped: false,
renderClosed: false,
right: null,
autogrow: true,
open: undefined,
icon: 'ts-angle-right',
noRotate: false,
fixed: false,
noIcon: false,
onSearch: null,
onSearchEnter: null,
searchPlaceholder: '',
searchValue: undefined,
removeContentClosed: false,
onClick: null,
disabled: false,
controlled: false
};
Accordion.displayName = 'Accordion';
//# sourceMappingURL=Accordion.js.map