mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 12:32:34 +10:00
185 lines
5.0 KiB
TypeScript
185 lines
5.0 KiB
TypeScript
'use client';
|
|
|
|
import React, { useRef } from 'react';
|
|
|
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
import { match } from 'ts-pattern';
|
|
|
|
import { useElementScaleSize } from '@documenso/lib/client-only/hooks/use-element-scale-size';
|
|
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
|
import {
|
|
DEFAULT_HANDWRITING_FONT_SIZE,
|
|
DEFAULT_STANDARD_FONT_SIZE,
|
|
MIN_HANDWRITING_FONT_SIZE,
|
|
MIN_STANDARD_FONT_SIZE,
|
|
} from '@documenso/lib/constants/pdf';
|
|
import { Field, FieldType } from '@documenso/prisma/client';
|
|
import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
|
import { FieldRootContainer } from '@documenso/ui/components/field/field';
|
|
|
|
export type SinglePlayerModeFieldContainerProps = {
|
|
field: FieldWithSignature;
|
|
children: React.ReactNode;
|
|
};
|
|
|
|
export type SinglePlayerModeFieldProps<T> = {
|
|
field: T;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
export function SinglePlayerModeFieldCardContainer({
|
|
field,
|
|
children,
|
|
}: SinglePlayerModeFieldContainerProps) {
|
|
return (
|
|
<FieldRootContainer field={field}>
|
|
<AnimatePresence mode="wait" initial={false}>
|
|
<motion.div
|
|
key={field.inserted ? 'inserted' : 'not-inserted'}
|
|
initial={{ opacity: 0 }}
|
|
animate={{
|
|
opacity: 1,
|
|
}}
|
|
exit={{ opacity: 0 }}
|
|
transition={{
|
|
duration: 0.2,
|
|
ease: 'easeIn',
|
|
}}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
</AnimatePresence>
|
|
</FieldRootContainer>
|
|
);
|
|
}
|
|
|
|
export function SinglePlayerModeSignatureField({
|
|
field,
|
|
onClick,
|
|
}: SinglePlayerModeFieldProps<FieldWithSignature>) {
|
|
const fontVariable = '--font-signature';
|
|
const fontVariableValue = getComputedStyle(document.documentElement).getPropertyValue(
|
|
fontVariable,
|
|
);
|
|
|
|
const minFontSize = MIN_HANDWRITING_FONT_SIZE;
|
|
const maxFontSize = DEFAULT_HANDWRITING_FONT_SIZE;
|
|
|
|
if (!isSignatureFieldType(field.type)) {
|
|
throw new Error('Invalid field type');
|
|
}
|
|
|
|
const $paragraphEl = useRef<HTMLParagraphElement>(null);
|
|
|
|
const { height, width } = useFieldPageCoords(field);
|
|
|
|
const scalingFactor = useElementScaleSize(
|
|
{
|
|
height,
|
|
width,
|
|
},
|
|
$paragraphEl,
|
|
maxFontSize,
|
|
fontVariableValue,
|
|
);
|
|
|
|
const fontSize = maxFontSize * scalingFactor;
|
|
|
|
const insertedBase64Signature = field.inserted && field.Signature?.signatureImageAsBase64;
|
|
const insertedTypeSignature = field.inserted && field.Signature?.typedSignature;
|
|
|
|
return (
|
|
<SinglePlayerModeFieldCardContainer field={field}>
|
|
{insertedBase64Signature ? (
|
|
<img
|
|
src={insertedBase64Signature}
|
|
alt="Your signature"
|
|
className="h-full w-full object-contain"
|
|
/>
|
|
) : insertedTypeSignature ? (
|
|
<p
|
|
ref={$paragraphEl}
|
|
style={{
|
|
fontSize: `clamp(${minFontSize}px, ${fontSize}px, ${maxFontSize}px)`,
|
|
fontFamily: `var(${fontVariable})`,
|
|
}}
|
|
className="font-signature"
|
|
>
|
|
{insertedTypeSignature}
|
|
</p>
|
|
) : (
|
|
<button
|
|
onClick={() => onClick?.()}
|
|
className="group-hover:text-primary text-muted-foreground absolute inset-0 duration-200"
|
|
>
|
|
Signature
|
|
</button>
|
|
)}
|
|
</SinglePlayerModeFieldCardContainer>
|
|
);
|
|
}
|
|
|
|
export function SinglePlayerModeCustomTextField({
|
|
field,
|
|
onClick,
|
|
}: SinglePlayerModeFieldProps<Field>) {
|
|
const fontVariable = '--font-sans';
|
|
const fontVariableValue = getComputedStyle(document.documentElement).getPropertyValue(
|
|
fontVariable,
|
|
);
|
|
|
|
const minFontSize = MIN_STANDARD_FONT_SIZE;
|
|
const maxFontSize = DEFAULT_STANDARD_FONT_SIZE;
|
|
|
|
if (isSignatureFieldType(field.type)) {
|
|
throw new Error('Invalid field type');
|
|
}
|
|
|
|
const $paragraphEl = useRef<HTMLParagraphElement>(null);
|
|
|
|
const { height, width } = useFieldPageCoords(field);
|
|
|
|
const scalingFactor = useElementScaleSize(
|
|
{
|
|
height,
|
|
width,
|
|
},
|
|
$paragraphEl,
|
|
maxFontSize,
|
|
fontVariableValue,
|
|
);
|
|
|
|
const fontSize = maxFontSize * scalingFactor;
|
|
|
|
return (
|
|
<SinglePlayerModeFieldCardContainer field={field}>
|
|
{field.inserted ? (
|
|
<p
|
|
ref={$paragraphEl}
|
|
style={{
|
|
fontSize: `clamp(${minFontSize}px, ${fontSize}px, ${maxFontSize}px)`,
|
|
fontFamily: `var(${fontVariable})`,
|
|
}}
|
|
>
|
|
{field.customText}
|
|
</p>
|
|
) : (
|
|
<button
|
|
onClick={() => onClick?.()}
|
|
className="group-hover:text-primary text-muted-foreground absolute inset-0 text-lg duration-200"
|
|
>
|
|
{match(field.type)
|
|
.with(FieldType.DATE, () => 'Date')
|
|
.with(FieldType.NAME, () => 'Name')
|
|
.with(FieldType.EMAIL, () => 'Email')
|
|
.with(FieldType.SIGNATURE, FieldType.FREE_SIGNATURE, () => 'Signature')
|
|
.otherwise(() => '')}
|
|
</button>
|
|
)}
|
|
</SinglePlayerModeFieldCardContainer>
|
|
);
|
|
}
|
|
|
|
const isSignatureFieldType = (fieldType: Field['type']) =>
|
|
fieldType === FieldType.SIGNATURE || fieldType === FieldType.FREE_SIGNATURE;
|