ToolJet/frontend/src/Editor/Components/Modal.jsx
Midhun Kumar E d36cc44920
test: Add automation for text-input revamp. (#8671)
* init textinput revamp

* updated styles panel

* bugfix

* updates

* fix :: accordion

* fix :: styling

* add box shadow , additional property,tooltip

* fix conditional render for styles

* feat :: fixed order of each property and styles

* feat :: styling input

* bugfix

* feat :: add option to add icon

* add option to add icon

* adding option to toggle visibility

* updated password input with new design

* chnaging component location

* bugfix

* style fixes

* fix :: added loader

* updated :: few detailing

* few bugfixes

* fix :: for form widget label

* fixes

* added option to add icon color

* including label field for password input

* fix for label

* fix

* test fix backward compatibility for height

* updates

* revert

* adding key for distinguishing older and newer widgets

* testing

* test

* test

* update

* update

* migration testing

* limit vertical resizing in textinput

* testing

* throw test

* test

* adding check for label length

* fixing edge cases

* removing resize

* backward compatibility height

* backward compatibility

* number input review fixes

* added exposed items

* fixing csa

* ui fixes

* fix height compatibility

* feat :: csa for all inputs and exposed variables

* backward compatibility fixes and validation fixes

* fixes :: textinput positioning of loader and icon

* fix :: password input

* cleanup and fixes

* fixes

* cleanup

* fixes

* review fixes

* review fixes

* typo fix

* fix padding

* review fixes styles component panel

* fix naming

* fix padding

* fix :: icons position

* updates

* cleanup

* updates events , csa

* backward compatibility

* clean

* feat :: change validation from properties

* ui fixes

* icon name

* removed 'px' text from tooltip

* fixes placeholder

* few updates :: removing label in form

* ui in form

* update :: number input validation behaviour

* testing fixes

* added side handlers

* removing unwanted fx

* disabling fx for padding field

* ordering change

* fix

* label issue + restricted side handler

* fix :: box shadow bug

* on change event doesnt propagate exposed vars correctly

* adding debounce for slider value change

* fix :: for modal ooen bug during onfocus event

* test slider

* Add common utils

* Modify helpers

* Add text input spec

* Add utils for field validation

* Minor spec updates

* Fix for password  basic automation cases

---------

Co-authored-by: stepinfwd <stepinfwd@gmail.com>
2024-02-06 10:27:31 +05:30

313 lines
9.5 KiB
JavaScript

import React, { useRef, useState, useEffect } from 'react';
import { default as BootstrapModal } from 'react-bootstrap/Modal';
import { SubCustomDragLayer } from '../SubCustomDragLayer';
import { SubContainer } from '../SubContainer';
import { ConfigHandle } from '../ConfigHandle';
var tinycolor = require('tinycolor2');
export const Modal = function Modal({
id,
component,
containerProps,
darkMode,
properties,
styles,
exposedVariables,
setExposedVariable,
setExposedVariables,
fireEvent,
dataCy,
height,
}) {
const [showModal, setShowModal] = useState(false);
const {
closeOnClickingOutside = false,
hideOnEsc,
hideCloseButton,
hideTitleBar,
loadingState,
useDefaultButton,
triggerButtonLabel,
modalHeight,
} = properties;
const {
headerBackgroundColor,
headerTextColor,
bodyBackgroundColor,
disabledState,
visibility,
triggerButtonBackgroundColor,
triggerButtonTextColor,
boxShadow,
} = styles;
const parentRef = useRef(null);
const title = properties.title ?? '';
const size = properties.size ?? 'lg';
useEffect(() => {
const exposedVariables = {
open: async function () {
setExposedVariable('show', true);
setShowModal(true);
},
close: async function () {
setShowModal(false);
setExposedVariable('show', false);
},
};
setExposedVariables(exposedVariables);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setShowModal]);
useEffect(() => {
const canShowModal = exposedVariables.show ?? false;
setShowModal(exposedVariables.show ?? false);
fireEvent(canShowModal ? 'onOpen' : 'onClose');
const inpuRef = document.getElementsByClassName('tj-text-input-widget')[0];
inpuRef.blur();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [exposedVariables.show]);
useEffect(() => {
const handleModalOpen = () => {
const canvasElement = document.getElementsByClassName('canvas-area')[0];
const modalBackdropEl = document.getElementsByClassName('modal-backdrop')[0];
const realCanvasEl = document.getElementsByClassName('real-canvas')[0];
const modalCanvasEl = document.getElementById(`canvas-${id}`);
if (canvasElement && modalBackdropEl && modalCanvasEl && realCanvasEl) {
canvasElement.style.height = '100vh';
canvasElement.style.maxHeight = '100vh';
canvasElement.style.minHeight = '100vh';
canvasElement.style.height = '100vh';
modalCanvasEl.style.height = modalHeight;
realCanvasEl.style.height = '100vh';
realCanvasEl.style.position = 'absolute';
canvasElement?.classList?.add('freeze-scroll');
modalBackdropEl.style.height = '100vh';
modalBackdropEl.style.minHeight = '100vh';
modalBackdropEl.style.minHeight = '100vh';
}
};
const handleModalClose = () => {
const canvasElement = document.getElementsByClassName('canvas-area')[0];
const realCanvasEl = document.getElementsByClassName('real-canvas')[0];
const canvasHeight = realCanvasEl?.getAttribute('canvas-height');
if (canvasElement && realCanvasEl && canvasHeight) {
canvasElement.style.height = '';
canvasElement.style.minHeight = '';
canvasElement.style.maxHeight = '';
realCanvasEl.style.height = canvasHeight;
realCanvasEl.style.position = '';
canvasElement?.classList?.remove('freeze-scroll');
}
};
if (showModal) {
handleModalOpen();
} else {
if (document.getElementsByClassName('modal-content')[0] == undefined) {
handleModalClose();
}
}
// Cleanup the effect
return () => {
if (document.getElementsByClassName('modal-content')[0] == undefined) {
handleModalClose();
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showModal, modalHeight]);
function hideModal() {
setShowModal(false);
setExposedVariable('show', false);
fireEvent('onClose');
}
const backwardCompatibilityCheck = height == '34' || modalHeight != undefined ? true : false;
const customStyles = {
modalBody: {
height: backwardCompatibilityCheck ? modalHeight : height,
backgroundColor:
['#fff', '#ffffffff'].includes(bodyBackgroundColor) && darkMode ? '#1F2837' : bodyBackgroundColor,
overflowX: 'hidden',
overflowY: 'auto',
},
modalHeader: {
backgroundColor:
['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor,
color: ['#000', '#000000', '#000000ff'].includes(headerTextColor) && darkMode ? '#fff' : headerTextColor,
},
buttonStyles: {
backgroundColor: triggerButtonBackgroundColor,
color: triggerButtonTextColor,
width: '100%',
display: visibility ? '' : 'none',
'--tblr-btn-color-darker': tinycolor(triggerButtonBackgroundColor).darken(8).toString(),
boxShadow,
},
};
useEffect(() => {
if (closeOnClickingOutside) {
const handleClickOutside = (event) => {
const modalRef = parentRef?.current?.parentElement?.parentElement?.parentElement;
if (modalRef && modalRef === event.target) {
hideModal();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [closeOnClickingOutside, parentRef]);
return (
<div className="container" data-disabled={disabledState} data-cy={dataCy}>
{useDefaultButton && (
<button
disabled={disabledState}
className="jet-button btn btn-primary p-1 overflow-hidden"
style={customStyles.buttonStyles}
onClick={(event) => {
event.stopPropagation();
setShowModal(true);
setExposedVariable('show', true);
}}
data-cy={`${dataCy}-launch-button`}
>
{triggerButtonLabel ?? 'Show Modal'}
</button>
)}
<Modal.Component
show={showModal}
contentClassName="modal-component"
container={document.getElementsByClassName('canvas-area')[0]}
size={size}
keyboard={true}
enforceFocus={false}
animation={false}
onEscapeKeyDown={() => hideOnEsc && hideModal()}
id="modal-container"
backdrop={'static'}
scrollable={true}
modalProps={{
customStyles,
parentRef,
id,
title,
hideTitleBar,
hideCloseButton,
hideModal,
component,
showConfigHandler: containerProps.mode === 'edit',
removeComponent: containerProps.removeComponent,
setSelected: containerProps.setSelectedComponent,
}}
>
{!loadingState ? (
<>
<SubContainer parent={id} {...containerProps} parentRef={parentRef} parentComponent={component} />
<SubCustomDragLayer
snapToGrid={true}
parentRef={parentRef}
parent={id}
currentLayout={containerProps.currentLayout}
/>
</>
) : (
<div className="p-2">
<center>
<div className="spinner-border mt-5" role="status"></div>
</center>
</div>
)}
</Modal.Component>
</div>
);
};
const Component = ({ children, ...restProps }) => {
const {
customStyles,
parentRef,
id,
title,
hideTitleBar,
hideCloseButton,
hideModal,
component,
showConfigHandler,
removeComponent,
setSelected,
} = restProps['modalProps'];
return (
<BootstrapModal {...restProps}>
{showConfigHandler && (
<ConfigHandle
id={id}
component={component}
removeComponent={removeComponent}
setSelectedComponent={setSelected} //! Only Modal uses setSelectedComponent instead of selecto lib
customClassName={hideTitleBar ? 'modalWidget-config-handle' : ''}
/>
)}
{!hideTitleBar && (
<BootstrapModal.Header style={{ ...customStyles.modalHeader }} data-cy={`modal-header`}>
<BootstrapModal.Title id="contained-modal-title-vcenter" data-cy={`modal-title`}>
{title}
</BootstrapModal.Title>
{!hideCloseButton && (
<span
className="cursor-pointer"
data-cy={`modal-close-button`}
size="sm"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
hideModal();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="icon icon-tabler icon-tabler-x"
width="24"
height="24"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</span>
)}
</BootstrapModal.Header>
)}
<BootstrapModal.Body style={{ ...customStyles.modalBody }} ref={parentRef} id={id} data-cy={`modal-body`}>
{children}
</BootstrapModal.Body>
</BootstrapModal>
);
};
Modal.Component = Component;