Files
Trilium/apps/client/src/widgets/react/Modal.tsx

91 lines
3.1 KiB
TypeScript
Raw Normal View History

2025-08-03 15:29:57 +03:00
import { useEffect, useRef } from "preact/hooks";
import { t } from "../../services/i18n";
import { ComponentChildren } from "preact";
2025-08-03 21:39:25 +03:00
import type { CSSProperties } from "preact/compat";
2025-08-03 15:29:57 +03:00
interface ModalProps {
className: string;
title: string;
size: "lg" | "sm";
children: ComponentChildren;
footer?: ComponentChildren;
2025-08-03 21:39:25 +03:00
maxWidth?: number;
2025-08-03 19:44:15 +03:00
/**
* If set, the modal body and footer will be wrapped in a form and the submit event will call this function.
* Especially useful for user input that can be submitted with Enter key.
*/
onSubmit?: () => void;
2025-08-03 23:20:32 +03:00
/** Called when the modal is shown. */
2025-08-03 15:29:57 +03:00
onShown?: () => void;
2025-08-03 23:20:32 +03:00
/** Called when the modal is hidden, either via close button, backdrop click or submit. */
onHidden?: () => void;
helpPageId?: string;
2025-08-03 15:29:57 +03:00
}
2025-08-03 23:20:32 +03:00
export default function Modal({ children, className, size, title, footer, onShown, onSubmit, helpPageId, maxWidth, onHidden: onHidden }: ModalProps) {
2025-08-03 15:29:57 +03:00
const modalRef = useRef<HTMLDivElement>(null);
2025-08-03 23:20:32 +03:00
if (onShown || onHidden) {
2025-08-03 15:29:57 +03:00
useEffect(() => {
const modalElement = modalRef.current;
if (modalElement) {
2025-08-03 23:20:32 +03:00
if (onShown) {
modalElement.addEventListener("shown.bs.modal", onShown);
}
if (onHidden) {
modalElement.addEventListener("hidden.bs.modal", onHidden);
}
2025-08-03 15:29:57 +03:00
}
});
2025-08-04 12:58:42 +03:00
}
2025-08-03 15:29:57 +03:00
2025-08-03 21:39:25 +03:00
const style: CSSProperties = {};
if (maxWidth) {
style.maxWidth = `${maxWidth}px`;
}
return (
2025-08-03 15:29:57 +03:00
<div className={`modal fade mx-auto ${className}`} tabIndex={-1} role="dialog" ref={modalRef}>
2025-08-03 21:39:25 +03:00
<div className={`modal-dialog modal-${size}`} style={style} role="document">
<div className="modal-content">
2025-08-03 15:29:57 +03:00
<div className="modal-header">
<h5 className="modal-title">{title}</h5>
{helpPageId && (
2025-08-04 12:58:42 +03:00
<button className="help-button" type="button" data-in-app-help={helpPageId} title={t("modal.help_title")}>?</button>
)}
2025-08-03 15:29:57 +03:00
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label={t("modal.close")}></button>
</div>
2025-08-03 19:44:15 +03:00
{onSubmit ? (
<form onSubmit={(e) => {
e.preventDefault();
onSubmit();
}}>
<ModalInner footer={footer}>{children}</ModalInner>
</form>
) : (
<ModalInner footer={footer}>
{children}
</ModalInner>
)}
</div>
</div>
</div>
);
2025-08-03 19:44:15 +03:00
}
function ModalInner({ children, footer }: Pick<ModalProps, "children" | "footer">) {
return (
<>
<div className="modal-body">
{children}
</div>
{footer && (
<div className="modal-footer">
{footer}
</div>
)}
</>
);
}