"use client"; import { createElement, useContext, useEffect, useRef, type ComponentProps, type ComponentType, type ReactNode, } from "react"; import { Slot } from "./_ynrlib/radix-ui.js"; import { isIconComponent, type IconProps } from "./icons.js"; import { GlassContext, GlassLayer, type GlassAppearance, } from "./glass-surface.js"; export interface GlassButtonProps extends ComponentProps<"button">, GlassAppearance { asChild?: boolean; label?: string; icon?: ReactNode | ComponentType; iconOnly?: boolean; iconClassName?: string; size?: "sm" | "md" | "lg"; loading?: boolean; } export function GlassButton({ asChild = false, shade, blur, refraction, dispersion, label, icon, iconOnly = false, iconClassName = "", size = "md", loading = false, disabled = false, type = "button", className = "", children, onPointerEnter, onPointerMove, onPointerLeave, onBlur, ...props }: GlassButtonProps) { const { group } = useContext(GlassContext); const rect = useRef(null); const frame = useRef(null); useEffect( () => () => { if (frame.current !== null) cancelAnimationFrame(frame.current); }, [], ); function reset(node: HTMLButtonElement) { if (frame.current !== null) cancelAnimationFrame(frame.current); frame.current = null; rect.current = null; node.style.removeProperty("--glass-light-x"); node.style.removeProperty("--glass-light-y"); } const busy = disabled || loading; const Comp = asChild ? Slot.Root : "button"; return ( { props.onClickCapture?.(e); if (asChild && busy) { e.preventDefault(); e.stopPropagation(); } }} aria-busy={loading || props["aria-busy"] || undefined} className={`ynr-glass-button inline-flex items-center justify-center ${className}`} data-size={size} data-icon-only={iconOnly || undefined} data-grouped={group || undefined} onPointerEnter={(e) => { onPointerEnter?.(e); if (!e.defaultPrevented) rect.current = e.currentTarget.getBoundingClientRect(); }} onPointerMove={(e) => { onPointerMove?.(e); if ( e.defaultPrevented || busy || e.pointerType !== "mouse" || matchMedia("(prefers-reduced-motion: reduce)").matches ) return; const node = e.currentTarget; const bounds = rect.current ?? node.getBoundingClientRect(); rect.current = bounds; const x = (e.clientX - bounds.left - bounds.width / 2) * 0.6; const y = (e.clientY - bounds.top - bounds.height / 2) * 0.6; if (frame.current !== null) cancelAnimationFrame(frame.current); frame.current = requestAnimationFrame(() => { node.style.setProperty("--glass-light-x", `${x}px`); node.style.setProperty("--glass-light-y", `${y}px`); frame.current = null; }); }} onPointerLeave={(e) => { reset(e.currentTarget); onPointerLeave?.(e); }} onBlur={(e) => { reset(e.currentTarget); onBlur?.(e); }} > {!group && ( )} ); }