kahuna/public/js/components/gr-confirmation-modal/gr-confirmation-modal.tsx (121 lines of code) (raw):

import * as React from "react"; import * as angular from "angular"; import { react2angular } from "react2angular"; import "./gr-confirmation-modal.css"; import {useEffect, useState, useRef} from "react"; const crossIcon = () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <rect width="24" height="24" fill="none" stroke="none"/> <path d="M7 17L16.8995 7.10051" stroke="#000" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"/> <path d="M7 7.00001L16.8995 16.8995" stroke="#000" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2"/> </svg>; const confirmationModal: React.FC = () => { const modalRef = useRef(null); const firstInputRef = useRef(null); const [isOpen, setIsOpen] = useState(false); const [showSingleBtn, setShowSingleBtn] = useState(false); const [title, setTitle] = useState(""); const [message, setMessage] = useState(""); const [cancelBtnTxt, setCancelBtnTxt] = useState(""); const [confirmBtnTxt, setConfirmBtnTxt] = useState(""); const [okayFunction, setOkayFunction] = useState(() => () => {return;}); const onOkay = () => { document.body.removeAttribute("aria-hidden"); setIsOpen(false); setShowSingleBtn(false); okayFunction(); }; const onCancel = () => { document.body.removeAttribute("aria-hidden"); setShowSingleBtn(false); setIsOpen(false); }; const handleDisplay = (e: any) => { document.body.setAttribute("aria-hidden", "true"); setOkayFunction(() => e.detail.okayFn); setTitle(e.detail.title); setMessage(e.detail.message); setCancelBtnTxt(e.detail.cancelBtnTxt); setConfirmBtnTxt(e.detail.confirmBtnTxt); e.detail.showSingleBtn ? setShowSingleBtn(true) : showSingleBtn; setIsOpen(true); }; const processedMessage = message.includes(';') ? message.split(';') .map((item, index) => <p key={index} className="styledParagraph">{item.trim()}</p>) : message; useEffect(() => { window.addEventListener('displayModal', handleDisplay); return () => { document.body.removeAttribute("aria-hidden"); window.removeEventListener('displayModal', handleDisplay); }; },[]); useEffect(() => { const handleKeyDown = (e: any) => { if ((e.target.className === 'closeButtonStyle' || e.target.className === 'confirmButtonStyle') && (e.keyCode === 32 || e.keyCode === 13)) { e.preventDefault(); // Prevent the default action to avoid scrolling when using Space e.target.click(); // Trigger the button's onClick event } if (e.keyCode === 27) { onCancel(); // Close on ESC } }; const trapTabKey = (e: any) => { if (e.keyCode !== 9) {return;} // Listen for TAB key only if (!modalRef.current) {return;} // Collect focusable items inside the modal const focusableModalElements = modalRef.current.querySelectorAll( 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' ); const firstElement = focusableModalElements[0]; const lastElement = focusableModalElements[focusableModalElements.length - 1]; // Trap focus inside modal if (e.shiftKey) { // if SHIFT + TAB if (document.activeElement === firstElement) { // loop focus back to last lastElement.focus(); e.preventDefault(); } } else { // if TAB if (document.activeElement === lastElement) { // loop focus back to first firstElement.focus(); e.preventDefault(); } } }; // Add event listeners document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', trapTabKey); // Set initial focus to the first input inside the modal if (firstInputRef.current) { firstInputRef.current.focus(); } // Remove event listeners on cleanup return () => { document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', trapTabKey); }; }, [isOpen]); return ( <div> {isOpen && ( <div aria-label={title} ref={modalRef}> <div className='backdropStyle'/> <div className='modalStyle'> <div className='modalTitle'>{title}</div> <div className='modalMessage'>{processedMessage}</div> <div className="closeIconStyle" aria-label={"Close"} onClick={() => {onCancel();}}> {crossIcon()} </div> <div className="buttonContainerStyle"> {!showSingleBtn && ( <div ref={firstInputRef} className='closeButtonStyle' aria-label={"Close"} tabIndex={0} onClick={() => {onCancel();}}> {cancelBtnTxt} </div> )} <div className='confirmButtonStyle' aria-label={"Confirm"} tabIndex={0} onClick={() => {onOkay();}}> {confirmBtnTxt} </div> </div> </div> </div>)} </div> ); }; export const ConfirmationModal = angular.module('gr.confirmationModal', []) .component('confirmationModal', react2angular(confirmationModal));