UNPKG

dynamic-modal

Version:

The dynamic-modal is a solution of creation different modals into project using a json configuration file

179 lines (178 loc) 11.4 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { createElement as _createElement } from "react"; import { useContext, useEffect, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { Portal } from './components/portal/portal'; import MakeToggle from './components/make-toggle/make-toggle'; import MakeInput from './components/make-input/make-input'; import MakeSelect from './components/make-select/make-select'; import MakeTextarea from './components/make-textarea/make-textarea'; import MakeDescription from './components/make-description/make-description'; import MakeUpload from './components/make-upload/make-upload'; import MakeButton from './components/make-button/make-button'; import { ComponentStateContext } from './context/component/component-state'; import MakeCustomUpload from './components/make-custom-upload/make-custom-upload'; import MakeWatcher from './components/make-watcher/make-watcher'; import MakeTable from './components/make-table/make-table'; const getUseSubmit = (modalConfig) => { return modalConfig.useSubmit !== false; }; const getFieldKey = (element, index) => { const fieldKey = 'name' in element && element.name ? element.name : 'id' in element && element.id ? element.id : element.elementType === 'group' ? (element.title ?? `group-${index}`) : element.elementType === 'text' ? (element.text ?? `text-${index}`) : `field-${index}`; return `${element.elementType}-${fieldKey}`; }; const renderModalField = (field, index, { fieldProps, getValues }) => { const { elementType, ...element } = field; const fieldKey = getFieldKey(field, index); return elementType === 'input' ? (_createElement(MakeInput, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'select' ? (_createElement(MakeSelect, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'textarea' ? (_createElement(MakeTextarea, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'toggle' ? (_createElement(MakeToggle, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'text' ? (_createElement(MakeDescription, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'upload' ? (_createElement(MakeUpload, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'custom-upload' ? (_createElement(MakeCustomUpload, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'watcher' ? (_createElement(MakeWatcher, { ...fieldProps, key: fieldKey, element: element })) : elementType === 'button' ? (_createElement(MakeButton, { ...fieldProps, key: fieldKey, element: element, getValues: getValues })) : elementType === 'table' ? (_createElement(MakeTable, { ...fieldProps, key: fieldKey, element: element })) : null; }; const ModalFields = ({ modalReady, fieldProps, getValues, BodyComponent, bodyClassName, bodyStyle, }) => { const bodyContent = (_jsx(_Fragment, { children: modalReady.fields.map((element, index) => { if (element.elementType !== 'group') { return renderModalField(element, index, { fieldProps, getValues }); } const groupElements = element.groups .filter((sub) => !['group', 'watcher', 'table'].includes(sub.elementType)) .map((sub, subIndex) => renderModalField(sub, index + subIndex, { fieldProps, getValues })); const hideDiv = groupElements.every((component) => component === null); return (_jsxs("div", { className: `flex flex-col w-full gap-2 ${hideDiv && 'hidden'}`, children: [element.title && (_jsx("h3", { className: "font-bold border-b-2 pb-2 mb-2", children: element.title })), _jsx("div", { className: "flex gap-4 w-full", style: element.style, children: groupElements })] }, getFieldKey(element, index))); }) })); const currentBodyClassName = `flex flex-col gap-4 py-4${bodyClassName ? ` ${bodyClassName}` : ''}`; const currentBodyStyle = { overflowY: modalReady.overFlowBody ? 'auto' : undefined, height: modalReady.overFlowBody, minHeight: modalReady.minHeightBody, ...bodyStyle, }; if (BodyComponent) { return (_jsx(BodyComponent, { className: currentBodyClassName, style: currentBodyStyle, children: bodyContent })); } return (_jsx("div", { className: currentBodyClassName, style: currentBodyStyle, children: bodyContent })); }; const ModalActions = ({ modalReady, closeHandler, manualSubmit, FooterComponent, footerClassName, footerStyle, showFooterDivider = true, }) => { const { ModalButtonAction, ModalButtonCancel } = useContext(ComponentStateContext); const footerContent = (_jsxs(_Fragment, { children: [modalReady.actions.cancel && (_jsx(ModalButtonCancel, { ...modalReady.actions.cancel, onClick: closeHandler })), getUseSubmit(modalReady) ? (_jsx(ModalButtonAction, { ...modalReady.actions.action, type: "submit" })) : (_jsx(ModalButtonAction, { ...modalReady.actions.action, onClick: manualSubmit, type: "button" }))] })); const currentFooterClassName = `flex gap-4 items-center justify-center p-2${showFooterDivider ? ' border-t' : ''}${footerClassName ? ` ${footerClassName}` : ''}`; const currentFooterStyle = { ...footerStyle, ...modalReady.actions.containerStyle, }; if (FooterComponent) { return (_jsx(FooterComponent, { className: currentFooterClassName, style: currentFooterStyle, children: footerContent })); } return (_jsx("div", { className: currentFooterClassName, style: currentFooterStyle, children: footerContent })); }; export const Modal = ({ open, close, config }) => { const [modalReady, setModalReady] = useState(undefined); const [defaultLoaded, setDefaultLoaded] = useState(false); const { control, handleSubmit, getValues, unregister, setValue, watch, trigger, reset, getFieldState, } = useForm(); const { ModalContainer, ModalHeader, ModalTitle, ModalBody, ModalFooter } = useContext(ComponentStateContext); const formValueHandler = (element) => { if ([ 'group', 'upload', 'custom-upload', 'text', 'description', 'watcher', 'table', ].includes(element.elementType)) return; if (element.defaultValue === undefined && element.renderIf) { unregister(element.name); return; } const defaultValue = element.defaultValue ?? ''; const parsedValue = defaultValue === 'true' ? true : defaultValue === 'false' ? false : defaultValue; setValue(element.name, parsedValue ?? ''); }; const autoLoadGroup = (groupFields) => { groupFields.forEach((element) => { formValueHandler(element); }); }; const autoLoadField = (modalFields) => { if (defaultLoaded) return; modalFields.forEach((element) => { if (element.elementType === 'group') { autoLoadGroup(element.groups); return; } formValueHandler(element); }); setDefaultLoaded(true); }; const fieldProps = useMemo(() => ({ control, watch, setValue, unregister, }), [control, setValue, unregister, watch]); const closeHandler = () => { if (modalReady?.onClose) modalReady.onClose(); setTimeout(() => { setModalReady(undefined); setDefaultLoaded(false); reset(); close(); }, 200); }; const manualSubmit = async () => { const form = getValues(); const fields = Object.keys(form); await trigger(fields); const validations = fields.map((field) => { const { invalid } = getFieldState(field); return invalid; }); const result = validations.some((isInvalid) => isInvalid) ? undefined : form; if (!result) return; actionHandler({ ...(modalReady?.reservedData ?? {}), ...form }); }; const actionHandler = (data) => { modalReady?.out({ ...(modalReady?.reservedData ?? {}), ...data }); closeHandler(); }; useEffect(() => { if (open && !modalReady) setModalReady(config); }, [config, modalReady, open]); useEffect(() => { if (!modalReady || defaultLoaded) return; const timeout = window.setTimeout(() => { autoLoadField(modalReady.fields); }, 0); return () => window.clearTimeout(timeout); }, [defaultLoaded, modalReady]); if (!modalReady) return null; const { container: containerLayout, header: headerLayout, title: titleLayout, body: bodyLayout, footer: footerLayout, } = modalReady.layout ?? {}; const showHeaderDivider = headerLayout?.showDivider !== false; const showFooterDivider = footerLayout?.showDivider !== false; const headerClassName = `w-full text-center${showHeaderDivider ? ' border-b pb-4' : ''}${headerLayout?.className ? ` ${headerLayout.className}` : ''}`; const titleClassName = `font-semibold${titleLayout?.className ? ` ${titleLayout.className}` : ''}`; const titleContent = ModalTitle ? (_jsx(ModalTitle, { className: titleClassName, style: titleLayout?.style, children: modalReady.title })) : (_jsx("h2", { className: titleClassName, style: titleLayout?.style, children: modalReady.title })); const headerContent = ModalHeader ? (_jsx(ModalHeader, { className: headerClassName, style: headerLayout?.style, children: titleContent })) : (_jsx("div", { className: headerClassName, style: headerLayout?.style, children: titleContent })); const modalContent = (_jsxs("form", { className: "flex flex-col p-4 gap-4", autoComplete: "off", onSubmit: handleSubmit(actionHandler), children: [headerContent, _jsx(ModalFields, { modalReady: modalReady, fieldProps: fieldProps, getValues: getValues, BodyComponent: ModalBody, bodyClassName: bodyLayout?.className, bodyStyle: bodyLayout?.style }), _jsx(ModalActions, { modalReady: modalReady, closeHandler: closeHandler, manualSubmit: manualSubmit, FooterComponent: ModalFooter, footerClassName: footerLayout?.className, footerStyle: footerLayout?.style, showFooterDivider: showFooterDivider })] })); return (_jsx(Portal, { closeTime: 200, portalOpen: open, portalTag: '#modal-portal', useBlur: modalReady.useBlur, children: ModalContainer ? (_jsx(ModalContainer, { className: `rounded bg-white relative w-auto h-auto min-h-[200px] min-w-[500px]${modalReady.useBlur ? ' shadow-md border border-gray-200' : ''}${containerLayout?.className ? ` ${containerLayout.className}` : ''}`, style: { ...modalReady.style, ...containerLayout?.style }, children: modalContent })) : (_jsx("div", { className: `rounded bg-white relative w-auto h-auto min-h-[200px] min-w-[500px]${modalReady.useBlur ? ' shadow-md border border-gray-200' : ''}${containerLayout?.className ? ` ${containerLayout.className}` : ''}`, style: { ...modalReady.style, ...containerLayout?.style }, children: modalContent })) })); }; export default Modal;