import * as stylex from "@stylexjs/stylex"; import { useCallback } from "react"; import { interaction } from "./mixins"; const styles = stylex.create({ ripple: { position: "absolute", inset: 0, overflow: "hidden", "::after": { content: "''", position: "absolute", inset: "var(--ripple-y) 0 0 var(--ripple-x)", filter: "blur(10px)", borderRadius: "50%", transition: "scale 0.5s linear, opacity 0.5s linear", // eslint-disable-next-line @stylexjs/valid-styles scale: { default: 4, "@starting-style": 0, }, // eslint-disable-next-line @stylexjs/valid-styles opacity: { default: 0, "@starting-style": 1, }, backgroundColor: "rgba(255, 255, 255, 0.7)", width: "var(--ripple-diameter)", height: "var(--ripple-diameter)", translate: "-50% -50%", transformOrigin: "center", }, }, }); const sxClassName = stylex.props(styles.ripple, interaction.inert).className; export default function useRippleEffect( disabled: boolean | undefined, ): (el: HTMLElement | null) => void { return useCallback( (node: HTMLElement | null) => { if (!node || disabled) { return; } const fn = (e: MouseEvent) => { if (e.button !== 0) { // Only trigger ripple on left click // See: https://stackoverflow.com/a/62616831 return; } const diameter = Math.max(node.clientWidth, node.clientHeight); const r = node.getBoundingClientRect(); const ripple = document.createElement("span"); ripple.style.overflow = "hidden"; // Partially based on // https://css-tricks.com/how-to-recreate-the-ripple-effect-of-material-design-buttons/ ripple.style.setProperty( "--ripple-x", `${e.clientX - r.left}px`, ); ripple.style.setProperty( "--ripple-y", `${e.clientY - r.top}px`, ); ripple.style.setProperty("--ripple-diameter", `${diameter}px`); ripple.dataset.ripple = "true"; const cs = getComputedStyle(node); const prevPosition = cs.position; node.style.position = "relative"; ripple.style.borderRadius = cs.borderRadius; ripple.addEventListener( "transitionend", () => { node.removeChild(ripple); node.style.position = prevPosition; }, { once: true, passive: true }, ); node.appendChild(ripple); if (sxClassName) { ripple.className = sxClassName; // Do we need to set "style"? } }; // Using native click-event, so we don't interfere with the events that the user might have added // (and we don't cause re-rendering and stuff) node.addEventListener("mousedown", fn, { passive: true }); return () => { node.removeEventListener("mousedown", fn); }; }, [disabled], ); }