atmosphere explorer
at main 89 lines 2.7 kB view raw
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};