diff --git a/app/javascript/packs/base.jsx b/app/javascript/packs/base.jsx
index 7343e245b..f249a8b2b 100644
--- a/app/javascript/packs/base.jsx
+++ b/app/javascript/packs/base.jsx
@@ -5,11 +5,9 @@ import {
initializeMemberMenu,
} from '../topNavigation/utilities';
import { waitOnBaseData } from '../utilities/waitOnBaseData';
+import { showWindowModal, closeWindowModal } from '@utilities/showModal';
import * as Runtime from '@utilities/runtime';
-// Unique ID applied to modals created using window.Forem.showModal
-const WINDOW_MODAL_ID = 'window-modal';
-
// Namespace for functions which need to be accessed in plain JS initializers
window.Forem = {
preactImport: undefined,
@@ -53,54 +51,8 @@ window.Forem = {
originalTextArea,
);
},
- modalImports: undefined,
- getModalImports() {
- if (!this.modalImports) {
- this.modalImports = [import('@crayons/Modal'), this.getPreactImport()];
- }
- return Promise.all(this.modalImports);
- },
- showModal: async ({ title, contentSelector, size = 'small', onOpen }) => {
- const [{ Modal }, { render, h }] = await window.Forem.getModalImports();
-
- // Guard against two modals being opened at once
- let currentModalContainer = document.getElementById(WINDOW_MODAL_ID);
- if (currentModalContainer) {
- render(null, currentModalContainer);
- } else {
- currentModalContainer = document.createElement('div');
- currentModalContainer.setAttribute('id', WINDOW_MODAL_ID);
- document.body.appendChild(currentModalContainer);
- }
-
- render(
- {
- render(null, currentModalContainer);
- }}
- size={size}
- focusTrapSelector={`#${WINDOW_MODAL_ID}`}
- >
-
- ,
- currentModalContainer,
- );
-
- onOpen?.();
- },
- closeModal: async () => {
- const currentModalContainer = document.getElementById(WINDOW_MODAL_ID);
- if (currentModalContainer) {
- const { render } = await window.Forem.getPreactImport();
- render(null, currentModalContainer);
- }
- },
+ showModal: showWindowModal,
+ closeModal: closeWindowModal,
Runtime,
};
diff --git a/app/javascript/utilities/showModal.jsx b/app/javascript/utilities/showModal.jsx
new file mode 100644
index 000000000..202fabe1e
--- /dev/null
+++ b/app/javascript/utilities/showModal.jsx
@@ -0,0 +1,81 @@
+// Unique ID applied to modals created using the showWindowModal function
+const WINDOW_MODAL_ID = 'window-modal';
+
+// We only import these modules if a user actually triggers a modal. Here we cache them so they are only imported once.
+let preactImport;
+let modalImports;
+
+const getPreactImport = () => {
+ if (!preactImport) {
+ preactImport = import('preact');
+ }
+ return preactImport;
+};
+
+const getModalImports = () => {
+ if (!modalImports) {
+ modalImports = [import('@crayons/Modal'), getPreactImport()];
+ }
+ return Promise.all(modalImports);
+};
+
+/**
+ * This helper function finds the HTML with the given contentSelector, and presents it inside a Preact modal.
+ * Only one modal will be presented at any given time.
+ *
+ * @param {Object} args
+ * @param {string} args.title The title of the modal (presented at the top of the modal dialog)
+ * @param {string} args.contentSelector The CSS query to locate the HTML to be presented in the modal (e.g. '#my-modal-content')
+ * @param {string} args.size The size prop for the modal ('small', 'medium', or 'large')
+ * @param {Function} args.onOpen A callback function to run when the modal opens. This can be useful, for example, to attach any event listeners to items inside the modal.
+ */
+export const showWindowModal = async ({
+ title,
+ contentSelector,
+ size = 'small',
+ onOpen,
+}) => {
+ const [{ Modal }, { render, h }] = await getModalImports();
+
+ // Guard against two modals being opened at once
+ let currentModalContainer = document.getElementById(WINDOW_MODAL_ID);
+ if (currentModalContainer) {
+ render(null, currentModalContainer);
+ } else {
+ currentModalContainer = document.createElement('div');
+ currentModalContainer.setAttribute('id', WINDOW_MODAL_ID);
+ document.body.appendChild(currentModalContainer);
+ }
+
+ render(
+ {
+ render(null, currentModalContainer);
+ }}
+ size={size}
+ focusTrapSelector={`#${WINDOW_MODAL_ID}`}
+ >
+
+ ,
+ currentModalContainer,
+ );
+
+ onOpen?.();
+};
+
+/**
+ * This helper function closes any currently open window modal. This can be useful, for example, if your modal contains a "cancel" button.
+ */
+export const closeWindowModal = async () => {
+ const currentModalContainer = document.getElementById(WINDOW_MODAL_ID);
+ if (currentModalContainer) {
+ const { render } = await getPreactImport();
+ render(null, currentModalContainer);
+ }
+};