UNPKG

chayns-components

Version:

A set of beautiful React components for developing chayns® applications.

511 lines (506 loc) 16.8 kB
"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: null, removeContentClosed: false, onClick: null, disabled: false, controlled: false }; Accordion.displayName = 'Accordion'; //# sourceMappingURL=Accordion.js.map