'use client'; import React, { useEffect, useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords'; import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta'; import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta'; import type { Field } from '@documenso/prisma/client'; import { cn } from '../../lib/utils'; import { Card, CardContent } from '../../primitives/card'; export type FieldRootContainerProps = { field: Field; children: React.ReactNode; }; export type FieldContainerPortalProps = { field: Field; className?: string; children: React.ReactNode; cardClassName?: string; }; const getCardClassNames = ( field: Field, parsedField: TFieldMetaSchema | null, isValidating: boolean, checkBoxOrRadio: boolean, cardClassName?: string, ) => { const baseClasses = 'field-card-container relative z-20 h-full w-full transition-all'; const insertedClasses = 'bg-documenso/20 border-documenso ring-documenso-200 ring-offset-documenso-200 ring-2 ring-offset-2 dark:shadow-none'; const nonRequiredClasses = 'border-yellow-300 shadow-none ring-2 ring-yellow-100 ring-offset-2 ring-offset-yellow-100 dark:border-2'; const validatingClasses = 'border-orange-300 ring-1 ring-orange-300'; const requiredClasses = 'border-red-500 shadow-none ring-2 ring-red-200 ring-offset-2 ring-offset-red-200 hover:text-red-500'; const requiredCheckboxRadioClasses = 'border-dashed border-red-500'; if (checkBoxOrRadio) { return cn( { [insertedClasses]: field.inserted, 'ring-offset-yellow-200 border-dashed border-yellow-300 ring-2 ring-yellow-200 ring-offset-2 dark:shadow-none': !field.inserted && !parsedField?.required, 'shadow-none': !field.inserted, [validatingClasses]: !field.inserted && isValidating, [requiredCheckboxRadioClasses]: !field.inserted && parsedField?.required, }, cardClassName, ); } return cn( baseClasses, { [insertedClasses]: field.inserted, [nonRequiredClasses]: !field.inserted && !parsedField?.required, 'shadow-none': !field.inserted && checkBoxOrRadio, [validatingClasses]: !field.inserted && isValidating, [requiredClasses]: !field.inserted && parsedField?.required && !checkBoxOrRadio, }, cardClassName, ); }; export function FieldContainerPortal({ field, children, className = '', }: FieldContainerPortalProps) { const coords = useFieldPageCoords(field); const isCheckboxOrRadioField = field.type === 'CHECKBOX' || field.type === 'RADIO'; const isFieldSigned = field.inserted; const style = { top: `${coords.y}px`, left: `${coords.x}px`, // height: `${coords.height}px`, // width: `${coords.width}px`, ...((!isCheckboxOrRadioField) && { height: `${coords.height}px`, width: `${coords.width}px`, }), }; return createPortal(
{children}
, document.body, ); } export function FieldRootContainer({ field, children, cardClassName }: FieldContainerPortalProps) { const [isValidating, setIsValidating] = useState(false); const ref = React.useRef(null); useEffect(() => { if (!ref.current) { return; } const observer = new MutationObserver((_mutations) => { if (ref.current) { setIsValidating(ref.current.getAttribute('data-validate') === 'true'); } }); observer.observe(ref.current, { attributes: true, }); return () => { observer.disconnect(); }; }, []); const parsedField = useMemo( () => (field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : null), [field.fieldMeta], ); const isCheckboxOrRadio = useMemo( () => parsedField?.type === 'checkbox' || parsedField?.type === 'radio', [parsedField], ); const cardClassNames = useMemo( () => getCardClassNames(field, parsedField, isValidating, isCheckboxOrRadio, cardClassName), [field, parsedField, isValidating, isCheckboxOrRadio, cardClassName], ); return ( {children} ); }