Modal.tsx
| 1 | import { type Accessor, createEffect, type JSX, type Setter, untrack } from "solid-js"; |
| 2 | |
| 3 | interface 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 | |
| 16 | export 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 |