"use client" import * as ModalPrimitive from "@radix-ui/react-dialog" import useMergedRef from "@react-hook/merged-ref" import { motion } from "motion/react" import React, { ComponentProps, ComponentPropsWithoutRef, ElementRef, forwardRef, useCallback, useRef, useState, } from "react" import { tv, type VariantProps } from "tailwind-variants" import { useCallbackRef } from "../../hooks" import { Close } from "../../icons/material-symbols/outlined/Close" import { overlayVariants } from "../../styles/utils/overlay" import { customScrollbarClassname } from "../../styles/utils/scrollbar" import { classNames } from "../../utils" import { AnimateHeight } from "../AnimateHeight" import { Block, BlockProps } from "../Block" import { BottomSheet, BottomSheetBody, BottomSheetFooter, BottomSheetFooterButton, BottomSheetHeader, BottomSheetHeaderTitle, } from "../BottomSheet" import { Button, ButtonProps } from "../Button" import { FlexCenter, FlexCenterProps } from "../FlexCenter" import { FlexColumn } from "../FlexColumn" import { Media, useIsLessThanMd } from "../Media" import { Text } from "../Text" export type ModalProps = Pick< ComponentProps, "open" | "defaultOpen" | "onOpenChange" | "modal" > & VariantProps & Omit, "content"> & { content: React.ReactNode withCloseIcon?: boolean overrides?: { Overlay?: Partial> CloseButton?: { className?: string } } } const modalVariants = tv({ base: classNames( "z-modal rounded-xl border border-border-1 ease-out-quint relative bg-bg-app", "max-w-[calc(100vw-32px)]", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:duration-300 data-[state=open]:duration-300", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "focus-visible:outline-hidden", ), variants: { size: { sm: "w-[420px]", lg: "w-[550px]", }, }, }) const MODAL_CONTENT_SELECTOR = "[data-id='ModalContent']" const MODAL_FOOTER_SELECTOR = "[data-id='ModalFooter']" const ModalBase = forwardRef< ElementRef, ModalProps >(function Modal( { open, defaultOpen, onOpenChange, content, children, className, modal, withCloseIcon = true, overrides, size = "lg", ...props }, ref, ) { // We need to keep track of the open state internally so we can render the bottom sheet with the correct state on mobile const [openedState, setOpenedState] = useState(defaultOpen ?? open) const resolvedOpenState = open === undefined ? openedState : open // This is needed due to a bug seemingly in vaul where unmounting the component through Media still locks the cursor. const isLessThanMd = useIsLessThanMd() const triggerRef = useRef(null) const handleOnOpenChange = useCallback( (open: boolean) => { setOpenedState(open) onOpenChange?.(open) }, [onOpenChange], ) return ( <> {children} {children} {content} {withCloseIcon && ( )} ) }) const modalHeaderVariants = tv({ base: "px-6 pb-6", variants: { variant: { default: "pt-6", heroMedia: "flex-col justify-center pt-12", }, }, }) export const ModalHeader = ({ className, variant = "default", ...props }: FlexCenterProps & VariantProps) => { return ( <> ) } export const ModalHeaderTitle = forwardRef< ElementRef, ComponentPropsWithoutRef >(function ModalHeaderTitle({ className, children, ...props }, ref) { return ( <> {children}

{children}

) }) export const ModalBody = forwardRef, BlockProps>( function ModalBody({ className, children, ...props }, ref) { const [bodyRef, setBodyRef] = useCallbackRef() const mergedRef = useMergedRef(ref, setBodyRef) const hasFooter = Boolean( bodyRef?.current ?.closest(MODAL_CONTENT_SELECTOR) ?.querySelector(MODAL_FOOTER_SELECTOR), ) return ( <> {children} {children} ) }, ) export const ModalFooter = forwardRef< ElementRef, ComponentPropsWithoutRef >(function ModalFooter({ children, className, ...props }, ref) { return ( <> {children} {children} ) }) export const ModalFooterButton = forwardRef< ElementRef, ButtonProps >(function ModalFooterButton({ className, ...rest }, ref) { return ( <>