forked from aces/Loris
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
303 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
interface LoaderProps { | ||
size?: number; | ||
} | ||
|
||
/** | ||
* Loader component renders a spinner wheel of a specified size. | ||
* | ||
* @param {LoaderProps} props - The properties for the Loader component | ||
* @returns {JSX.Element} A div representing the loading spinner | ||
*/ | ||
const Loader = ({size = 120}: LoaderProps) => { | ||
const loaderStyle = { | ||
width: size, | ||
height: size, | ||
borderWidth: size/15, | ||
}; | ||
|
||
return <div className='loader' style={loaderStyle}/>; | ||
}; | ||
|
||
export default Loader; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
import {useState, PropsWithChildren, CSSProperties} from 'react'; | ||
import Swal from 'sweetalert2'; | ||
import Loader from './Loader'; | ||
import { | ||
ButtonElement, | ||
} from 'jsx/Form'; | ||
|
||
type ModalProps = PropsWithChildren<{ | ||
throwWarning?: boolean; | ||
show: boolean; | ||
onClose: () => void; | ||
onSubmit?: () => Promise<any>; | ||
onSuccess?: (data: any) => void; | ||
title?: string; | ||
}>; | ||
|
||
/** | ||
* Modal Component | ||
* | ||
* A React functional component that renders a modal dialog with optional | ||
* form submission and loading indicators. Supports asynchronous form submission | ||
* with loading and success feedback. | ||
* | ||
* @param {ModalProps} props - Properties for the modal component | ||
* @returns {JSX.Element} - A modal dialog box w/ optional submit functionality | ||
*/ | ||
const Modal = ({ | ||
throwWarning = false, | ||
show = false, | ||
onClose, | ||
onSubmit, | ||
onSuccess, | ||
title, | ||
children, | ||
}: ModalProps) => { | ||
const [loading, setLoading] = useState(false); // Tracks loading during submit | ||
const [success, setSuccess] = useState(false); // Tracks success after submit | ||
|
||
/** | ||
* Handles modal close event. Shows a confirmation if `throwWarning` is true. | ||
*/ | ||
const handleClose = () => { | ||
if (throwWarning) { // Display warning if enabled | ||
Swal.fire({ | ||
title: 'Are You Sure?', | ||
text: 'Leaving the form will result in the loss of any information ' + | ||
'entered.', | ||
type: 'warning', | ||
showCancelButton: true, | ||
confirmButtonText: 'Proceed', | ||
cancelButtonText: 'Cancel', | ||
}).then((result) => result.value && onClose()); | ||
} else { | ||
onClose(); // Close immediately if no warning | ||
} | ||
}; | ||
|
||
/** | ||
* Manages form submission with loading and success states, calling | ||
* `onSubmit` and handling modal state based on success or failure. | ||
*/ | ||
const submit = async () => { | ||
if (!onSubmit) return; // Ensure onSubmit exists | ||
|
||
setLoading(true); // Show loader | ||
|
||
try { | ||
const data = await onSubmit(); | ||
setLoading(false); | ||
setSuccess(true); // Show success | ||
|
||
await new Promise((resolve) => setTimeout(resolve, 2000)); // Close delay | ||
|
||
setSuccess(false); // Reset success state | ||
onClose(); // Close modal | ||
onSuccess?.(data); // call onSuccess if defined | ||
} catch { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
/** | ||
* Renders submit button if `onSubmit` is provided and no loading or success. | ||
* | ||
* @returns {JSX.Element | undefined} - The submit button if conditions are met | ||
*/ | ||
const submitButton = () => { | ||
if (onSubmit && !(loading || success)) { // Show button if conditions met | ||
return ( | ||
<div style={submitStyle}> | ||
<ButtonElement onUserInput={submit}/> | ||
</div> | ||
); | ||
} | ||
}; | ||
|
||
const headerStyle: CSSProperties = { | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
height: '40px', | ||
borderTopRightRadius: '10', | ||
fontSize: 24, | ||
padding: 35, | ||
borderBottom: '1px solid #DDDDDD', | ||
}; | ||
|
||
const glyphStyle: CSSProperties = { | ||
marginLeft: 'auto', | ||
cursor: 'pointer', | ||
}; | ||
|
||
const bodyStyle: CSSProperties = { | ||
padding: success ? 0 : '15px 15px', | ||
maxHeight: success ? 0 : '75vh', | ||
overflow: 'scroll', | ||
opacity: success ? 0 : 1, | ||
transition: '1s ease, opacity 0.3s', | ||
}; | ||
|
||
const modalContainer: CSSProperties = { | ||
display: 'block', | ||
position: 'fixed', | ||
zIndex: 9999, | ||
paddingTop: '100px', | ||
paddingBottom: '100px', | ||
left: 0, | ||
top: 0, | ||
width: '100%', | ||
height: '100%', | ||
overflow: 'auto', | ||
backgroundColor: 'rgba(0,0,0,0.7)', | ||
visibility: show ? 'visible' : 'hidden', | ||
}; | ||
|
||
const modalContent: CSSProperties = { | ||
opacity: show ? 1 : 0, | ||
top: show ? 0 : '-300px', | ||
position: 'relative', | ||
backgroundColor: '#fefefe', | ||
borderRadius: '7px', | ||
margin: 'auto', | ||
padding: 0, | ||
border: '1px solid #888', | ||
width: '700px', | ||
boxShadow: '0 4px 8px 0 rbga(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19)', | ||
transition: '0.4s ease', | ||
}; | ||
|
||
/** | ||
* Renders the modal children if `show` is true. | ||
* | ||
* @returns {JSX.Element | null} - The children to render or null if hidden | ||
*/ | ||
const renderChildren = () => show && children; | ||
|
||
const footerStyle: CSSProperties = { | ||
borderTop: '1px solid #DDDDDD', | ||
display: 'flex', | ||
flexDirection: 'row', | ||
alignItems: 'center', | ||
height: '40px', | ||
padding: '35px', | ||
backgroundColor: success ? '#e0ffec' : undefined, | ||
}; | ||
|
||
const submitStyle: CSSProperties = { | ||
marginLeft: 'auto', | ||
marginRight: '20px', | ||
}; | ||
|
||
const processStyle: CSSProperties = { | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'space-evenly', | ||
margin: '0px auto', | ||
width: '90px', | ||
}; | ||
|
||
/** | ||
* Loader element displayed during form submission. | ||
*/ | ||
const loader = loading && ( | ||
<div style={processStyle}> | ||
<Loader size={20}/> | ||
<h5 className='animate-flicker'>Saving</h5> | ||
</div> | ||
); | ||
|
||
/** | ||
* Success display element shown after successful form submission. | ||
*/ | ||
const successDisplay = success && ( | ||
<div style={processStyle}> | ||
<span | ||
style={{color: 'green', marginBottom: '2px'}} | ||
className='glyphicon glyphicon-ok-circle' | ||
/> | ||
<h5>Success!</h5> | ||
</div> | ||
); | ||
|
||
return ( | ||
<div style={modalContainer} onClick={handleClose}> | ||
<div style={modalContent} onClick={(e) => e.stopPropagation()}> | ||
<div style={headerStyle}> | ||
{title} | ||
<span style={glyphStyle} onClick={handleClose}>×</span> | ||
</div> | ||
<div> | ||
<div style={bodyStyle}>{renderChildren()}</div> | ||
<div style={footerStyle}> | ||
{loader} | ||
{successDisplay} | ||
{submitButton()} | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Modal; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import React, {useState, ElementType} from 'react'; | ||
import Modal, {ModalProps} from 'Modal'; | ||
import Form from 'jsx/Form'; | ||
|
||
interface TriggerableModalProps extends Omit<ModalProps, 'show'> { | ||
label: string; // Label for the default CTA trigger button | ||
onUserInput?: () => void; // Optional callback when the trigger is activated | ||
TriggerTag?: ElementType; // Custom component for the modal trigger | ||
} | ||
|
||
/** | ||
* TriggerableModal Component | ||
* | ||
* Renders a modal triggered by a custom or default CTA component, with `show` | ||
* controlled internally. | ||
* | ||
* @param {TriggerableModalProps} props - The properties for the component. | ||
* @returns {JSX.Element} The rendered TriggerableModal component. | ||
*/ | ||
const TriggerableModal = ({ | ||
label, | ||
onUserInput, | ||
TriggerTag = Form.CTA, // Default trigger component is CTA | ||
...modalProps // Spread other modal-related props to pass to Modal | ||
}: TriggerableModalProps) => { | ||
const [open, setOpen] = useState(false); | ||
|
||
/** | ||
* Handles closing the modal by updating the state and calling the optional | ||
* `onClose` callback provided in props. | ||
*/ | ||
const handleClose = () => { | ||
setOpen(false); | ||
modalProps.onClose?.(); // Call onClose if it exists | ||
}; | ||
|
||
/** | ||
* Trigger element to open the modal. Uses `TriggerTag` for a custom | ||
* trigger component, defaults to CTA. | ||
*/ | ||
const trigger = ( | ||
<TriggerTag | ||
label={label} | ||
onUserInput={() => { | ||
onUserInput?.(); // Call onUserInput if it exists | ||
setOpen(true); // Open the modal | ||
}} | ||
/> | ||
); | ||
|
||
return ( | ||
<> | ||
{trigger} | ||
<Modal {...modalProps} show={open} onClose={handleClose} /> | ||
</> | ||
); | ||
}; | ||
|
||
export default TriggerableModal; |