Skip to content

Commit

Permalink
IBX-6547: Updated Popup modal (#940)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gengar-i committed Nov 14, 2023
1 parent f6f020e commit 9c89d0e
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 246 deletions.
277 changes: 100 additions & 177 deletions src/bundle/ui-dev/src/modules/common/popup/popup.component.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { Component } from 'react';
import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import Icon from '../icon/icon';

const { Translator } = window;
import { createCssClassNames } from '@ibexa-admin-ui/src/bundle/ui-dev/src/modules/common/helpers/css.class.names';

const { bootstrap, Translator } = window;

const CLASS_NON_SCROLLABLE = 'ibexa-non-scrollable';
const CLASS_MODAL_OPEN = 'modal-open';
Expand All @@ -16,205 +18,129 @@ const MODAL_SIZE_CLASS = {
large: 'modal-lg',
};

class Popup extends Component {
constructor(props) {
super(props);

this._refModal = null;

this.setModalRef = this.setModalRef.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);

this.state = {
currentProps: {
isVisible: props.isVisible,
isLoading: props.isLoading,
},
isVisible: props.isVisible,
isLoading: props.isLoading,
};
}
const Popup = ({
isVisible,
onClose,
children,
title,
subtitle,
hasFocus,
noKeyboard,
actionBtnsConfig,
size,
noHeader,
noCloseBtn,
extraClasses,
}) => {
const modalRef = useRef(null);

useEffect(() => {
document.body.classList.toggle(CLASS_MODAL_OPEN, isVisible);
document.body.classList.toggle(CLASS_NON_SCROLLABLE, isVisible);

static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.isVisible !== prevState.currentProps.isVisible || nextProps.isLoading !== prevState.currentProps.isLoading) {
return {
currentProps: {
isVisible: nextProps.isVisible,
isLoading: nextProps.isLoading,
},
isVisible: nextProps.isVisible,
isLoading: nextProps.isLoading,
};
if (isVisible) {
showPopup();
modalRef.current.addEventListener('hidden.bs.modal', onClose);
}
}, [isVisible]);

if (!isVisible) {
return null;
}

componentDidMount() {
const { noKeyboard, hasFocus } = this.props;
const { isVisible: show } = this.state;

if (show) {
const bootstrapModal = window.bootstrap.Modal.getOrCreateInstance(this._refModal, {
...MODAL_CONFIG,
keyboard: !noKeyboard,
focus: hasFocus,
});

bootstrapModal.show();

this.attachModalEventHandlers();
}
}

componentDidUpdate() {
const { isVisible: show } = this.state;

const bootstrapModal = window.bootstrap.Modal.getOrCreateInstance(this._refModal, {
const modalClasses = createCssClassNames({
'c-popup modal fade': true,
'c-popup--no-header': noHeader,
[extraClasses]: extraClasses,
});
const closeBtnLabel = Translator.trans(/*@Desc("Close")*/ 'popup.close.label', {}, 'ibexa_universal_discovery_widget');
const hidePopup = () => {
bootstrap.Modal.getOrCreateInstance(modalRef.current).hide();
document.body.classList.remove(CLASS_MODAL_OPEN, CLASS_NON_SCROLLABLE);
};
const showPopup = () => {
const bootstrapModal = bootstrap.Modal.getOrCreateInstance(modalRef.current, {
...MODAL_CONFIG,
focus: this.props.hasFocus,
keyboard: !noKeyboard,
focus: hasFocus,
});

if (show) {
bootstrapModal.show();
this.attachModalEventHandlers();
} else {
bootstrapModal.hide();
}
}

componentWillUnmount() {
window.bootstrap.Modal.getOrCreateInstance(this._refModal).hide();
document.body.classList.remove(CLASS_MODAL_OPEN, CLASS_NON_SCROLLABLE);
}

attachModalEventHandlers() {
this._refModal.addEventListener('keyup', this.onKeyUp);
this._refModal.addEventListener('hidden.bs.modal', this.props.onClose);
}

onKeyUp(event) {
const { originalEvent } = event;
const escKeyCode = 27;
const escKeyPressed = originalEvent && (originalEvent.which === escKeyCode || originalEvent.keyCode === escKeyCode);

if (escKeyPressed) {
this.props.onClose();
}
}

setModalRef(component) {
this._refModal = component;
}

renderHeader() {
return (
<div className={'modal-header c-popup__header'}>
{this.renderHeadline()}
{this.renderCloseButton()}
</div>
);
}

renderCloseButton() {
if (this.props.noCloseBtn) {
return;
bootstrapModal.show();
};
const handleOnClick = (event, onClick) => {
modalRef.current.removeEventListener('hidden.bs.modal', onClose);
hidePopup();
onClick(event);
};
const renderCloseBtn = () => {
if (noCloseBtn) {
return null;
}

const closeBtnLabel = Translator.trans(/*@Desc("Close")*/ 'popup.close.label', {}, 'ibexa_universal_discovery_widget');

return (
<button
type="button"
className="close c-popup__btn--close"
data-bs-dismiss="modal"
aria-label={closeBtnLabel}
onClick={this.props.onClose}
onClick={hidePopup}
>
<Icon name="discard" extraClasses="ibexa-icon--small" />
</button>
);
}

renderHeadline() {
const { title } = this.props;

if (!title) {
return null;
}

return (
<h3 className="modal-title c-popup__headline" title={this.props.title}>
<span className="c-popup__title">{this.props.title}</span>
{this.renderSubtitle()}
</h3>
);
}

renderSubtitle() {
const { subtitle } = this.props;

if (!subtitle) {
return null;
}

return <span className="c-popup__subtitle">{subtitle}</span>;
}

renderFooter() {
const { footerChildren } = this.props;

if (!footerChildren) {
return;
}

return <div className={'modal-footer c-popup__footer'}>{footerChildren}</div>;
}

render() {
const { isVisible } = this.state;
const { additionalClasses, size, noHeader, extraClasses } = this.props;
const modalAttrs = {
className: `c-popup modal fade ${extraClasses}`,
ref: this.setModalRef,
tabIndex: this.props.hasFocus ? -1 : undefined,
};

document.body.classList.toggle(CLASS_MODAL_OPEN, isVisible);
document.body.classList.toggle(CLASS_NON_SCROLLABLE, isVisible);

if (additionalClasses) {
modalAttrs.className = `${modalAttrs.className} ${additionalClasses}`;
}

if (noHeader) {
modalAttrs.className = `${modalAttrs.className} c-popup--no-header`;
}

return (
<div {...modalAttrs}>
<div className={`modal-dialog c-popup__dialog ${MODAL_SIZE_CLASS[size]}`} role="dialog">
<div className="modal-content c-popup__content">
{noHeader ? this.renderCloseButton() : this.renderHeader()}
<div className="modal-body c-popup__body">{this.props.children}</div>
{this.renderFooter()}
};

return (
<div ref={modalRef} className={modalClasses} tabIndex={hasFocus ? -1 : undefined}>
<div className={`modal-dialog c-popup__dialog ${MODAL_SIZE_CLASS[size]}`} role="dialog">
<div className="modal-content c-popup__content">
{noHeader
? renderCloseBtn
: title && (
<div className="modal-header c-popup__header">
<h3 className="modal-title c-popup__headline" title={title}>
<span className="c-popup__title">{title}</span>
{subtitle && <span className="c-popup__subtitle">{subtitle}</span>}
</h3>
{renderCloseBtn}
</div>
)}
<div className="modal-body c-popup__body">{children}</div>
<div className="modal-footer c-popup__footer">
{actionBtnsConfig.map(({ className, onClick, disabled = false, label, ...extraProps }) => (
<button
key={label}
type="button"
className={`btn ibexa-btn ${className}`}
onClick={onClick ? (event) => handleOnClick(event, onClick) : hidePopup}
disabled={disabled}
{...extraProps}
>
{label}
</button>
))}
</div>
</div>
</div>
);
}
}
</div>
);
};

Popup.propTypes = {
isVisible: PropTypes.bool,
isLoading: PropTypes.bool,
onClose: PropTypes.func.isRequired,
children: PropTypes.element.isRequired,
actionBtnsConfig: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
onClick: PropTypes.func,
disabled: PropTypes.bool,
className: PropTypes.string,
}),
).isRequired,
children: PropTypes.node.isRequired,
isVisible: PropTypes.bool.isRequired,
onClose: PropTypes.func,
title: PropTypes.string,
subtitle: PropTypes.string,
hasFocus: PropTypes.bool,
additionalClasses: PropTypes.string,
footerChildren: PropTypes.element,
size: PropTypes.string,
noHeader: PropTypes.bool,
noCloseBtn: PropTypes.bool,
Expand All @@ -223,18 +149,15 @@ Popup.propTypes = {
};

Popup.defaultProps = {
isVisible: false,
isLoading: true,
hasFocus: true,
noKeyboard: false,
onClose: null,
size: 'large',
noHeader: false,
noCloseBtn: false,
noKeyboard: false,
extraClasses: '',
title: null,
subtitle: null,
additionalClasses: null,
footerChildren: null,
};

export default Popup;
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export default class UploadPopupModule extends Component {
}

UploadPopupModule.propTypes = {
popupTitle: PropTypes.string.isRequired,
visible: PropTypes.bool,
itemsToUpload: PropTypes.array,
onAfterUpload: PropTypes.func.isRequired,
Expand Down
Loading

0 comments on commit 9c89d0e

Please sign in to comment.