Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 53 additions & 15 deletions packages/unity-bootstrap-theme/src/js/modals.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,61 @@
import { EventHandler } from "./bootstrap-helper";

function initModals() {
document
.getElementById("openModalButton")
?.addEventListener("click", function () {
document.getElementById("uds-modal")?.classList.add("open");
document.getElementById("closeModalButton")?.focus();
});
const modal = document.getElementById("uds-modal");
if (!modal) return null;
const modalBackdrop = document.getElementById("uds-modal-backdrop");
if (!modalBackdrop) return null;
const openModalButton = document.getElementById("openModalButton");
if (!openModalButton) return null;
const closeModalButton = document.getElementById("closeModalButton");
if (!closeModalButton) return null;
const firstFocusable = modal.querySelector(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);

document
.getElementById("closeModalButton")
?.addEventListener("click", function () {
document.getElementById("uds-modal").classList.remove("open");
});
let previousFocus = null;
function focusTrap(e) {
// If relatedTarget is outside, move focus back inside
if (!modal.contains(e.relatedTarget)) {
firstFocusable?.focus();
}
}

document?.addEventListener("keydown", function (event) {
event.key === "Escape" &&
document.getElementById("uds-modal")?.classList.remove("open");
});
function openModal() {
// When opening: save current focus and move into modal
previousFocus = document.activeElement;
modal.classList.add("open");
modalBackdrop.classList.add("open");
// attach event listeners to trap focus and close modal
modalBackdrop.addEventListener("focusout", focusTrap);
modalBackdrop.addEventListener("click", closeModal, true);
document.addEventListener("keydown", closeModal);

// Focus the first interactive element inside (or body if none)
setTimeout(() => {
firstFocusable?.focus();
}, 200);
}

function closeModal({ type, target, key } = {}) {
if (
// escape key pressed
(type === "keydown" && key === "Escape") ||
// click on close button
(type === "click" && target === closeModalButton) ||
// click on backdrop
(type === "click" && target === modalBackdrop)
) {
modal.classList.remove("open");
modalBackdrop.classList.remove("open");
modalBackdrop.removeEventListener("focusout", focusTrap);
modalBackdrop.removeEventListener("click", closeModal, true);
document.removeEventListener("keydown", closeModal);
// When closing: restore original focus
previousFocus?.focus();
}
}
openModalButton.addEventListener("click", openModal);
}

EventHandler.on(window, "load.uds.modals", initModals);
Expand Down
11 changes: 10 additions & 1 deletion packages/unity-bootstrap-theme/src/scss/extends/_modals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
left: 0;
opacity: 0;
padding: 4rem 2rem;
position: absolute;
position: fixed;
right: 0;
top: 0;
z-index: 1030;
Expand Down Expand Up @@ -84,3 +84,12 @@
}
}
}

.uds-modal-main {
background-color: #0000;
pointer-events: none;
}

.uds-modal-container {
pointer-events: all;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const gaDefaultObject = {
*/
export const ButtonIconOnly = ({
color = "gray",
autoFocus = undefined,
icon = undefined,
innerRef = undefined,
onClick = undefined,
Expand All @@ -46,6 +47,7 @@ export const ButtonIconOnly = ({
}}
>
<button
autoFocus={autoFocus}
type="button"
className={`btn btn-circle btn-circle-alt-${color} ${
size === "large" && "btn-circle-large"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,37 @@ export default {
component: Modal,
args: {
open: false,
setOpen: undefined, //Untested. Could potentially be used as a custom react js useState setter function for handling the modal open state value
// openModalInput: ( //Untested with react js as it requires a custom useState which would violate the rules of hooks if defined here. Using a custom JSX input option will replace the default button allowing any type of custom input options for handling the modal
// <button
// autoFocus
// // inert={isBootstrap ? undefined : open ?? false} // React js may require a custom React.useState open state value to disable the button when the modal opens
// type="button"
// // onClick={setOpen(true)} // React js would require a custom React.useState setOpen function
// id="openModalButton" // This id value is necessary for bootstrap but not React.
// className={`btn btn-dark`}
// >
// testing custom button
// </button>
// ),
openModalButtonClassName: "btn-dark",
openModalButtonText: "Show modal",
children: (
<>
<h1>Content</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod incididuntåç ut labore et dolore magna aliqua eiusmod tempo.
</p>
<button type="button" className="btn btn-primary">
button
</button>
</>
),
},
};

//@ts-ignore
const modalTemplate = args => <Modal {...args} />;

export const Overview = {
Expand Down
Loading