Modal.tsx
⎇
Raw
1import { type Accessor, createEffect, type JSX, type Setter, untrack } from "solid-js";
2
3interface ModalProps {
4 show: Accessor<boolean>;
5 setShow: Setter<boolean>;
6 //runs when the dialog opens, to reset whatever state it holds. untracked, so reading a store in
7 //here cannot turn a later change to it into a second open
8 onOpen?: () => void;
9 //runs on every close path, including Escape and a backdrop click
10 onClose?: () => void;
11 style?: JSX.CSSProperties;
12 //a function, so the content can close the dialog without needing its own ref to the element
13 children: (close: () => void) => JSX.Element;
14}
15
16export function Modal(props: ModalProps) {
17 let dialogElement!: HTMLDialogElement;
18
19 createEffect(() => {
20 if (!props.show()) return;
21 dialogElement.showModal();
22 untrack(() => props.onOpen?.());
23 });
24
25 const close = () => dialogElement.close();
26
27 return (
28 <dialog
29 ref={dialogElement}
30 style={props.style}
31 //the close event covers every path, so the show signal cannot get out of sync with the element
32 onClose={() => {
33 props.onClose?.();
34 props.setShow(false);
35 }}
36 onclick={(e) => e.target.nodeName === "DIALOG" && close()}
37 >
38 {props.children(close)}
39 </dialog>
40 );
41}
42