import { useWindowSize } from "@solid-primitives/resize-observer"; import { MenuList } from "@suid/material"; import { createEffect, createSignal, type JSX, type ParentComponent, } from "solid-js"; import { ANIM_CURVE_STD } from "./theme"; type Props = { open?: boolean; onClose?: JSX.EventHandlerUnion; anchor: () => DOMRect; }; function px(n?: number) { if (n) { return `${n}px`; } else { return undefined; } } const Menu: ParentComponent = (props) => { let root: HTMLDialogElement; const windowSize = useWindowSize(); const [anchorPos, setAnchorPos] = createSignal<{ left?: number; top?: number; }>({}); let openAnimationOrigin: "lt" | "rt" = "lt"; createEffect(() => { if (props.open) { const a = props.anchor(); if (!root.open) { root.showModal(); const rend = root.getBoundingClientRect(); const { width } = windowSize; const { left, top, right } = a; if (left > width / 2) { openAnimationOrigin = "rt"; setAnchorPos({ left: right - rend.width, top, }); const overflow = root.style.overflow; root.style.overflow = "hidden"; const duration = (rend.height / 1600) * 1000; const easing = ANIM_CURVE_STD; const animation = root.animate( { height: [`${rend.height / 2}px`, `${rend.height}px`], }, { duration, easing, }, ); animation.addEventListener( "finish", () => (root.style.overflow = overflow), ); } else { openAnimationOrigin = "lt"; setAnchorPos({ left, top }); const overflow = root.style.overflow; root.style.overflow = "hidden"; const duration = (rend.height / 1600) * 1000; const easing = ANIM_CURVE_STD; const animation = root.animate( { height: [`${rend.height / 2}px`, `${rend.height}px`], width: [`${(rend.width / 4) * 3}px`, `${rend.width}px`], }, { duration, easing, }, ); animation.addEventListener( "finish", () => (root.style.overflow = overflow), ); } } else { // TODO: update the pos } } else { animateClose(); } }); const animateClose = () => { const rend = root.getBoundingClientRect(); if (openAnimationOrigin === "lt") { const overflow = root.style.overflow; root.style.overflow = "hidden"; const animation = root.animate( { height: [`${rend.height}px`, `${rend.height / 2}px`], width: [`${rend.width}px`, `${(rend.width / 4) * 3}px`], }, { duration: (rend.height / 2 / 1600) * 1000, easing: ANIM_CURVE_STD, }, ); animation.addEventListener("finish", () => { root.style.overflow = overflow; root.close(); }); } else { const overflow = root.style.overflow; root.style.overflow = "hidden"; const animation = root.animate( { height: [`${rend.height}px`, `${rend.height / 2}px`], }, { duration: (rend.height / 2 / 1600) * 1000, easing: ANIM_CURVE_STD, }, ); animation.addEventListener("finish", () => { root.style.overflow = overflow; root.close(); }); } }; return ( { if (e.target === root) { if (props.onClose) { if (Array.isArray(props.onClose)) { props.onClose[0](props.onClose[1], e); } else { ( props.onClose as ( event: Event & { currentTarget: HTMLDialogElement }, ) => void )(e); } } e.stopPropagation(); } }} style={{ position: "fixed", left: px(anchorPos().left), top: px(anchorPos().top), border: "none", "border-radius": "2px", padding: 0, "max-width": "560px", width: "max-content", /* FIXME: the content may be overflow */ "box-shadow": "var(--tutu-shadow-e8)", }} >
{props.children}
); }; export default Menu;