import * as PopoverPrimitive from "@radix-ui/react-popover"; import * as React from "react"; import { useEffect, useState } from "react"; import { cn } from "@sparkle/lib/utils"; const PopoverRoot = PopoverPrimitive.Root; const PopoverTrigger = PopoverPrimitive.Trigger; const PopoverPortal = PopoverPrimitive.Portal; const PopoverAnchor = PopoverPrimitive.Anchor; export interface PopoverContentProps extends React.ComponentPropsWithoutRef< typeof PopoverPrimitive.Content > { fullWidth?: boolean; mountPortal?: boolean; mountPortalContainer?: HTMLElement; preventAutoFocusOnClose?: boolean; } const PopoverContent = React.forwardRef< React.ElementRef, PopoverContentProps >( ( { className, align = "center", sideOffset = 4, mountPortal = true, mountPortalContainer, fullWidth = false, preventAutoFocusOnClose = true, onCloseAutoFocus, ...props }, ref ) => { const handleCloseAutoFocus = React.useCallback( (event: Event) => { if (preventAutoFocusOnClose) { event.preventDefault(); } onCloseAutoFocus?.(event); }, [preventAutoFocusOnClose, onCloseAutoFocus] ); const content = ( ); const [container, setContainer] = useState( mountPortalContainer ); useEffect(() => { if (mountPortal && !container) { const dialogElements = document.querySelectorAll( ".s-sheet[role=dialog][data-state=open]" ); const defaultContainer = dialogElements[dialogElements.length - 1]; setContainer(defaultContainer); } }, [mountPortal, container]); return mountPortal ? ( {content} ) : ( content ); } ); interface PopoverProps extends Omit { trigger: React.ReactNode; popoverTriggerAsChild?: boolean; content: React.ReactNode; } function Popover({ trigger, popoverTriggerAsChild = false, content, ...props }: PopoverProps) { return ( {trigger} {content} ); } PopoverContent.displayName = PopoverPrimitive.Content.displayName; interface AnchoredPopoverProps extends PopoverContentProps { open: boolean; anchorRef?: React.RefObject; children: React.ReactNode; } function AnchoredPopover({ open, anchorRef, children, className, ...props }: AnchoredPopoverProps) { const [position, setPosition] = useState({ top: "50%", left: "50%", width: "0px", height: "0px", }); useEffect(() => { if (!open) { return; } const updatePosition = () => { if (!anchorRef?.current) { setPosition({ top: "50%", left: "50%", width: "0px", height: "0px", }); return; } const rect = anchorRef.current.getBoundingClientRect(); setPosition({ top: `${rect.top}px`, left: `${rect.left}px`, width: `${rect.width}px`, height: `${rect.height}px`, }); }; updatePosition(); const resizeObserver = new ResizeObserver(updatePosition); if (anchorRef?.current) { resizeObserver.observe(anchorRef.current); } window.addEventListener("scroll", updatePosition, true); return () => { resizeObserver.disconnect(); window.removeEventListener("scroll", updatePosition, true); }; }, [open, anchorRef]); return ( e.preventDefault()} mountPortal={false} className={cn(className, !anchorRef && "s-translate-y-[-50%]")} > {children} ); } export { AnchoredPopover, Popover, PopoverAnchor, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger, };