forked from
pds.ls/pdsls
atmosphere explorer
1import { ComponentProps, createEffect, createSignal, onCleanup, Show } from "solid-js";
2
3export interface ModalProps extends Pick<ComponentProps<"svg">, "children"> {
4 open?: boolean;
5 onClose?: () => void;
6 onClosed?: () => void;
7 closeOnClick?: boolean;
8 nonBlocking?: boolean;
9 alignTop?: boolean;
10 contentClass?: string;
11}
12
13export const CLOSE_DURATION = 200;
14
15export const Modal = (props: ModalProps) => {
16 const [mounted, setMounted] = createSignal(props.open ?? false);
17 const [closing, setClosing] = createSignal(false);
18
19 createEffect(() => {
20 if (props.open) {
21 setMounted(true);
22 setClosing(false);
23 } else if (mounted()) {
24 setClosing(true);
25 const t = setTimeout(() => {
26 setMounted(false);
27 setClosing(false);
28 props.onClosed?.();
29 }, CLOSE_DURATION);
30 onCleanup(() => clearTimeout(t));
31 }
32 });
33
34 return (
35 <Show when={mounted()}>
36 <div
37 data-modal
38 class="fixed inset-0 z-50 flex h-full max-h-none w-full max-w-none justify-center bg-transparent text-neutral-900 dark:text-neutral-200"
39 classList={{
40 "pointer-events-none": props.nonBlocking,
41 "items-start pt-18": props.alignTop,
42 "items-start pt-[20vh]": !props.alignTop,
43 }}
44 ref={(node) => {
45 const handleEscape = (e: KeyboardEvent) => {
46 if (e.key === "Escape") {
47 const modals = document.querySelectorAll("[data-modal]");
48 const lastModal = modals[modals.length - 1];
49 if (lastModal === node) {
50 e.preventDefault();
51 e.stopPropagation();
52 if (props.onClose) props.onClose();
53 }
54 }
55 };
56
57 createEffect(() => {
58 if (!props.nonBlocking) document.body.style.overflow = "hidden";
59 else document.body.style.overflow = "auto";
60 });
61
62 document.addEventListener("keydown", handleEscape);
63
64 onCleanup(() => {
65 document.body.style.overflow = "auto";
66 document.removeEventListener("keydown", handleEscape);
67 });
68 }}
69 onClick={(ev) => {
70 if (
71 (props.closeOnClick ?? true) &&
72 ev.target === ev.currentTarget &&
73 !props.nonBlocking
74 ) {
75 if (props.onClose) props.onClose();
76 }
77 }}
78 >
79 <div
80 class={`transition-all starting:scale-95 starting:opacity-0 ${props.contentClass ?? ""}`}
81 classList={{ "scale-95 opacity-0": closing() }}
82 style={{ "transition-duration": `${CLOSE_DURATION}ms` }}
83 >
84 {props.children}
85 </div>
86 </div>
87 </Show>
88 );
89};