"use client"; import type { GlobalOptions as ConfettiGlobalOptions, CreateTypes as ConfettiInstance, Options as ConfettiOptions, } from "canvas-confetti"; import confetti from "canvas-confetti"; import type React from "react"; import type { ReactNode } from "react"; import { createContext, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, } from "react"; import { Button } from "@/components/ui/button"; type Api = { fire: (options?: ConfettiOptions) => void; }; type Props = React.ComponentPropsWithRef<"canvas"> & { options?: ConfettiOptions; globalOptions?: ConfettiGlobalOptions; manualstart?: boolean; children?: ReactNode; }; export type ConfettiRef = Api | null; const ConfettiContext = createContext({} as Api); // Define component first const ConfettiComponent = forwardRef((props, ref) => { const { options, globalOptions = { resize: true, useWorker: true }, manualstart = false, children, ...rest } = props; const instanceRef = useRef(null); const canvasRef = useCallback( (node: HTMLCanvasElement) => { if (node !== null) { if (instanceRef.current) return; instanceRef.current = confetti.create(node, { ...globalOptions, resize: true, }); } else { if (instanceRef.current) { instanceRef.current.reset(); instanceRef.current = null; } } }, [globalOptions] ); const fire = useCallback( async (opts = {}) => { try { await instanceRef.current?.({ ...options, ...opts }); } catch (error) { console.error("Confetti error:", error); } }, [options] ); const api = useMemo( () => ({ fire, }), [fire] ); useImperativeHandle(ref, () => api, [api]); useEffect(() => { if (!manualstart) { (async () => { try { await fire(); } catch (error) { console.error("Confetti effect error:", error); } })(); } }, [manualstart, fire]); return ( {children} ); }); // Set display name immediately ConfettiComponent.displayName = "Confetti"; // Export as Confetti export const Confetti = ConfettiComponent; interface ConfettiButtonProps extends React.ComponentProps { options?: ConfettiOptions & ConfettiGlobalOptions & { canvas?: HTMLCanvasElement }; children?: React.ReactNode; } const ConfettiButtonComponent = ({ options, children, ...props }: ConfettiButtonProps) => { const handleClick = async (event: React.MouseEvent) => { try { const rect = event.currentTarget.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; await confetti({ ...options, origin: { x: x / window.innerWidth, y: y / window.innerHeight, }, }); } catch (error) { console.error("Confetti button error:", error); } }; return ( ); }; ConfettiButtonComponent.displayName = "ConfettiButton"; export const ConfettiButton = ConfettiButtonComponent;