chore: merge main, resolve biome formatting conflicts

Merge origin/main into feat/document-file-conversion. Conflicts were
format-only (Tailwind class ordering, single-line vs multi-line) plus
two semantic merges:

- files.helpers.ts: combine main's pending-PDF download path with the
  branch's original-source-file (DOCX/PNG/JPEG) download path
- download-pdf.ts: combine main's versionToFilenameSuffix helper with
  the branch's server-provided Content-Disposition filename support
This commit is contained in:
ephraimduncan
2026-05-12 11:28:47 +00:00
1495 changed files with 22068 additions and 33465 deletions
@@ -6,11 +6,7 @@ type AnimateGenericFadeInOutProps = {
motionKey?: string;
};
export const AnimateGenericFadeInOut = ({
children,
className,
motionKey,
}: AnimateGenericFadeInOutProps) => {
export const AnimateGenericFadeInOut = ({ children, className, motionKey }: AnimateGenericFadeInOutProps) => {
return (
<motion.section
key={motionKey}
+1 -1
View File
@@ -6,5 +6,5 @@ type ClientOnlyProps = {
};
export const ClientOnly = ({ children, fallback = null }: ClientOnlyProps) => {
return useHydrated() ? <>{children()}</> : <>{fallback}</>;
return useHydrated() ? children() : fallback;
};
@@ -1,11 +1,8 @@
import { useState } from 'react';
import { motion } from 'framer-motion';
import { AnimatePresence } from 'framer-motion';
import { CheckSquareIcon, CopyIcon } from 'lucide-react';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { Button } from '@documenso/ui/primitives/button';
import { AnimatePresence, motion } from 'framer-motion';
import { CheckSquareIcon, CopyIcon } from 'lucide-react';
import { useState } from 'react';
import { cn } from '../../lib/utils';
@@ -66,11 +63,7 @@ export const CopyTextButton = ({
)}
>
<div className="absolute">
{copiedTimeout ? (
<CheckSquareIcon className="h-3.5 w-3.5" />
) : (
<CopyIcon className="h-3.5 w-3.5" />
)}
{copiedTimeout ? <CheckSquareIcon className="h-3.5 w-3.5" /> : <CopyIcon className="h-3.5 w-3.5" />}
</div>
</div>
</motion.div>
@@ -1,17 +1,10 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { CheckIcon } from 'lucide-react';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { cn } from '@documenso/ui/lib/utils';
import {
CommandDialog,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@documenso/ui/primitives/command';
import { CommandDialog, CommandGroup, CommandInput, CommandItem, CommandList } from '@documenso/ui/primitives/command';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { CheckIcon } from 'lucide-react';
type LanguageSwitcherDialogProps = {
open: boolean;
@@ -48,12 +41,7 @@ export const LanguageSwitcherDialog = ({ open, setOpen }: LanguageSwitcherDialog
value={_(language.full)}
onSelect={async () => setLanguage(language.short)}
>
<CheckIcon
className={cn(
'mr-2 h-4 w-4',
i18n.locale === language.short ? 'opacity-100' : 'opacity-0',
)}
/>
<CheckIcon className={cn('mr-2 h-4 w-4', i18n.locale === language.short ? 'opacity-100' : 'opacity-0')} />
{_(language.full)}
</CommandItem>
))}
+2 -8
View File
@@ -1,7 +1,6 @@
import { useCallback, useMemo, useRef, useState } from 'react';
import { CheckIcon, CopyIcon } from 'lucide-react';
import { DateTime } from 'luxon';
import { useCallback, useMemo, useRef, useState } from 'react';
import { cn } from '../../lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover';
@@ -86,12 +85,7 @@ export const LocalTime = ({ date, className }: LocalTimeProps) => {
isCopied={copiedField === 'Local'}
onCopy={() => void onCopy('Local', local)}
/>
<TimeRow
label="UTC"
value={utc}
isCopied={copiedField === 'UTC'}
onCopy={() => void onCopy('UTC', utc)}
/>
<TimeRow label="UTC" value={utc} isCopied={copiedField === 'UTC'} onCopy={() => void onCopy('UTC', utc)} />
<TimeRow
label="Unix"
value={unix}
@@ -1,9 +1,8 @@
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import type { TDocumentEmailSettings } from '@documenso/lib/types/document-email';
import { DocumentEmailEvents } from '@documenso/lib/types/document-email';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import { cn } from '../../lib/utils';
import { Checkbox } from '../../primitives/checkbox';
@@ -16,11 +15,7 @@ type DocumentEmailCheckboxesProps = {
className?: string;
};
export const DocumentEmailCheckboxes = ({
value,
onChange,
className,
}: DocumentEmailCheckboxesProps) => {
export const DocumentEmailCheckboxes = ({ value, onChange, className }: DocumentEmailCheckboxesProps) => {
return (
<div className={cn('space-y-3', className)}>
<div className="flex flex-row items-center">
@@ -28,13 +23,11 @@ export const DocumentEmailCheckboxes = ({
id={DocumentEmailEvents.RecipientSigned}
className="h-5 w-5"
checked={value.recipientSigned}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.RecipientSigned]: Boolean(checked) })
}
onCheckedChange={(checked) => onChange({ ...value, [DocumentEmailEvents.RecipientSigned]: Boolean(checked) })}
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.RecipientSigned}
>
<Trans>Email the owner when a recipient signs</Trans>
@@ -52,9 +45,7 @@ export const DocumentEmailCheckboxes = ({
</h2>
<p>
<Trans>
This email is sent to the document owner when a recipient has signed the document.
</Trans>
<Trans>This email is sent to the document owner when a recipient has signed the document.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -72,7 +63,7 @@ export const DocumentEmailCheckboxes = ({
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.RecipientSigningRequest}
>
<Trans>Email recipients with a signing request</Trans>
@@ -90,9 +81,7 @@ export const DocumentEmailCheckboxes = ({
</h2>
<p>
<Trans>
This email is sent to the recipient requesting them to sign the document.
</Trans>
<Trans>This email is sent to the recipient requesting them to sign the document.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -110,7 +99,7 @@ export const DocumentEmailCheckboxes = ({
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.RecipientRemoved}
>
<Trans>Email recipients when they're removed from a pending document</Trans>
@@ -128,9 +117,7 @@ export const DocumentEmailCheckboxes = ({
</h2>
<p>
<Trans>
This email is sent to the recipient if they are removed from a pending document.
</Trans>
<Trans>This email is sent to the recipient if they are removed from a pending document.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -142,13 +129,11 @@ export const DocumentEmailCheckboxes = ({
id={DocumentEmailEvents.DocumentPending}
className="h-5 w-5"
checked={value.documentPending}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.DocumentPending]: Boolean(checked) })
}
onCheckedChange={(checked) => onChange({ ...value, [DocumentEmailEvents.DocumentPending]: Boolean(checked) })}
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.DocumentPending}
>
<Trans>Email the signer if the document is still pending</Trans>
@@ -167,8 +152,8 @@ export const DocumentEmailCheckboxes = ({
<p>
<Trans>
This email will be sent to the recipient who has just signed the document, if
there are still other recipients who have not signed yet.
This email will be sent to the recipient who has just signed the document, if there are still other
recipients who have not signed yet.
</Trans>
</p>
</TooltipContent>
@@ -187,7 +172,7 @@ export const DocumentEmailCheckboxes = ({
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.DocumentCompleted}
>
<Trans>Email recipients when the document is completed</Trans>
@@ -205,9 +190,7 @@ export const DocumentEmailCheckboxes = ({
</h2>
<p>
<Trans>
This will be sent to all recipients once the document has been fully completed.
</Trans>
<Trans>This will be sent to all recipients once the document has been fully completed.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -219,13 +202,11 @@ export const DocumentEmailCheckboxes = ({
id={DocumentEmailEvents.DocumentDeleted}
className="h-5 w-5"
checked={value.documentDeleted}
onCheckedChange={(checked) =>
onChange({ ...value, [DocumentEmailEvents.DocumentDeleted]: Boolean(checked) })
}
onCheckedChange={(checked) => onChange({ ...value, [DocumentEmailEvents.DocumentDeleted]: Boolean(checked) })}
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.DocumentDeleted}
>
<Trans>Email recipients when a pending document is deleted</Trans>
@@ -243,9 +224,7 @@ export const DocumentEmailCheckboxes = ({
</h2>
<p>
<Trans>
This will be sent to all recipients if a pending document has been deleted.
</Trans>
<Trans>This will be sent to all recipients if a pending document has been deleted.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -263,7 +242,7 @@ export const DocumentEmailCheckboxes = ({
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.OwnerDocumentCompleted}
>
<Trans>Email the owner when the document is completed</Trans>
@@ -281,10 +260,7 @@ export const DocumentEmailCheckboxes = ({
</h2>
<p>
<Trans>
This will be sent to the document owner once the document has been fully
completed.
</Trans>
<Trans>This will be sent to the document owner once the document has been fully completed.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -302,7 +278,7 @@ export const DocumentEmailCheckboxes = ({
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.OwnerDocumentCreated}
>
<Trans>Email the owner when a document is created from a direct template</Trans>
@@ -321,8 +297,8 @@ export const DocumentEmailCheckboxes = ({
<p>
<Trans>
This email is sent to the document owner when a recipient creates a document via a
direct template link.
This email is sent to the document owner when a recipient creates a document via a direct template
link.
</Trans>
</p>
</TooltipContent>
@@ -341,7 +317,7 @@ export const DocumentEmailCheckboxes = ({
/>
<label
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
htmlFor={DocumentEmailEvents.OwnerRecipientExpired}
>
<Trans>Send recipient expired email to the owner</Trans>
@@ -359,10 +335,7 @@ export const DocumentEmailCheckboxes = ({
</h2>
<p>
<Trans>
This will be sent to the document owner when a recipient's signing window has
expired.
</Trans>
<Trans>This will be sent to the document owner when a recipient's signing window has expired.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -1,14 +1,11 @@
import React from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
import { MultiSelect, type Option } from '@documenso/ui/primitives/multiselect';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
export interface DocumentGlobalAuthAccessSelectProps {
value?: string[];
@@ -41,15 +38,11 @@ export const DocumentGlobalAuthAccessSelect = ({
// Convert string array to Option array for MultiSelect
const selectedOptions =
(value
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
(value?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
// Convert default value to Option array
const defaultOptions =
(defaultValue
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
(defaultValue?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
const handleChange = (options: Option[]) => {
const values = options.map((option) => option.value);
@@ -102,8 +95,7 @@ export const DocumentGlobalAuthAccessTooltip = () => (
</li>
<li>
<Trans>
<strong>No restrictions</strong> - The document can be accessed directly by the URL sent
to the recipient
<strong>No restrictions</strong> - The document can be accessed directly by the URL sent to the recipient
</Trans>
</li>
</ul>
@@ -1,12 +1,11 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
import { DocumentActionAuth, DocumentAuth } from '@documenso/lib/types/document-auth';
import { MultiSelect, type Option } from '@documenso/ui/primitives/multiselect';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
export interface DocumentGlobalAuthActionSelectProps {
value?: string[];
@@ -41,15 +40,11 @@ export const DocumentGlobalAuthActionSelect = ({
// Convert string array to Option array for MultiSelect
const selectedOptions =
(value
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
(value?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
// Convert default value to Option array
const defaultOptions =
(defaultValue
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
(defaultValue?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
const handleChange = (options: Option[]) => {
const values = options.map((option) => option.value);
@@ -85,36 +80,33 @@ export const DocumentGlobalAuthActionTooltip = () => (
</h2>
<p>
<Trans>
The authentication methods required for recipients to sign the signature field.
</Trans>
<Trans>The authentication methods required for recipients to sign the signature field.</Trans>
</p>
<p>
<Trans>
These can be overriden by setting the authentication requirements directly on each
recipient in the next step. Multiple methods can be selected.
These can be overriden by setting the authentication requirements directly on each recipient in the next step.
Multiple methods can be selected.
</Trans>
</p>
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
<li>
<Trans>
<strong>Require passkey</strong> - The recipient must have an account and passkey
configured via their settings
<strong>Require passkey</strong> - The recipient must have an account and passkey configured via their
settings
</Trans>
</li>
<li>
<Trans>
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via
their settings
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via their settings
</Trans>
</li>
<li>
<Trans>
<strong>Require password</strong> - The recipient must have an account and password
configured via their settings, the password will be verified during signing
<strong>Require password</strong> - The recipient must have an account and password configured via their
settings, the password will be verified during signing
</Trans>
</li>
@@ -1,11 +1,3 @@
import { useState } from 'react';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { DocumentMeta, Field } from '@prisma/client';
import { SigningStatus } from '@prisma/client';
import { Clock, EyeOffIcon } from 'lucide-react';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
@@ -18,6 +10,12 @@ import { Badge } from '@documenso/ui/primitives/badge';
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
import { PopoverHover } from '@documenso/ui/primitives/popover';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { DocumentMeta, Field } from '@prisma/client';
import { SigningStatus } from '@prisma/client';
import { Clock, EyeOffIcon } from 'lucide-react';
import { useState } from 'react';
import { getRecipientColorStyles } from '../../lib/recipient-colors';
import { FieldContent } from '../../primitives/document-flow/field-content';
@@ -67,10 +65,7 @@ export type DocumentReadOnlyFieldsProps = {
showRecipientColors?: boolean;
};
export const mapFieldsWithRecipients = (
fields: Field[],
recipients: TRecipientLite[],
): DocumentField[] => {
export const mapFieldsWithRecipients = (fields: Field[], recipients: TRecipientLite[]): DocumentField[] => {
return fields.map((field) => {
const recipient = recipients.find((recipient) => recipient.id === field.recipientId) || {
id: field.recipientId,
@@ -110,18 +105,16 @@ export const DocumentReadOnlyFields = ({
readonly={true}
color={
showRecipientColors
? getRecipientColorStyles(
recipientIds.findIndex((id) => id === field.recipientId),
)
? getRecipientColorStyles(recipientIds.findIndex((id) => id === field.recipientId))
: undefined
}
>
{showRecipientTooltip && (
<div className="absolute -right-3 -top-3">
<div className="absolute -top-3 -right-3">
<PopoverHover
trigger={
<Avatar className="h-6 w-6 border-2 border-solid border-gray-200/50 transition-colors hover:border-gray-200">
<AvatarFallback className="bg-neutral-50 text-xs text-gray-400">
<Avatar className="h-6 w-6 border-2 border-gray-200/50 border-solid transition-colors hover:border-gray-200">
<AvatarFallback className="bg-neutral-50 text-gray-400 text-xs">
{extractInitials(field.recipient.name || field.recipient.email)}
</AvatarFallback>
</Avatar>
@@ -133,11 +126,7 @@ export const DocumentReadOnlyFields = ({
{showFieldStatus && (
<Badge
className="mx-auto mb-1 py-0.5"
variant={
field.recipient.signingStatus === SigningStatus.SIGNED
? 'default'
: 'secondary'
}
variant={field.recipient.signingStatus === SigningStatus.SIGNED ? 'default' : 'secondary'}
>
{field.recipient.signingStatus === SigningStatus.SIGNED ? (
<>
@@ -154,17 +143,15 @@ export const DocumentReadOnlyFields = ({
)}
<p className="text-center font-semibold">
<span>
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])} field
</span>
<span>{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])} field</span>
</p>
<p className="mt-1 text-center text-xs text-muted-foreground">
<p className="mt-1 text-center text-muted-foreground text-xs">
{getRecipientDisplayText(field.recipient)}
</p>
<button
className="absolute right-0 top-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
className="absolute top-0 right-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
onClick={() => handleHideField(field.secondaryId)}
title="Hide field"
>
@@ -1,5 +1,3 @@
import React from 'react';
import { Trans } from '@lingui/react/macro';
export const DocumentSendEmailMessageHelper = () => {
@@ -11,21 +9,15 @@ export const DocumentSendEmailMessageHelper = () => {
<ul className="mt-2 flex list-inside list-disc flex-col gap-y-2 text-sm">
<li className="text-muted-foreground">
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
{'{signer.name}'}
</code>{' '}
- <Trans>The signer's name</Trans>
<code className="rounded bg-muted-foreground/20 p-1 text-muted-foreground text-sm">{'{signer.name}'}</code> -{' '}
<Trans>The signer's name</Trans>
</li>
<li className="text-muted-foreground">
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
{'{signer.email}'}
</code>{' '}
- <Trans>The signer's email</Trans>
<code className="rounded bg-muted-foreground/20 p-1 text-muted-foreground text-sm">{'{signer.email}'}</code> -{' '}
<Trans>The signer's email</Trans>
</li>
<li className="text-muted-foreground">
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
{'{document.name}'}
</code>{' '}
<code className="rounded bg-muted-foreground/20 p-1 text-muted-foreground text-sm">{'{document.name}'}</code>{' '}
- <Trans>The document's name</Trans>
</li>
</ul>
@@ -1,17 +1,16 @@
import type { HTMLAttributes } from 'react';
import React, { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Copy, Sparkles } from 'lucide-react';
import { FaXTwitter } from 'react-icons/fa6';
import { useCopyShareLink } from '@documenso/lib/client-only/hooks/use-copy-share-link';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent';
import { trpc } from '@documenso/trpc/react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Copy, Sparkles } from 'lucide-react';
import type React from 'react';
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import { FaXTwitter } from 'react-icons/fa6';
import { cn } from '../../lib/utils';
import { Button } from '../../primitives/button';
@@ -31,12 +30,7 @@ export type DocumentShareButtonProps = HTMLAttributes<HTMLButtonElement> & {
trigger?: (_props: { loading: boolean; disabled: boolean }) => React.ReactNode;
};
export const DocumentShareButton = ({
token,
documentId,
className,
trigger,
}: DocumentShareButtonProps) => {
export const DocumentShareButton = ({ token, documentId, className, trigger }: DocumentShareButtonProps) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -147,9 +141,8 @@ export const DocumentShareButton = ({
<DialogDescription className="mt-4">
<Trans>
Rest assured, your document is strictly confidential and will never be shared. Only
your signing experience will be highlighted. Share your personalized signing card to
showcase your signature!
Rest assured, your document is strictly confidential and will never be shared. Only your signing
experience will be highlighted. Share your personalized signing card to showcase your signature!
</Trans>
</DialogDescription>
</DialogHeader>
@@ -157,8 +150,7 @@ export const DocumentShareButton = ({
<div className="flex w-full flex-col">
<div className="rounded-md border p-4">
I just {token ? 'signed' : 'sent'} a document in style with{' '}
<span className="font-medium text-blue-400">@documenso</span>
. Check it out!
<span className="font-medium text-blue-400">@documenso</span>. Check it out!
<span className="mt-2 block" />
<span
className={cn('break-all font-medium text-blue-400', {
@@ -168,12 +160,9 @@ export const DocumentShareButton = ({
{NEXT_PUBLIC_WEBAPP_URL()}/share/{shareLink?.slug || '...'}
</span>
<div
className={cn(
'mt-4 aspect-[1200/630] overflow-hidden rounded-lg border bg-muted/40',
{
'animate-pulse': !shareLink?.slug,
},
)}
className={cn('mt-4 aspect-[1200/630] overflow-hidden rounded-lg border bg-muted/40', {
'animate-pulse': !shareLink?.slug,
})}
>
{shareLink?.slug && (
<img
@@ -1,8 +1,7 @@
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
export const DocumentSignatureSettingsTooltip = () => {
return (
<Tooltip>
@@ -10,7 +9,7 @@ export const DocumentSignatureSettingsTooltip = () => {
<InfoIcon className="mx-2 h-4 w-4" />
</TooltipTrigger>
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
<h2>
<strong>
<Trans>Signature types</Trans>
@@ -18,9 +17,7 @@ export const DocumentSignatureSettingsTooltip = () => {
</h2>
<p>
<Trans>
The types of signatures that recipients are allowed to use when signing the document.
</Trans>
<Trans>The types of signatures that recipients are allowed to use when signing the document.</Trans>
</p>
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
@@ -1,22 +1,14 @@
import React, { forwardRef } from 'react';
import { DOCUMENT_VISIBILITY } from '@documenso/lib/constants/document-visibility';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { TeamMemberRole } from '@prisma/client';
import type { SelectProps } from '@radix-ui/react-select';
import { InfoIcon } from 'lucide-react';
import { DOCUMENT_VISIBILITY } from '@documenso/lib/constants/document-visibility';
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { forwardRef } from 'react';
export type DocumentVisibilitySelectType = SelectProps & {
currentTeamMemberRole?: string;
@@ -26,10 +18,7 @@ export type DocumentVisibilitySelectType = SelectProps & {
};
export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVisibilitySelectType>(
(
{ currentTeamMemberRole, isTeamSettings = false, disabled, canUpdateVisibility, ...props },
ref,
) => {
({ currentTeamMemberRole, isTeamSettings = false, disabled, canUpdateVisibility, ...props }, ref) => {
const { _ } = useLingui();
const isAdmin = currentTeamMemberRole === TeamMemberRole.ADMIN;
@@ -43,13 +32,8 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
</SelectTrigger>
<SelectContent position="popper">
<SelectItem value={DocumentVisibility.EVERYONE}>
{_(DOCUMENT_VISIBILITY.EVERYONE.value)}
</SelectItem>
<SelectItem
value={DocumentVisibility.MANAGER_AND_ABOVE}
disabled={!isAdmin && !isManager}
>
<SelectItem value={DocumentVisibility.EVERYONE}>{_(DOCUMENT_VISIBILITY.EVERYONE.value)}</SelectItem>
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE} disabled={!isAdmin && !isManager}>
{_(DOCUMENT_VISIBILITY.MANAGER_AND_ABOVE.value)}
</SelectItem>
<SelectItem value={DocumentVisibility.ADMIN} disabled={!isAdmin}>
@@ -89,8 +73,7 @@ export const DocumentVisibilityTooltip = () => {
</li>
<li>
<Trans>
<strong>Managers and above</strong> - Only managers and above can access and view the
document
<strong>Managers and above</strong> - Only managers and above can access and view the document
</Trans>
</li>
<li>
@@ -1,12 +1,10 @@
import { useCallback, useEffect, useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { SigningStatus } from '@prisma/client';
import type { Field, Recipient } from '@prisma/client';
import { ClockIcon, EyeOffIcon, LockIcon } from 'lucide-react';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { Trans, useLingui } from '@lingui/react/macro';
import type { Field, Recipient } from '@prisma/client';
import { SigningStatus } from '@prisma/client';
import { ClockIcon, EyeOffIcon, LockIcon } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { isTemplateRecipientEmailPlaceholder } from '../../../lib/constants/template';
import { extractInitials } from '../../../lib/utils/recipient-formatter';
@@ -20,15 +18,7 @@ import { PopoverHover } from '../../primitives/popover';
interface EnvelopeRecipientFieldTooltipProps {
field: Pick<
Field,
| 'id'
| 'inserted'
| 'positionX'
| 'positionY'
| 'width'
| 'height'
| 'page'
| 'type'
| 'fieldMeta'
'id' | 'inserted' | 'positionX' | 'positionY' | 'width' | 'height' | 'page' | 'type' | 'fieldMeta'
> & {
recipient: Pick<Recipient, 'name' | 'email' | 'signingStatus'>;
};
@@ -70,9 +60,7 @@ export function EnvelopeRecipientFieldTooltip({
});
const calculateCoords = useCallback(() => {
const $page = document.querySelector<HTMLElement>(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
);
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
if (!$page) {
return;
@@ -111,9 +99,7 @@ export function EnvelopeRecipientFieldTooltip({
}, [calculateCoords]);
useEffect(() => {
const $page = document.querySelector<HTMLElement>(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
);
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
if (!$page) {
return;
@@ -145,8 +131,8 @@ export function EnvelopeRecipientFieldTooltip({
>
<PopoverHover
trigger={
<Avatar className="absolute -left-3 -top-3 z-50 h-6 w-6 border-2 border-solid border-gray-200/50 transition-colors hover:border-gray-200">
<AvatarFallback className="bg-neutral-50 text-xs text-gray-400">
<Avatar className="absolute -top-3 -left-3 z-50 h-6 w-6 border-2 border-gray-200/50 border-solid transition-colors hover:border-gray-200">
<AvatarFallback className="bg-neutral-50 text-gray-400 text-xs">
{extractInitials(field.recipient.name || field.recipient.email)}
</AvatarFallback>
</Avatar>
@@ -191,12 +177,10 @@ export function EnvelopeRecipientFieldTooltip({
</span>
</p>
<p className="mt-1 text-center text-xs text-muted-foreground">
{getRecipientDisplayText(field.recipient)}
</p>
<p className="mt-1 text-center text-muted-foreground text-xs">{getRecipientDisplayText(field.recipient)}</p>
<button
className="absolute right-0 top-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
className="absolute top-0 right-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
onClick={() => setHideField(true)}
title="Hide field"
>
@@ -1,17 +1,10 @@
import { Plural, Trans } from '@lingui/react/macro';
import type {
TEnvelopeExpirationDurationPeriod,
TEnvelopeExpirationPeriod,
} from '@documenso/lib/constants/envelope-expiration';
import { Input } from '@documenso/ui/primitives/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Plural, Trans } from '@lingui/react/macro';
type ExpirationMode = 'duration' | 'disabled' | 'inherit';
@@ -35,9 +28,7 @@ const getAmount = (value: TEnvelopeExpirationPeriod | null | undefined): number
return 1;
};
const getUnit = (
value: TEnvelopeExpirationPeriod | null | undefined,
): TEnvelopeExpirationDurationPeriod['unit'] => {
const getUnit = (value: TEnvelopeExpirationPeriod | null | undefined): TEnvelopeExpirationDurationPeriod['unit'] => {
if (value && 'unit' in value) {
return value.unit;
}
@@ -1,5 +1,3 @@
import { Plural, Trans } from '@lingui/react/macro';
import type {
TEnvelopeReminderDurationPeriod,
TEnvelopeReminderPeriod,
@@ -7,13 +5,8 @@ import type {
} from '@documenso/lib/constants/envelope-reminder';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Plural, Trans } from '@lingui/react/macro';
type ReminderMode = 'enabled' | 'disabled' | 'inherit';
@@ -37,9 +30,7 @@ const getPeriodAmount = (period: TEnvelopeReminderPeriod | undefined): number =>
return 1;
};
const getPeriodUnit = (
period: TEnvelopeReminderPeriod | undefined,
): TEnvelopeReminderDurationPeriod['unit'] => {
const getPeriodUnit = (period: TEnvelopeReminderPeriod | undefined): TEnvelopeReminderDurationPeriod['unit'] => {
if (period && 'unit' in period) {
return period.unit;
}
@@ -87,9 +78,7 @@ export const ReminderSettingsPicker = ({
});
};
const updateSendAfter = (
updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>,
) => {
const updateSendAfter = (updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>) => {
const newAmount = Math.max(1, Math.floor(updates.amount ?? sendAfterAmount));
const newUnit = updates.unit ?? sendAfterUnit;
@@ -99,9 +88,7 @@ export const ReminderSettingsPicker = ({
});
};
const updateRepeatEvery = (
updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>,
) => {
const updateRepeatEvery = (updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>) => {
const newAmount = Math.max(1, Math.floor(updates.amount ?? repeatEveryAmount));
const newUnit = updates.unit ?? repeatEveryUnit;
@@ -151,7 +138,7 @@ export const ReminderSettingsPicker = ({
{mode === 'enabled' && (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<Label className="text-sm text-muted-foreground">
<Label className="text-muted-foreground text-sm">
<Trans>Send first reminder after</Trans>
</Label>
@@ -181,7 +168,7 @@ export const ReminderSettingsPicker = ({
</div>
<div className="flex flex-col gap-2">
<Label className="text-sm text-muted-foreground">
<Label className="text-muted-foreground text-sm">
<Trans>Then repeat every</Trans>
</Label>
@@ -1,12 +1,10 @@
import { useCallback, useEffect, useState } from 'react';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import type { Field } from '@prisma/client';
import { TooltipArrow } from '@radix-ui/react-tooltip';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { useCallback, useEffect, useState } from 'react';
import { cn } from '../../lib/utils';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../primitives/tooltip';
@@ -26,21 +24,13 @@ const tooltipVariants = cva('font-semibold', {
interface EnvelopeFieldToolTipProps extends VariantProps<typeof tooltipVariants> {
children: React.ReactNode;
className?: string;
field: Pick<
Field,
'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'
>;
field: Pick<Field, 'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'>;
}
/**
* Renders a tooltip for a given field.
*/
export function EnvelopeFieldToolTip({
children,
color,
className = '',
field,
}: EnvelopeFieldToolTipProps) {
export function EnvelopeFieldToolTip({ children, color, className = '', field }: EnvelopeFieldToolTipProps) {
const [coords, setCoords] = useState({
x: 0,
y: 0,
@@ -49,9 +39,7 @@ export function EnvelopeFieldToolTip({
});
const calculateCoords = useCallback(() => {
const $page = document.querySelector<HTMLElement>(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
);
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
if (!$page) {
return;
@@ -90,9 +78,7 @@ export function EnvelopeFieldToolTip({
}, [calculateCoords]);
useEffect(() => {
const $page = document.querySelector<HTMLElement>(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
);
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
if (!$page) {
return;
@@ -124,10 +110,7 @@ export function EnvelopeFieldToolTip({
<Tooltip delayDuration={0} open={!field.inserted || !field.fieldMeta}>
<TooltipTrigger className="absolute inset-0 w-full"></TooltipTrigger>
<TooltipContent
className={tooltipVariants({ color, className: cn(className, 'z-40') })}
sideOffset={2}
>
<TooltipContent className={tooltipVariants({ color, className: cn(className, 'z-40') })} sideOffset={2}>
{children}
<TooltipArrow />
</TooltipContent>
+3 -12
View File
@@ -1,18 +1,12 @@
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
import type { Field } from '@prisma/client';
import { TooltipArrow } from '@radix-ui/react-tooltip';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import { createPortal } from 'react-dom';
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
import { cn } from '../..//lib/utils';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '../..//primitives/tooltip';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../..//primitives/tooltip';
const tooltipVariants = cva('font-semibold', {
variants: {
@@ -29,10 +23,7 @@ const tooltipVariants = cva('font-semibold', {
interface FieldToolTipProps extends VariantProps<typeof tooltipVariants> {
children: React.ReactNode;
className?: string;
field: Pick<
Field,
'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'
>;
field: Pick<Field, 'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'>;
}
/**
+8 -28
View File
@@ -1,16 +1,11 @@
import React, { useEffect, useMemo, useState } from 'react';
import { type Field, FieldType } from '@prisma/client';
import { createPortal } from 'react-dom';
import { useElementBounds } from '@documenso/lib/client-only/hooks/use-element-bounds';
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
import { useIsPageInDom } from '@documenso/lib/client-only/hooks/use-is-page-in-dom';
import {
PDF_VIEWER_CONTENT_SELECTOR,
PDF_VIEWER_PAGE_SELECTOR,
} from '@documenso/lib/constants/pdf-viewer';
import { PDF_VIEWER_CONTENT_SELECTOR, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
import { type Field, FieldType } from '@prisma/client';
import React, { useEffect, useMemo, useState } from 'react';
import { createPortal } from 'react-dom';
import type { RecipientColorStyles } from '../../lib/recipient-colors';
import { cn } from '../../lib/utils';
@@ -21,17 +16,11 @@ export type FieldContainerPortalProps = {
children: React.ReactNode;
};
export function FieldContainerPortal({
field,
children,
className = '',
}: FieldContainerPortalProps) {
export function FieldContainerPortal({ field, children, className = '' }: FieldContainerPortalProps) {
const alternativePortalRoot = document.getElementById('document-field-portal-root');
const coords = useFieldPageCoords(field);
const $pageBounds = useElementBounds(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
);
const $pageBounds = useElementBounds(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
const maxWidth = $pageBounds?.width ? $pageBounds.width - coords.x : undefined;
@@ -77,13 +66,7 @@ export type FieldRootContainerProps = {
readonly?: boolean;
};
export function FieldRootContainer({
field,
children,
color,
className,
readonly,
}: FieldRootContainerProps) {
export function FieldRootContainer({ field, children, color, className, readonly }: FieldRootContainerProps) {
const [isValidating, setIsValidating] = useState(false);
const isPageInDom = useIsPageInDom(field.page);
@@ -101,10 +84,7 @@ export function FieldRootContainer({
// pick up the validation state immediately.
const pdfContent = document.querySelector(PDF_VIEWER_CONTENT_SELECTOR);
if (
pdfContent?.getAttribute('data-validate-fields') === 'true' &&
isFieldUnsignedAndRequired(field)
) {
if (pdfContent?.getAttribute('data-validate-fields') === 'true' && isFieldUnsignedAndRequired(field)) {
ref.current.setAttribute('data-validate', 'true');
setIsValidating(true);
}
@@ -1,14 +1,11 @@
import React from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
import { RecipientActionAuth } from '@documenso/lib/types/document-auth';
import { MultiSelect, type Option } from '@documenso/ui/primitives/multiselect';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { InfoIcon } from 'lucide-react';
import { cn } from '../../lib/utils';
@@ -45,15 +42,11 @@ export const RecipientActionAuthSelect = ({
// Convert string array to Option array for MultiSelect
const selectedOptions =
(value
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
(value?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
// Convert default value to Option array
const defaultOptions =
(defaultValue
?.map((val) => authOptions.find((option) => option.value === val))
.filter(Boolean) as Option[]) || [];
(defaultValue?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
const handleChange = (options: Option[]) => {
const values = options.map((option) => option.value);
@@ -76,7 +69,7 @@ export const RecipientActionAuthSelect = ({
<Tooltip>
<TooltipTrigger
className={cn('absolute right-2 top-1/2 -translate-y-1/2', {
className={cn('absolute top-1/2 right-2 -translate-y-1/2', {
'right-8': selectedOptions.length > 0,
})}
>
@@ -95,28 +88,25 @@ export const RecipientActionAuthSelect = ({
</p>
<p className="mt-2">
<Trans>
These will override any global settings. Multiple methods can be selected.
</Trans>
<Trans>These will override any global settings. Multiple methods can be selected.</Trans>
</p>
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
<li>
<Trans>
<strong>Inherit authentication method</strong> - Use the global action signing
authentication method configured in the "General Settings" step
<strong>Inherit authentication method</strong> - Use the global action signing authentication method
configured in the "General Settings" step
</Trans>
</li>
<li>
<Trans>
<strong>Require passkey</strong> - The recipient must have an account and passkey
configured via their settings
<strong>Require passkey</strong> - The recipient must have an account and passkey configured via their
settings
</Trans>
</li>
<li>
<Trans>
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled
via their settings
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via their settings
</Trans>
</li>
<li>
@@ -1,9 +1,9 @@
import React, { useRef, useState } from 'react';
import { Popover, PopoverContent } from '@documenso/ui/primitives/popover';
import { Trans } from '@lingui/react/macro';
import { PopoverAnchor } from '@radix-ui/react-popover';
import { Popover, PopoverContent } from '@documenso/ui/primitives/popover';
import type React from 'react';
import { useRef, useState } from 'react';
import { Command, CommandGroup, CommandItem } from '../../primitives/command';
import { Input } from '../../primitives/input';
@@ -77,11 +77,7 @@ export const RecipientAutoCompleteInput = ({
{/* Not using <CommandEmpty /> here due to some weird behaviour */}
{options.length === 0 && (
<div className="px-2 py-1.5 text-sm">
{loading ? (
<Trans>Loading suggestions...</Trans>
) : (
<Trans>No suggestions found</Trans>
)}
{loading ? <Trans>Loading suggestions...</Trans> : <Trans>No suggestions found</Trans>}
</div>
)}
@@ -1,13 +1,11 @@
import React, { forwardRef } from 'react';
import { ROLE_ICONS } from '@documenso/ui/primitives/recipient-role-icons';
import { Select, SelectContent, SelectItem, SelectTrigger } from '@documenso/ui/primitives/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { Trans } from '@lingui/react/macro';
import { RecipientRole } from '@prisma/client';
import type { SelectProps } from '@radix-ui/react-select';
import { InfoIcon } from 'lucide-react';
import { ROLE_ICONS } from '@documenso/ui/primitives/recipient-role-icons';
import { Select, SelectContent, SelectItem, SelectTrigger } from '@documenso/ui/primitives/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { forwardRef } from 'react';
import { cn } from '../../lib/utils';
@@ -21,17 +19,7 @@ export type RecipientRoleSelectProps = SelectProps & {
};
export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSelectProps>(
(
{
hideAssistantRole,
hideCCerRole,
hideViewerRole,
hideApproverRole,
isAssistantEnabled = true,
...props
},
ref,
) => (
({ hideAssistantRole, hideCCerRole, hideViewerRole, hideApproverRole, isAssistantEnabled = true, ...props }, ref) => (
<Select {...props}>
<SelectTrigger ref={ref} className="w-[50px] bg-background p-2" title={props.value}>
{/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */}
@@ -51,9 +39,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
</TooltipTrigger>
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
<p>
<Trans>
The recipient is required to sign the document for it to be completed.
</Trans>
<Trans>The recipient is required to sign the document for it to be completed.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -73,9 +59,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
</TooltipTrigger>
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
<p>
<Trans>
The recipient is required to approve the document for it to be completed.
</Trans>
<Trans>The recipient is required to approve the document for it to be completed.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -96,9 +80,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
</TooltipTrigger>
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
<p>
<Trans>
The recipient is required to view the document for it to be completed.
</Trans>
<Trans>The recipient is required to view the document for it to be completed.</Trans>
</p>
</TooltipContent>
</Tooltip>
@@ -120,8 +102,8 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
<p>
<Trans>
The recipient is not required to take any action and receives a copy of the
document after it is completed.
The recipient is not required to take any action and receives a copy of the document after it is
completed.
</Trans>
</p>
</TooltipContent>
@@ -134,10 +116,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
<SelectItem
value={RecipientRole.ASSISTANT}
disabled={!isAssistantEnabled}
className={cn(
!isAssistantEnabled &&
'cursor-not-allowed opacity-50 data-[disabled]:pointer-events-auto',
)}
className={cn(!isAssistantEnabled && 'cursor-not-allowed opacity-50 data-[disabled]:pointer-events-auto')}
>
<div className="flex items-center">
<div className="flex w-[150px] items-center">
@@ -152,14 +131,10 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
<p>
{isAssistantEnabled ? (
<Trans>
The recipient can prepare the document for later signers by pre-filling
suggest values.
The recipient can prepare the document for later signers by pre-filling suggest values.
</Trans>
) : (
<Trans>
Assistant role is only available when the document is in sequential signing
mode.
</Trans>
<Trans>Assistant role is only available when the document is in sequential signing mode.</Trans>
)}
</p>
</TooltipContent>
+14 -41
View File
@@ -1,8 +1,7 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import type { Signature } from '@prisma/client';
import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion';
import { P, match } from 'ts-pattern';
import { useCallback, useEffect, useRef, useState } from 'react';
import { match, P } from 'ts-pattern';
import { cn } from '../lib/utils';
import { Card, CardContent } from '../primitives/card';
@@ -17,19 +16,12 @@ export type SigningCardProps = {
/**
* 2D signing card.
*/
export const SigningCard = ({
className,
name,
signature,
signingCelebrationImage,
}: SigningCardProps) => {
export const SigningCard = ({ className, name, signature, signingCelebrationImage }: SigningCardProps) => {
return (
<div className={cn('relative w-full max-w-sm md:max-w-md', className)}>
<SigningCardContent name={name} signature={signature} />
{signingCelebrationImage && (
<SigningCardImage signingCelebrationImage={signingCelebrationImage} />
)}
{signingCelebrationImage && <SigningCardImage signingCelebrationImage={signingCelebrationImage} />}
</div>
);
};
@@ -37,12 +29,7 @@ export const SigningCard = ({
/**
* 3D signing card that follows the mouse movement within a certain range.
*/
export const SigningCard3D = ({
className,
name,
signature,
signingCelebrationImage,
}: SigningCardProps) => {
export const SigningCard3D = ({ className, name, signature, signingCelebrationImage }: SigningCardProps) => {
// Should use % based dimensions by calculating the window height/width.
const boundary = 400;
@@ -127,12 +114,9 @@ export const SigningCard3D = ({
}, [onMouseMove]);
return (
<div
className={cn('relative w-full max-w-sm md:max-w-md', className)}
style={{ perspective: 800 }}
>
<div className={cn('relative w-full max-w-sm md:max-w-md', className)} style={{ perspective: 800 }}>
<motion.div
className="bg-background w-full rounded-lg [--sheen-color:180_180_180] dark:[--sheen-color:200_200_200]"
className="w-full rounded-lg bg-background [--sheen-color:180_180_180] dark:[--sheen-color:200_200_200]"
ref={cardRef}
style={{
perspective: '800',
@@ -145,9 +129,7 @@ export const SigningCard3D = ({
<SigningCardContent className="bg-transparent" name={name} signature={signature} />
</motion.div>
{signingCelebrationImage && (
<SigningCardImage signingCelebrationImage={signingCelebrationImage} />
)}
{signingCelebrationImage && <SigningCardImage signingCelebrationImage={signingCelebrationImage} />}
</div>
);
};
@@ -161,34 +143,25 @@ type SigningCardContentProps = {
const SigningCardContent = ({ className, name, signature }: SigningCardContentProps) => {
return (
<Card
className={cn(
'group z-10 mx-auto flex aspect-[21/9] w-full items-center justify-center',
className,
)}
className={cn('group z-10 mx-auto flex aspect-[21/9] w-full items-center justify-center', className)}
degrees={-145}
gradient
>
<CardContent
className="font-signature p-6 text-center"
className="p-6 text-center font-signature"
style={{
container: 'main',
}}
>
{match(signature)
.with({ signatureImageAsBase64: P.string }, (signature) => (
<img
src={signature.signatureImageAsBase64}
alt="signature"
className="h-full max-w-[100%] dark:invert"
/>
<img src={signature.signatureImageAsBase64} alt="signature" className="h-full max-w-[100%] dark:invert" />
))
.with({ typedSignature: P.string }, (signature) => (
<span
className="text-muted-foreground/60 group-hover:text-primary/80 break-all font-semibold duration-300"
className="break-all font-semibold text-muted-foreground/60 duration-300 group-hover:text-primary/80"
style={{
fontSize: `max(min(4rem, ${(100 / signature.typedSignature.length / 2).toFixed(
4,
)}cqw), 1.875rem)`,
fontSize: `max(min(4rem, ${(100 / signature.typedSignature.length / 2).toFixed(4)}cqw), 1.875rem)`,
}}
>
{signature.typedSignature}
@@ -196,7 +169,7 @@ const SigningCardContent = ({ className, name, signature }: SigningCardContentPr
))
.otherwise(() => (
<span
className="text-muted-foreground/60 group-hover:text-primary/80 break-all font-semibold duration-300"
className="break-all font-semibold text-muted-foreground/60 duration-300 group-hover:text-primary/80"
style={{
fontSize: `max(min(4rem, ${(100 / name.length / 2).toFixed(4)}cqw), 1.875rem)`,
}}
@@ -1,50 +1,36 @@
import React, { forwardRef } from 'react';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { t } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { TemplateType } from '@prisma/client';
import type { SelectProps } from '@radix-ui/react-select';
import { InfoIcon } from 'lucide-react';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
import { forwardRef } from 'react';
export type TemplateTypeSelectProps = SelectProps;
export const TemplateTypeSelect = forwardRef<HTMLButtonElement, TemplateTypeSelectProps>(
({ ...props }, ref) => {
useLingui();
export const TemplateTypeSelect = forwardRef<HTMLButtonElement, TemplateTypeSelectProps>(({ ...props }, ref) => {
useLingui();
return (
<Select {...props}>
<SelectTrigger ref={ref} className="bg-background">
<SelectValue />
</SelectTrigger>
return (
<Select {...props}>
<SelectTrigger ref={ref} className="bg-background">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={TemplateType.PRIVATE}>{t`Private`}</SelectItem>
<SelectItem value={TemplateType.PUBLIC}>{t`Public`}</SelectItem>
<SelectItem value={TemplateType.ORGANISATION}>{t`Organisation`}</SelectItem>
</SelectContent>
</Select>
);
},
);
<SelectContent>
<SelectItem value={TemplateType.PRIVATE}>{t`Private`}</SelectItem>
<SelectItem value={TemplateType.PUBLIC}>{t`Public`}</SelectItem>
<SelectItem value={TemplateType.ORGANISATION}>{t`Organisation`}</SelectItem>
</SelectContent>
</Select>
);
});
TemplateTypeSelect.displayName = 'TemplateTypeSelect';
export const TemplateTypeTooltip = ({
organisationTeamCount,
}: {
organisationTeamCount: number;
}) => {
export const TemplateTypeTooltip = ({ organisationTeamCount }: { organisationTeamCount: number }) => {
return (
<Tooltip>
<TooltipTrigger>
@@ -65,8 +51,8 @@ export const TemplateTypeTooltip = ({
{organisationTeamCount >= 2 && (
<p>
<Trans>
<strong>Organisation</strong> templates are shared across all teams in your
organisation but can only be edited by the owning team.
<strong>Organisation</strong> templates are shared across all teams in your organisation but can only be
edited by the owning team.
</Trans>
</p>
)}
+3 -9
View File
@@ -1,12 +1,8 @@
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
import { forwardRef } from 'react';
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
export const SignatureIcon: LucideIcon = forwardRef(
(
{ size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props },
ref,
) => {
({ size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props }, ref) => {
return (
<svg
ref={ref}
@@ -20,9 +16,7 @@ export const SignatureIcon: LucideIcon = forwardRef(
<path
d="M1.5 11H14.5M1.5 14C1.5 14 8.72 2 4.86938 2H4.875C2.01 2 1.97437 14.0694 8 6.51188V6.5C8 6.5 9 11.3631 11.5 7.52375V7.5C11.5 7.5 11.5 9 14.5 9"
stroke={color}
strokeWidth={
absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth
}
strokeWidth={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
/>
+24 -27
View File
@@ -1,31 +1,28 @@
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
import { forwardRef } from 'react';
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
export const VerifiedIcon: LucideIcon = forwardRef(
({ size = 24, color = 'currentColor', ...props }, ref) => {
return (
<svg
ref={ref}
width={size}
height={size}
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g id="badge, verified, award">
<path
id="Icon"
fillRule="evenodd"
clipRule="evenodd"
d="M10.5457 2.89094C11.5779 1.70302 13.4223 1.70302 14.4545 2.89094L15.2585 3.81628C15.3917 3.96967 15.5947 4.04354 15.7954 4.0117L17.0061 3.81965C18.5603 3.57309 19.9732 4.75869 20.0003 6.33214L20.0214 7.55778C20.0249 7.76096 20.1329 7.94799 20.3071 8.05261L21.358 8.6837C22.7071 9.49389 23.0274 11.3103 22.0368 12.5331L21.2651 13.4855C21.1372 13.6434 21.0997 13.8561 21.1659 14.0482L21.5652 15.2072C22.0779 16.695 21.1557 18.2923 19.6109 18.5922L18.4075 18.8258C18.208 18.8646 18.0426 19.0034 17.9698 19.1931L17.5308 20.3376C16.9672 21.8069 15.234 22.4378 13.8578 21.6745L12.7858 21.08C12.6081 20.9814 12.3921 20.9814 12.2144 21.08L11.1424 21.6745C9.76623 22.4378 8.033 21.8069 7.4694 20.3376L7.03038 19.1931C6.9576 19.0034 6.79216 18.8646 6.59268 18.8258L5.38932 18.5922C3.84448 18.2923 2.92224 16.695 3.43495 15.2072L3.83431 14.0482C3.90052 13.8561 3.86302 13.6434 3.7351 13.4855L2.96343 12.5331C1.97279 11.3103 2.29307 9.49389 3.64218 8.6837L4.69306 8.05261C4.86728 7.94799 4.97526 7.76096 4.97875 7.55778L4.99985 6.33214C5.02694 4.75869 6.43987 3.57309 7.99413 3.81965L9.20481 4.0117C9.40551 4.04354 9.60845 3.96967 9.74173 3.81628L10.5457 2.89094ZM15.7072 11.2071C16.0977 10.8166 16.0977 10.1834 15.7072 9.79289C15.3167 9.40237 14.6835 9.40237 14.293 9.79289L11.5001 12.5858L10.7072 11.7929C10.3167 11.4024 9.68351 11.4024 9.29298 11.7929C8.90246 12.1834 8.90246 12.8166 9.29298 13.2071L10.4394 14.3536C11.0252 14.9393 11.975 14.9393 12.5608 14.3536L15.7072 11.2071Z"
fill={color}
/>
</g>
</svg>
);
},
);
export const VerifiedIcon: LucideIcon = forwardRef(({ size = 24, color = 'currentColor', ...props }, ref) => {
return (
<svg
ref={ref}
width={size}
height={size}
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g id="badge, verified, award">
<path
id="Icon"
fillRule="evenodd"
clipRule="evenodd"
d="M10.5457 2.89094C11.5779 1.70302 13.4223 1.70302 14.4545 2.89094L15.2585 3.81628C15.3917 3.96967 15.5947 4.04354 15.7954 4.0117L17.0061 3.81965C18.5603 3.57309 19.9732 4.75869 20.0003 6.33214L20.0214 7.55778C20.0249 7.76096 20.1329 7.94799 20.3071 8.05261L21.358 8.6837C22.7071 9.49389 23.0274 11.3103 22.0368 12.5331L21.2651 13.4855C21.1372 13.6434 21.0997 13.8561 21.1659 14.0482L21.5652 15.2072C22.0779 16.695 21.1557 18.2923 19.6109 18.5922L18.4075 18.8258C18.208 18.8646 18.0426 19.0034 17.9698 19.1931L17.5308 20.3376C16.9672 21.8069 15.234 22.4378 13.8578 21.6745L12.7858 21.08C12.6081 20.9814 12.3921 20.9814 12.2144 21.08L11.1424 21.6745C9.76623 22.4378 8.033 21.8069 7.4694 20.3376L7.03038 19.1931C6.9576 19.0034 6.79216 18.8646 6.59268 18.8258L5.38932 18.5922C3.84448 18.2923 2.92224 16.695 3.43495 15.2072L3.83431 14.0482C3.90052 13.8561 3.86302 13.6434 3.7351 13.4855L2.96343 12.5331C1.97279 11.3103 2.29307 9.49389 3.64218 8.6837L4.69306 8.05261C4.86728 7.94799 4.97526 7.76096 4.97875 7.55778L4.99985 6.33214C5.02694 4.75869 6.43987 3.57309 7.99413 3.81965L9.20481 4.0117C9.40551 4.04354 9.60845 3.96967 9.74173 3.81628L10.5457 2.89094ZM15.7072 11.2071C16.0977 10.8166 16.0977 10.1834 15.7072 9.79289C15.3167 9.40237 14.6835 9.40237 14.293 9.79289L11.5001 12.5858L10.7072 11.7929C10.3167 11.4024 9.68351 11.4024 9.29298 11.7929C8.90246 12.1834 8.90246 12.8166 9.29298 13.2071L10.4394 14.3536C11.0252 14.9393 11.975 14.9393 12.5608 14.3536L15.7072 11.2071Z"
fill={color}
/>
</g>
</svg>
);
});
VerifiedIcon.displayName = 'VerifiedIcon';
@@ -1,17 +1,13 @@
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
import { msg } from '@lingui/core/macro';
import { ErrorCode, type FileRejection } from 'react-dropzone';
import { match } from 'ts-pattern';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
export const buildDropzoneRejectionDescription = (fileRejections: FileRejection[]) => {
const errorCode = fileRejections[0]?.errors[0]?.code;
return match(errorCode)
.with(
ErrorCode.FileTooLarge,
() => msg`File is larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`,
)
.with(ErrorCode.FileTooLarge, () => msg`File is larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`)
.with(ErrorCode.FileInvalidType, () => msg`Only PDF files are allowed`)
.with(ErrorCode.FileTooSmall, () => msg`File is too small`)
.with(ErrorCode.TooManyFiles, () => msg`Only one file can be uploaded at a time`)
-2
View File
@@ -12,8 +12,6 @@
"index.tsx"
],
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"clean": "rimraf node_modules"
},
"devDependencies": {
+4 -5
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { ChevronDown } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -44,15 +43,15 @@ const AccordionContent = React.forwardRef<
<AccordionPrimitive.Content
ref={ref}
className={cn(
'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm transition-all',
'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
className,
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
<div className="pt-0 pb-4">{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
+13 -27
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
import * as React from 'react';
import { cn } from '../lib/utils';
import { buttonVariants } from './button';
@@ -11,9 +10,7 @@ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
const AlertDialogPortal = ({ children, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal {...props}>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
{children}
</div>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">{children}</div>
</AlertDialogPrimitive.Portal>
);
@@ -25,7 +22,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, children: _children, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
'bg-background/80 animate-in fade-in fixed inset-0 z-50 backdrop-blur-sm transition-opacity',
'fade-in fixed inset-0 z-50 animate-in bg-background/80 backdrop-blur-sm transition-opacity',
className,
)}
{...props}
@@ -44,7 +41,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
'bg-background animate-in fade-in-90 slide-in-from-bottom-10 sm:zoom-in-90 sm:slide-in-from-bottom-0 fixed z-50 grid w-full max-w-lg scale-100 gap-4 border p-6 opacity-100 shadow-lg sm:rounded-lg md:w-full',
'fade-in-90 slide-in-from-bottom-10 sm:zoom-in-90 sm:slide-in-from-bottom-0 fixed z-50 grid w-full max-w-lg scale-100 animate-in gap-4 border bg-background p-6 opacity-100 shadow-lg sm:rounded-lg md:w-full',
className,
)}
{...props}
@@ -61,10 +58,7 @@ const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDiv
AlertDialogHeader.displayName = 'AlertDialogHeader';
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
);
AlertDialogFooter.displayName = 'AlertDialogFooter';
@@ -73,11 +67,7 @@ const AlertDialogTitle = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold', className)}
{...props}
/>
<AlertDialogPrimitive.Title ref={ref} className={cn('font-semibold text-lg', className)} {...props} />
));
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
@@ -86,11 +76,7 @@ const AlertDialogDescription = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Description
ref={ref}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
<AlertDialogPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
@@ -119,12 +105,12 @@ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
export {
AlertDialog,
AlertDialogTrigger,
AlertDialogContent,
AlertDialogHeader,
AlertDialogFooter,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
};
+10 -22
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -10,14 +9,11 @@ const alertVariants = cva(
{
variants: {
variant: {
default:
'bg-green-50 text-green-700 [&_.alert-title]:text-green-800 [&>svg]:text-green-400',
neutral:
'bg-gray-50 dark:bg-neutral-900/20 text-muted-foreground [&_.alert-title]:text-foreground',
default: 'bg-green-50 text-green-700 [&_.alert-title]:text-green-800 [&>svg]:text-green-400',
neutral: 'bg-gray-50 dark:bg-neutral-900/20 text-muted-foreground [&_.alert-title]:text-foreground',
secondary: 'bg-blue-50 text-blue-700 [&_.alert-title]:text-blue-800 [&>svg]:text-blue-400',
destructive: 'bg-red-50 text-red-700 [&_.alert-title]:text-red-800 [&>svg]:text-red-400',
warning:
'bg-yellow-50 text-yellow-700 [&_.alert-title]:text-yellow-800 [&>svg]:text-yellow-400',
warning: 'bg-yellow-50 text-yellow-700 [&_.alert-title]:text-yellow-800 [&>svg]:text-yellow-400',
},
padding: {
tighter: 'p-2',
@@ -36,31 +32,23 @@ const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, padding, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn('space-y-2', alertVariants({ variant, padding }), className)}
{...props}
/>
<div ref={ref} role="alert" className={cn('space-y-2', alertVariants({ variant, padding }), className)} {...props} />
));
Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5 ref={ref} className={cn('alert-title text-base font-medium', className)} {...props} />
<h5 ref={ref} className={cn('alert-title font-medium text-base', className)} {...props} />
),
);
AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('text-sm', className)} {...props} />
));
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('text-sm', className)} {...props} />,
);
AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertTitle, AlertDescription };
export { Alert, AlertDescription, AlertTitle };
+6 -18
View File
@@ -47,12 +47,7 @@ function pxToRem(px: number): number {
return px / getBaseFontSize();
}
export function AutoSizedText({
children,
className,
maxHeight,
useRem = false,
}: AutoSizedTextProps) {
export function AutoSizedText({ children, className, maxHeight, useRem = false }: AutoSizedTextProps) {
const childRef = useRef<HTMLDivElement>(null);
const fontSize = useRef<number>(0);
@@ -68,20 +63,15 @@ export function AutoSizedText({
let newFontSize: number;
const targetHeight =
maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const targetHeight = maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const isElementTooBig =
childDimensions.width > parentDimensions.width || childDimensions.height > targetHeight;
const isElementTooBig = childDimensions.width > parentDimensions.width || childDimensions.height > targetHeight;
if (isElementTooBig) {
// Scale down if element is bigger than target
newFontSize = (fontSizeLowerBound.current + fontSize.current) / 2;
fontSizeUpperBound.current = fontSize.current;
} else if (
childDimensions.width < parentDimensions.width ||
childDimensions.height < parentDimensions.height
) {
} else if (childDimensions.width < parentDimensions.width || childDimensions.height < parentDimensions.height) {
// Scale up if element is smaller than target
newFontSize = (fontSizeUpperBound.current + fontSize.current) / 2;
fontSizeLowerBound.current = fontSize.current;
@@ -120,16 +110,14 @@ export function AutoSizedText({
while (iterationCount <= ITERATION_LIMIT) {
const childDimensions = getElementDimensions(childElement);
const targetHeight =
maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const targetHeight = maxHeight && maxHeight < parentDimensions.height ? maxHeight : parentDimensions.height;
const widthDifference = parentDimensions.width - childDimensions.width;
const heightDifference = targetHeight - childDimensions.height;
const childFitsIntoParent = heightDifference >= 0 && widthDifference >= 0;
const isWithinTolerance =
Math.abs(widthDifference) <= MAXIMUM_DIFFERENCE ||
Math.abs(heightDifference) <= MAXIMUM_DIFFERENCE;
Math.abs(widthDifference) <= MAXIMUM_DIFFERENCE || Math.abs(heightDifference) <= MAXIMUM_DIFFERENCE;
if (childFitsIntoParent && isWithinTolerance) {
break;
+9 -21
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as AvatarPrimitive from '@radix-ui/react-avatar';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -21,11 +20,7 @@ const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
<AvatarPrimitive.Image ref={ref} className={cn('aspect-square h-full w-full', className)} {...props} />
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
@@ -36,10 +31,7 @@ const AvatarFallback = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'bg-muted flex h-full w-full items-center justify-center rounded-full',
className,
)}
className={cn('flex h-full w-full items-center justify-center rounded-full bg-muted', className)}
{...props}
/>
));
@@ -69,22 +61,18 @@ const AvatarWithText = ({
textSectionClassName,
}: AvatarWithTextProps) => (
<div className={cn('flex w-full max-w-xs items-center gap-2', className)}>
<Avatar
className={cn('dark:border-border h-10 w-10 border-2 border-solid border-white', avatarClass)}
>
<Avatar className={cn('h-10 w-10 border-2 border-white border-solid dark:border-border', avatarClass)}>
{avatarSrc && <AvatarImage src={avatarSrc} />}
<AvatarFallback className="text-xs text-gray-400">{avatarFallback}</AvatarFallback>
<AvatarFallback className="text-gray-400 text-xs">{avatarFallback}</AvatarFallback>
</Avatar>
<div
className={cn('flex flex-col truncate text-left text-sm font-normal', textSectionClassName)}
>
<span className="text-foreground truncate">{primaryText}</span>
<span className="text-muted-foreground truncate text-xs">{secondaryText}</span>
<div className={cn('flex flex-col truncate text-left font-normal text-sm', textSectionClassName)}>
<span className="truncate text-foreground">{primaryText}</span>
<span className="truncate text-muted-foreground text-xs">{secondaryText}</span>
</div>
{rightSideComponent}
</div>
);
export { Avatar, AvatarImage, AvatarFallback, AvatarWithText };
export { Avatar, AvatarFallback, AvatarImage, AvatarWithText };
+25 -35
View File
@@ -1,49 +1,39 @@
import * as React from 'react';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import type * as React from 'react';
import { cn } from '../lib/utils';
const badgeVariants = cva(
'inline-flex items-center rounded-md text-xs font-medium ring-1 ring-inset w-fit',
{
variants: {
variant: {
neutral:
'bg-gray-50 text-gray-600 ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20',
destructive:
'bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20',
warning:
'bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20',
default:
'bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20',
secondary:
'bg-blue-50 text-blue-700 ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30',
orange:
'bg-orange-50 text-orange-700 ring-orange-700/10 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/30',
},
size: {
small: 'px-1.5 py-0.5 text-xs',
default: 'px-2 py-1.5 text-xs',
large: 'px-3 py-2 text-sm',
},
const badgeVariants = cva('inline-flex items-center rounded-md text-xs font-medium ring-1 ring-inset w-fit', {
variants: {
variant: {
neutral: 'bg-gray-50 text-gray-600 ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20',
destructive: 'bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20',
warning:
'bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20',
default:
'bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20',
secondary:
'bg-blue-50 text-blue-700 ring-blue-700/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/30',
orange:
'bg-orange-50 text-orange-700 ring-orange-700/10 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/30',
},
defaultVariants: {
variant: 'default',
size: 'default',
size: {
small: 'px-1.5 py-0.5 text-xs',
default: 'px-2 py-1.5 text-xs',
large: 'px-3 py-2 text-sm',
},
},
);
defaultVariants: {
variant: 'default',
size: 'default',
},
});
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, size, ...props }: BadgeProps) {
return (
<div role="status" className={cn(badgeVariants({ variant, size }), className)} {...props} />
);
return <div role="status" className={cn(badgeVariants({ variant, size }), className)} {...props} />;
}
export { Badge, badgeVariants };
+3 -11
View File
@@ -1,9 +1,8 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import { Loader } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -61,21 +60,14 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, loading, ...props }, ref) => {
if (asChild) {
return (
<Slot className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
);
return <Slot className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
}
const isLoading = loading === true;
const isDisabled = props.disabled || isLoading;
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
disabled={isDisabled}
>
<button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} disabled={isDisabled}>
{isLoading && <Loader className={cn('mr-2 animate-spin', loaderVariants({ size }))} />}
{props.children}
</button>
+2 -6
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import type * as React from 'react';
import { DayPicker } from 'react-day-picker';
import { cn } from '../lib/utils';
@@ -30,10 +29,7 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
head_cell: 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: 'text-center text-sm p-0 relative [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100',
),
day: cn(buttonVariants({ variant: 'ghost' }), 'h-9 w-9 p-0 font-normal aria-selected:opacity-100'),
day_selected:
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
+11 -22
View File
@@ -16,10 +16,7 @@ export type CardProps = React.HTMLAttributes<HTMLDivElement> & {
};
const Card = React.forwardRef<HTMLDivElement, CardProps>(
(
{ className, children, gradient = false, degrees = 120, backdropBlur = true, ...props },
ref,
) => {
({ className, children, gradient = false, degrees = 120, backdropBlur = true, ...props }, ref) => {
return (
<div
ref={ref}
@@ -30,15 +27,14 @@ const Card = React.forwardRef<HTMLDivElement, CardProps>(
} as React.CSSProperties
}
className={cn(
'bg-background text-foreground group relative rounded-lg border-2',
'group relative rounded-lg border-2 bg-background text-foreground',
{
'backdrop-blur-[2px]': backdropBlur,
'gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/50%)_5%,theme(colors.border/80%)_30%)]':
gradient,
'dark:gradient-border-mask before:pointer-events-none before:absolute before:-inset-[2px] before:rounded-lg before:p-[2px] before:[background:linear-gradient(var(--card-gradient-degrees),theme(colors.primary.DEFAULT/70%)_5%,theme(colors.border/80%)_30%)]':
gradient,
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_var(colors.primary.DEFAULT/70%)]':
true,
'shadow-[0_0_0_4px_theme(colors.gray.100/70%),0_0_0_1px_theme(colors.gray.100/70%),0_0_0_0.5px_var(colors.primary.DEFAULT/70%)]': true,
'dark:shadow-[0]': true,
},
className,
@@ -63,29 +59,22 @@ CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
{...props}
/>
<h3 ref={ref} className={cn('font-semibold text-lg leading-none tracking-tight', className)} {...props} />
),
);
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
),
);
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
),
({ className, ...props }, ref) => <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />,
);
CardContent.displayName = 'CardContent';
@@ -98,4 +87,4 @@ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv
CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };
+3 -4
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -14,13 +13,13 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'border-input bg-background ring-offset-background focus-visible:ring-ring data-[state=checked]:border-primary data-[state=checked]:bg-primary peer h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'peer h-4 w-4 shrink-0 rounded-sm border border-input bg-background ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:border-primary data-[state=checked]:bg-primary',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('text-primary-foreground flex items-center justify-center', checkClassName)}
className={cn('flex items-center justify-center text-primary-foreground', checkClassName)}
>
<Check className="h-3 w-3 stroke-[3px]" />
</CheckboxPrimitive.Indicator>
+1 -1
View File
@@ -6,4 +6,4 @@ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
export { Collapsible, CollapsibleContent, CollapsibleTrigger };
+14 -7
View File
@@ -1,7 +1,7 @@
import type { HTMLAttributes } from 'react';
import React, { useState } from 'react';
import { useEffect, useState } from 'react';
import { HexColorInput, HexColorPicker } from 'react-colorful';
import { HexColorInput, HexColorPicker, setNonce } from 'react-colorful';
import { cn } from '../lib/utils';
import { Popover, PopoverContent, PopoverTrigger } from './popover';
@@ -11,6 +11,7 @@ export type ColorPickerProps = {
value: string;
defaultValue?: string;
onChange: (color: string) => void;
nonce?: string;
} & HTMLAttributes<HTMLDivElement>;
export const ColorPicker = ({
@@ -19,6 +20,7 @@ export const ColorPicker = ({
value,
defaultValue = '#000000',
onChange,
nonce,
...props
}: ColorPickerProps) => {
const [color, setColor] = useState(value || defaultValue);
@@ -39,13 +41,19 @@ export const ColorPicker = ({
onChange(inputColor);
};
useEffect(() => {
if (nonce) {
setNonce(nonce);
}
}, [nonce]);
return (
<Popover>
<PopoverTrigger>
<button
type="button"
disabled={disabled}
className="bg-background h-12 w-12 rounded-md border p-1 disabled:pointer-events-none disabled:opacity-50"
className="h-12 w-12 rounded-md border bg-background p-1 disabled:pointer-events-none disabled:opacity-50"
>
<div className="h-full w-full rounded-sm" style={{ backgroundColor: color }} />
</button>
@@ -53,13 +61,11 @@ export const ColorPicker = ({
<PopoverContent className="w-auto">
<HexColorPicker
className={cn(
className,
'w-full aria-disabled:pointer-events-none aria-disabled:opacity-50',
)}
className={cn(className, 'w-full aria-disabled:pointer-events-none aria-disabled:opacity-50')}
color={color}
onChange={onColorChange}
aria-disabled={disabled}
nonce={nonce}
{...props}
/>
@@ -75,6 +81,7 @@ export const ColorPicker = ({
}
}}
disabled={disabled}
nonce={nonce}
/>
</PopoverContent>
</Popover>
+2 -5
View File
@@ -1,9 +1,8 @@
import * as React from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { Check, ChevronDown } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Button } from './button';
@@ -69,9 +68,7 @@ const Combobox = ({
<CommandGroup className="max-h-[250px] overflow-y-auto">
{options.map((option, index) => (
<CommandItem key={index} onSelect={() => onOptionSelected(option)}>
<Check
className={cn('mr-2 h-4 w-4', option === value ? 'opacity-100' : 'opacity-0')}
/>
<Check className={cn('mr-2 h-4 w-4', option === value ? 'opacity-100' : 'opacity-0')} />
{option}
</CommandItem>
+16 -31
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import type { DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Dialog, DialogContent } from './dialog';
@@ -14,7 +13,7 @@ const Command = React.forwardRef<
<CommandPrimitive
ref={ref}
className={cn(
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
className,
)}
{...props}
@@ -39,16 +38,13 @@ const CommandDialog = ({
return (
<Dialog {...props}>
<DialogContent
className={cn(
'w-11/12 items-center overflow-hidden rounded-lg p-0 shadow-2xl lg:mt-0',
dialogContentClassName,
)}
className={cn('w-11/12 items-center overflow-hidden rounded-lg p-0 shadow-2xl lg:mt-0', dialogContentClassName)}
position={position}
overlayClassName="bg-background/60"
>
<Command
{...commandProps}
className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4"
className="[&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4"
>
{children}
</Command>
@@ -66,7 +62,7 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
className={cn(
'placeholder:text-foreground-muted flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50',
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-foreground-muted disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
@@ -84,10 +80,10 @@ const CommandTextInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
className={cn(
'bg-background border-input ring-offset-background placeholder:text-muted-foreground/40 focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
{
'ring-2 !ring-red-500 transition-all': props['aria-invalid'],
'!ring-red-500 ring-2 transition-all': props['aria-invalid'],
},
)}
{...props}
@@ -113,9 +109,7 @@ CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
));
>((props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />);
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
@@ -126,7 +120,7 @@ const CommandGroup = React.forwardRef<
<CommandPrimitive.Group
ref={ref}
className={cn(
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden border-b p-1 last:border-0 [&_[cmdk-group-heading]]:mt-2 [&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:opacity-50',
'overflow-hidden border-b p-1 text-foreground last:border-0 [&_[cmdk-group-heading]]:mt-2 [&_[cmdk-group-heading]]:px-0 [&_[cmdk-group-heading]]:py-2 [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:opacity-50',
className,
)}
{...props}
@@ -139,11 +133,7 @@ const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn('bg-border -mx-1 h-px', className)}
{...props}
/>
<CommandPrimitive.Separator ref={ref} className={cn('-mx-1 h-px bg-border', className)} {...props} />
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
@@ -155,7 +145,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
'hover:bg-accent hover:text-accent-foreground aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -165,12 +155,7 @@ const CommandItem = React.forwardRef<
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
return <span className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)} {...props} />;
};
CommandShortcut.displayName = 'CommandShortcut';
@@ -178,12 +163,12 @@ CommandShortcut.displayName = 'CommandShortcut';
export {
Command,
CommandDialog,
CommandInput,
CommandTextInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandShortcut,
CommandList,
CommandSeparator,
CommandShortcut,
CommandTextInput,
};
+17 -27
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -26,7 +25,7 @@ const ContextMenuSubTrigger = React.forwardRef<
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className,
)}
@@ -46,7 +45,7 @@ const ContextMenuSubContent = React.forwardRef<
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in slide-in-from-left-1 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'slide-in-from-left-1 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -63,7 +62,7 @@ const ContextMenuContent = React.forwardRef<
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in fade-in-80 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'fade-in-80 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -82,7 +81,7 @@ const ContextMenuItem = React.forwardRef<
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
@@ -99,7 +98,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
@@ -123,7 +122,7 @@ const ContextMenuRadioItem = React.forwardRef<
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -147,7 +146,7 @@ const ContextMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn('text-foreground px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn('px-2 py-1.5 font-semibold text-foreground text-sm', inset && 'pl-8', className)}
{...props}
/>
));
@@ -158,40 +157,31 @@ const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
<ContextMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-border', className)} {...props} />
));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
return <span className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)} {...props} />;
};
ContextMenuShortcut.displayName = 'ContextMenuShortcut';
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuLabel,
ContextMenuPortal,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
ContextMenuTrigger,
};
@@ -23,22 +23,14 @@ export function DataTablePagination<TData>({
}: DataTablePaginationProps<TData>) {
return (
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-4 px-2">
<div className="flex-1 text-sm text-muted-foreground">
<div className="flex-1 text-muted-foreground text-sm">
{match(additionalInformation)
.with('SelectedCount', () => (
<span>
<Plural
value={table.getFilteredRowModel().rows.length}
one={
<Trans>
{table.getFilteredSelectedRowModel().rows.length} of # row selected.
</Trans>
}
other={
<Trans>
{table.getFilteredSelectedRowModel().rows.length} of # rows selected.
</Trans>
}
one={<Trans>{table.getFilteredSelectedRowModel().rows.length} of # row selected.</Trans>}
other={<Trans>{table.getFilteredSelectedRowModel().rows.length} of # rows selected.</Trans>}
/>
</span>
))
@@ -47,11 +39,7 @@ export function DataTablePagination<TData>({
return (
<span data-testid="data-table-count">
<Plural
value={visibleRows}
one={`Showing # result.`}
other={`Showing # results.`}
/>
<Plural value={visibleRows} one={`Showing # result.`} other={`Showing # results.`} />
</span>
);
})
@@ -60,7 +48,7 @@ export function DataTablePagination<TData>({
</div>
<div className="flex items-center gap-x-2">
<p className="whitespace-nowrap text-sm font-medium">
<p className="whitespace-nowrap font-medium text-sm">
<Trans>Rows per page</Trans>
</p>
<Select
@@ -82,7 +70,7 @@ export function DataTablePagination<TData>({
</Select>
</div>
<div className="flex flex-wrap items-center gap-x-6 gap-y-4 lg:gap-x-8">
<div className="flex items-center text-sm font-medium md:justify-center">
<div className="flex items-center font-medium text-sm md:justify-center">
<Trans>
Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount() || 1}
</Trans>
+4 -9
View File
@@ -1,5 +1,3 @@
import React, { useMemo } from 'react';
import { Trans } from '@lingui/react/macro';
import type {
ColumnDef,
@@ -10,6 +8,8 @@ import type {
VisibilityState,
} from '@tanstack/react-table';
import { flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import type React from 'react';
import { useMemo } from 'react';
import { Skeleton } from './skeleton';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './table';
@@ -128,9 +128,7 @@ export function DataTable<TData, TValue>({
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
@@ -180,10 +178,7 @@ export function DataTable<TData, TValue>({
</p>
{hasFilters && onClearFilters !== undefined && (
<button
onClick={() => onClearFilters()}
className="mt-1 text-sm text-foreground"
>
<button onClick={() => onClearFilters()} className="mt-1 text-foreground text-sm">
<Trans>Clear filters</Trans>
</button>
)}
+42 -52
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import { Trans } from '@lingui/react/macro';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -39,7 +38,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in',
'data-[state=closed]:fade-out data-[state=open]:fade-in fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out',
className,
)}
{...props}
@@ -56,41 +55,36 @@ const DialogContent = React.forwardRef<
/* Below prop is to add additional classes to the overlay */
overlayClassName?: string;
}
>(
(
{ className, children, overlayClassName, position = 'start', hideClose = false, ...props },
ref,
) => (
<DialogPortal position={position}>
<DialogOverlay className={cn(overlayClassName)} />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed z-50 grid w-full gap-4 border bg-background p-6 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0',
{
'rounded-b-xl': position === 'start',
'rounded-t-xl': position === 'end',
},
className,
)}
{...props}
>
{children}
{!hideClose && (
<DialogPrimitive.Close
data-testid="btn-dialog-close"
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
),
);
>(({ className, children, overlayClassName, position = 'start', hideClose = false, ...props }, ref) => (
<DialogPortal position={position}>
<DialogOverlay className={cn(overlayClassName)} />
<DialogPrimitive.Content
ref={ref}
className={cn(
'data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0 fixed z-50 grid w-full animate-in gap-4 border bg-background p-6 shadow-lg sm:max-w-lg sm:rounded-lg',
{
'rounded-b-xl': position === 'start',
'rounded-t-xl': position === 'end',
},
className,
)}
{...props}
>
{children}
{!hideClose && (
<DialogPrimitive.Close
data-testid="btn-dialog-close"
className="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
>
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
@@ -118,7 +112,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn('truncate text-lg font-semibold tracking-tight', className)}
className={cn('truncate font-semibold text-lg tracking-tight', className)}
{...props}
/>
));
@@ -129,24 +123,20 @@ const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
<DialogPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogOverlay,
DialogTitle,
DialogDescription,
DialogPortal,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};
+10 -21
View File
@@ -1,3 +1,7 @@
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
@@ -8,11 +12,6 @@ import type { FileRejection } from 'react-dropzone';
import { useDropzone } from 'react-dropzone';
import { Link } from 'react-router';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import {
DocumentDropzoneCardCenterVariants,
DocumentDropzoneCardLeftVariants,
@@ -70,19 +69,12 @@ export const DocumentDropzone = ({
});
const heading = {
document: disabled
? disabledHeading || msg`You have reached your document limit.`
: msg`Add a document`,
document: disabled ? disabledHeading || msg`You have reached your document limit.` : msg`Add a document`,
template: msg`Upload Template Document`,
};
return (
<motion.div
variants={DocumentDropzoneContainerVariants}
initial="initial"
animate="animate"
whileHover="hover"
>
<motion.div variants={DocumentDropzoneContainerVariants} initial="initial" animate="animate" whileHover="hover">
<Card
role="button"
className={cn(
@@ -100,7 +92,7 @@ export const DocumentDropzone = ({
// Disabled State
<div className="flex">
<motion.div
className="group-hover:bg-destructive/2 a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 dark:bg-muted/80"
className="a z-10 flex aspect-[3/4] w-24 origin-top-right -rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 group-hover:bg-destructive/2 dark:bg-muted/80"
variants={DocumentDropzoneDisabledCardLeftVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
@@ -117,7 +109,7 @@ export const DocumentDropzone = ({
/>
</motion.div>
<motion.div
className="group-hover:bg-destructive/2 z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 dark:bg-muted/80"
className="z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-destructive/10 group-hover:bg-destructive/2 dark:bg-muted/80"
variants={DocumentDropzoneDisabledCardRightVariants}
>
<div className="h-2 w-full rounded-[2px] bg-muted-foreground/10 group-hover:bg-destructive/10" />
@@ -140,10 +132,7 @@ export const DocumentDropzone = ({
className="z-20 flex aspect-[3/4] w-24 flex-col items-center justify-center gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-documenso/80 dark:bg-muted/80"
variants={DocumentDropzoneCardCenterVariants}
>
<Plus
strokeWidth="2px"
className="h-12 w-12 text-muted-foreground/20 group-hover:text-documenso"
/>
<Plus strokeWidth="2px" className="h-12 w-12 text-muted-foreground/20 group-hover:text-documenso" />
</motion.div>
<motion.div
className="z-10 flex aspect-[3/4] w-24 origin-top-left rotate-[22deg] flex-col gap-y-1 rounded-lg border border-muted-foreground/20 bg-white/80 px-2 py-4 backdrop-blur-sm group-hover:border-documenso/80 dark:bg-muted/80"
@@ -160,7 +149,7 @@ export const DocumentDropzone = ({
<p className="mt-6 font-medium text-foreground">{_(heading[type])}</p>
<p className="mt-1 text-center text-sm text-muted-foreground/80">
<p className="mt-1 text-center text-muted-foreground/80 text-sm">
{_(disabled ? disabledMessage : msg`Drag & drop PDF, DOCX, or images here.`)}
</p>
@@ -1,33 +1,8 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { FieldType, Prisma, RecipientRole, SendStatus } from '@prisma/client';
import {
CalendarDays,
CheckSquare,
ChevronDown,
Contact,
Disc,
Hash,
Mail,
Type,
User,
} from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useHotkeys } from 'react-hotkeys-hook';
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
import { PDF_VIEWER_PAGE_SELECTOR, getPdfPagesCount } from '@documenso/lib/constants/pdf-viewer';
import {
type TFieldMetaSchema as FieldMeta,
ZFieldMetaSchema,
} from '@documenso/lib/types/field-meta';
import { getPdfPagesCount, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import { type TFieldMetaSchema as FieldMeta, ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { nanoid } from '@documenso/lib/universal/id';
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
@@ -38,6 +13,16 @@ import {
canRecipientFieldsBeModified,
getRecipientsWithMissingFields,
} from '@documenso/lib/utils/recipients';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { FieldType, Prisma, RecipientRole, SendStatus } from '@prisma/client';
import { CalendarDays, CheckSquare, ChevronDown, Contact, Disc, Hash, Mail, Type, User } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useHotkeys } from 'react-hotkeys-hook';
import { FieldToolTip } from '../../components/field/field-tooltip';
import { getRecipientColorStyles } from '../../lib/recipient-colors';
@@ -111,8 +96,7 @@ export const AddFieldsFormPartial = ({
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
const { currentStep, totalSteps, previousStep } = useStep();
const canRenderBackButtonAsRemove =
currentStep === 1 && typeof documentFlow.onBackStep === 'function' && canGoBack;
const canRenderBackButtonAsRemove = currentStep === 1 && typeof documentFlow.onBackStep === 'function' && canGoBack;
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
const [currentField, setCurrentField] = useState<FieldFormType>();
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
@@ -128,8 +112,7 @@ export const AddFieldsFormPartial = ({
pageY: Number(field.positionY),
pageWidth: Number(field.width),
pageHeight: Number(field.height),
signerEmail:
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
signerEmail: recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
recipientId: field.recipientId,
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
})),
@@ -174,12 +157,8 @@ export const AddFieldsFormPartial = ({
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
const [selectedSigner, setSelectedSigner] = useState<TRecipientLite | null>(null);
const [lastActiveField, setLastActiveField] = useState<TAddFieldsFormSchema['fields'][0] | null>(
null,
);
const [fieldClipboard, setFieldClipboard] = useState<TAddFieldsFormSchema['fields'][0] | null>(
null,
);
const [lastActiveField, setLastActiveField] = useState<TAddFieldsFormSchema['fields'][0] | null>(null);
const [fieldClipboard, setFieldClipboard] = useState<TAddFieldsFormSchema['fields'][0] | null>(null);
const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id);
const selectedSignerStyles = getRecipientColorStyles(selectedSignerIndex);
@@ -214,15 +193,12 @@ export const AddFieldsFormPartial = ({
[localFields],
);
const hasErrors =
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
const hasErrors = emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
const fieldsWithError = useMemo(() => {
const fields = localFields.filter((field) => {
const hasError =
((field.type === FieldType.CHECKBOX ||
field.type === FieldType.RADIO ||
field.type === FieldType.DROPDOWN) &&
((field.type === FieldType.CHECKBOX || field.type === FieldType.RADIO || field.type === FieldType.DROPDOWN) &&
field.fieldMeta === undefined) ||
(field.fieldMeta && 'values' in field.fieldMeta && field?.fieldMeta?.values?.length === 0);
@@ -271,12 +247,7 @@ export const AddFieldsFormPartial = ({
const onMouseMove = useCallback(
(event: MouseEvent) => {
setIsFieldWithinBounds(
isWithinPageBounds(
event,
PDF_VIEWER_PAGE_SELECTOR,
fieldBounds.current.width,
fieldBounds.current.height,
),
isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height),
);
setCoords({
@@ -297,12 +268,7 @@ export const AddFieldsFormPartial = ({
if (
!$page ||
!isWithinPageBounds(
event,
PDF_VIEWER_PAGE_SELECTOR,
fieldBounds.current.width,
fieldBounds.current.height,
)
!isWithinPageBounds(event, PDF_VIEWER_PAGE_SELECTOR, fieldBounds.current.width, fieldBounds.current.height)
) {
setSelectedField(null);
return;
@@ -365,12 +331,7 @@ export const AddFieldsFormPartial = ({
return;
}
const {
x: pageX,
y: pageY,
width: pageWidth,
height: pageHeight,
} = getFieldPosition($page, node);
const { x: pageX, y: pageY, width: pageWidth, height: pageHeight } = getFieldPosition($page, node);
update(index, {
...field,
@@ -526,13 +487,11 @@ export const AddFieldsFormPartial = ({
useEffect(() => {
const recipientsByRoleToDisplay = recipients.filter(
(recipient) =>
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
(recipient) => recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
);
setSelectedSigner(
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ??
recipientsByRoleToDisplay[0],
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ?? recipientsByRoleToDisplay[0],
);
}, [recipients]);
@@ -595,10 +554,7 @@ export const AddFieldsFormPartial = ({
{showAdvancedSettings && currentField ? (
<FieldAdvancedSettings
title={msg`Advanced settings`}
description={msg`Configure the ${parseMessageDescriptor(
_,
FRIENDLY_FIELD_TYPE[currentField.type],
)} field`}
description={msg`Configure the ${parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[currentField.type])} field`}
field={currentField}
fields={localFields}
onAdvancedSettings={handleAdvancedSettings}
@@ -614,17 +570,14 @@ export const AddFieldsFormPartial = ({
/>
) : (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
<div className="flex flex-col">
{selectedField && (
<div
className={cn(
'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size]',
'pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white text-muted-foreground ring-2 transition duration-200 [container-type:size] dark:text-muted-background',
selectedSignerStyles?.base,
{
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
@@ -658,8 +611,7 @@ export const AddFieldsFormPartial = ({
recipientIndex={recipientIndex === -1 ? 0 : recipientIndex}
field={field}
disabled={
selectedSigner?.email !== field.signerEmail ||
!canRecipientBeModified(selectedSigner, fields)
selectedSigner?.email !== field.signerEmail || !canRecipientBeModified(selectedSigner, fields)
}
minHeight={MIN_HEIGHT_PX}
minWidth={MIN_WIDTH_PX}
@@ -704,7 +656,7 @@ export const AddFieldsFormPartial = ({
selectedRecipient={selectedSigner}
onSelectedRecipientChange={setSelectedSigner}
recipients={recipients}
className="mb-12 mt-2"
className="mt-2 mb-12"
/>
)}
@@ -726,7 +678,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 font-signature text-lg font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal font-signature text-lg text-muted-foreground group-data-[selected]:text-foreground',
)}
>
<Trans>Signature</Trans>
@@ -750,7 +702,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Contact className="h-4 w-4" />
@@ -775,7 +727,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="flex flex-col items-center justify-center px-6 py-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Mail className="h-4 w-4" />
@@ -800,7 +752,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<User className="h-4 w-4" />
@@ -825,7 +777,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<CalendarDays className="h-4 w-4" />
@@ -850,7 +802,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Type className="h-4 w-4" />
@@ -875,7 +827,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Hash className="h-4 w-4" />
@@ -900,7 +852,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<Disc className="h-4 w-4" />
@@ -925,7 +877,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<CheckSquare className="h-4 w-4" />
@@ -950,7 +902,7 @@ export const AddFieldsFormPartial = ({
<CardContent className="p-4">
<p
className={cn(
'flex items-center justify-center gap-x-1.5 text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
'flex items-center justify-center gap-x-1.5 font-normal text-muted-foreground text-sm group-data-[selected]:text-foreground',
)}
>
<ChevronDown className="h-4 w-4" />
@@ -968,14 +920,10 @@ export const AddFieldsFormPartial = ({
{hasErrors && (
<div className="mt-4">
<ul>
<li className="text-sm text-red-500">
<li className="text-red-500 text-sm">
<Trans>
To proceed further, please set at least one value for the{' '}
{emptyCheckboxFields.length > 0
? 'Checkbox'
: emptyRadioFields.length > 0
? 'Radio'
: 'Select'}{' '}
{emptyCheckboxFields.length > 0 ? 'Checkbox' : emptyRadioFields.length > 0 ? 'Radio' : 'Select'}{' '}
field.
</Trans>
</li>
@@ -987,8 +935,7 @@ export const AddFieldsFormPartial = ({
<Alert variant="warning">
<AlertDescription>
<Trans>
This recipient can no longer be modified as they have signed a field, or completed
the document.
This recipient can no longer be modified as they have signed a field, or completed the document.
</Trans>
</AlertDescription>
</Alert>
@@ -1,8 +1,7 @@
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { FieldType } from '@prisma/client';
import { z } from 'zod';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
export const ZAddFieldsFormSchema = z.object({
fields: z.array(
z.object({
@@ -1,18 +1,3 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import {
DocumentStatus,
DocumentVisibility,
type Field,
SendStatus,
TeamMemberRole,
} from '@prisma/client';
import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { DATE_FORMATS, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
@@ -39,21 +24,16 @@ import {
DocumentVisibilitySelect,
DocumentVisibilityTooltip,
} from '@documenso/ui/components/document/document-visibility-select';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@documenso/ui/primitives/accordion';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@documenso/ui/primitives/accordion';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { DocumentStatus, DocumentVisibility, type Field, SendStatus, TeamMemberRole } from '@prisma/client';
import { InfoIcon } from 'lucide-react';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { DocumentSignatureSettingsTooltip } from '../../components/document/document-signature-settings-tooltip';
import { Combobox } from '../combobox';
@@ -112,11 +92,10 @@ export const AddSettingsFormPartial = ({
meta: {
timezone:
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
DEFAULT_DOCUMENT_TIME_ZONE,
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ?? DEFAULT_DOCUMENT_TIME_ZONE,
dateFormat:
DATE_FORMATS.find((format) => format.value === document.documentMeta?.dateFormat)
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
DATE_FORMATS.find((format) => format.value === document.documentMeta?.dateFormat)?.value ??
DEFAULT_DOCUMENT_DATE_FORMAT,
redirectUrl: document.documentMeta?.redirectUrl ?? '',
language: document.documentMeta?.language ?? 'en',
signatureTypes: extractTeamSignatureSettings(document.documentMeta),
@@ -126,9 +105,7 @@ export const AddSettingsFormPartial = ({
const { stepIndex, currentStep, totalSteps, previousStep } = useStep();
const documentHasBeenSent = recipients.some(
(recipient) => recipient.sendStatus === SendStatus.SENT,
);
const documentHasBeenSent = recipients.some((recipient) => recipient.sendStatus === SendStatus.SENT);
const canUpdateVisibility = match(currentTeamMemberRole)
.with(TeamMemberRole.ADMIN, () => true)
@@ -149,11 +126,7 @@ export const AddSettingsFormPartial = ({
// We almost always want to set the timezone to the user's local timezone to avoid confusion
// when the document is signed.
useEffect(() => {
if (
!form.formState.touchedFields.meta?.timezone &&
!documentHasBeenSent &&
!document.documentMeta?.timezone
) {
if (!form.formState.touchedFields.meta?.timezone && !documentHasBeenSent && !document.documentMeta?.timezone) {
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
}
}, [
@@ -188,10 +161,7 @@ export const AddSettingsFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
{isDocumentPdfLoaded && (
@@ -203,10 +173,7 @@ export const AddSettingsFormPartial = ({
)}
<Form {...form}>
<fieldset
className="flex h-full flex-col space-y-6"
disabled={form.formState.isSubmitting}
>
<fieldset className="flex h-full flex-col space-y-6" disabled={form.formState.isSubmitting}>
<FormField
control={form.control}
name="title"
@@ -244,9 +211,8 @@ export const AddSettingsFormPartial = ({
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
<Trans>
Controls the language for the document, including the language to be used
for email notifications, and the final certificate that is generated and
attached to the document.
Controls the language for the document, including the language to be used for email
notifications, and the final certificate that is generated and attached to the document.
</Trans>
</TooltipContent>
</Tooltip>
@@ -365,7 +331,7 @@ export const AddSettingsFormPartial = ({
<Trans>Advanced Options</Trans>
</AccordionTrigger>
<AccordionContent className="-mx-1 px-1 pt-2 text-sm leading-relaxed text-muted-foreground">
<AccordionContent className="-mx-1 px-1 pt-2 text-muted-foreground text-sm leading-relaxed">
<div className="flex flex-col space-y-6">
<FormField
control={form.control}
@@ -381,8 +347,8 @@ export const AddSettingsFormPartial = ({
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Add an external ID to the document. This can be used to identify
the document in external systems.
Add an external ID to the document. This can be used to identify the document in
external systems.
</Trans>
</TooltipContent>
</Tooltip>
@@ -507,9 +473,7 @@ export const AddSettingsFormPartial = ({
</TooltipTrigger>
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
Add a URL to redirect the user to once the document is signed
</Trans>
<Trans>Add a URL to redirect the user to once the document is signed</Trans>
</TooltipContent>
</Tooltip>
</FormLabel>
@@ -1,26 +1,16 @@
import { msg } from '@lingui/core/macro';
import { DocumentVisibility } from '@prisma/client';
import { z } from 'zod';
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
import { DEFAULT_DOCUMENT_TIME_ZONE } from '@documenso/lib/constants/time-zones';
import {
ZDocumentAccessAuthTypesSchema,
ZDocumentActionAuthTypesSchema,
} from '@documenso/lib/types/document-auth';
import {
ZDocumentMetaDateFormatSchema,
ZDocumentMetaTimezoneSchema,
} from '@documenso/lib/types/document-meta';
import { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
import { msg } from '@lingui/core/macro';
import { DocumentVisibility } from '@prisma/client';
import { z } from 'zod';
export const ZAddSettingsFormSchema = z.object({
title: z
.string()
.trim()
.min(1, { message: msg`Title cannot be empty`.id }),
title: z.string().trim().min(1, { message: msg`Title cannot be empty`.id }),
externalId: z.string().optional(),
visibility: z.nativeEnum(DocumentVisibility).optional(),
globalAccessAuth: z
@@ -36,8 +26,7 @@ export const ZAddSettingsFormSchema = z.object({
.string()
.optional()
.refine((value) => value === undefined || value === '' || isValidRedirectUrl(value), {
message:
'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
message: 'Please enter a valid URL, make sure you include http:// or https:// part of the url.',
}),
language: z
.union([z.string(), z.enum(SUPPORTED_LANGUAGE_CODES)])
@@ -1,18 +1,3 @@
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@prisma/client';
import { motion } from 'framer-motion';
import { GripVerticalIcon, HelpCircle, Plus, Trash } from 'lucide-react';
import { useFieldArray, useForm } from 'react-hook-form';
import { prop, sortBy } from 'remeda';
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
@@ -27,11 +12,21 @@ import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animat
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
import { cn } from '@documenso/ui/lib/utils';
import type { DropResult, SensorAPI } from '@hello-pangea/dnd';
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@prisma/client';
import { motion } from 'framer-motion';
import { GripVerticalIcon, HelpCircle, Plus, Trash } from 'lucide-react';
import { useCallback, useId, useMemo, useRef, useState } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { prop, sortBy } from 'remeda';
import {
DocumentReadOnlyFields,
mapFieldsWithRecipients,
} from '../../components/document/document-read-only-fields';
import { DocumentReadOnlyFields, mapFieldsWithRecipients } from '../../components/document/document-read-only-fields';
import type { RecipientAutoCompleteOption } from '../../components/recipient/recipient-autocomplete-input';
import { RecipientAutoCompleteInput } from '../../components/recipient/recipient-autocomplete-input';
import { Button } from '../button';
@@ -130,8 +125,7 @@ export const AddSignersFormPartial = ({
email: recipient.email,
role: recipient.role,
signingOrder: recipient.signingOrder ?? index + 1,
actionAuth:
ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
actionAuth: ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
})),
[prop('signingOrder'), 'asc'],
[prop('nativeId'), 'asc'],
@@ -147,14 +141,10 @@ export const AddSignersFormPartial = ({
const recipientHasAuthOptions = recipients.find((recipient) => {
const recipientAuthOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);
return (
recipientAuthOptions.accessAuth.length > 0 || recipientAuthOptions.actionAuth.length > 0
);
return recipientAuthOptions.accessAuth.length > 0 || recipientAuthOptions.actionAuth.length > 0;
});
const formHasActionAuth = form
.getValues('signers')
.find((signer) => signer.actionAuth.length > 0);
const formHasActionAuth = form.getValues('signers').find((signer) => signer.actionAuth.length > 0);
return recipientHasAuthOptions !== undefined || formHasActionAuth !== undefined;
}, [recipients, form]);
@@ -193,10 +183,7 @@ export const AddSignersFormPartial = ({
name: 'signers',
});
const emptySigners = useCallback(
() => form.getValues('signers').filter((signer) => signer.email === ''),
[form],
);
const emptySigners = useCallback(() => form.getValues('signers').filter((signer) => signer.email === ''), [form]);
const { scheduleSave } = useAutoSave(onAutoSave);
@@ -220,9 +207,7 @@ export const AddSignersFormPartial = ({
const updatedSigners = currentSigners.map((signer) => {
// Find the matching recipient from the response using nativeId
const matchingRecipient = response.recipients.find(
(recipient) => recipient.id === signer.nativeId,
);
const matchingRecipient = response.recipients.find((recipient) => recipient.id === signer.nativeId);
if (matchingRecipient) {
// Update the signer with the server-returned data, especially the ID
@@ -233,9 +218,7 @@ export const AddSignersFormPartial = ({
} else if (!signer.nativeId) {
// For new signers without nativeId, match by email and update with server ID
// Bug: Multiple recipients with the same email will be matched incorrectly here.
const newRecipient = response.recipients.find(
(recipient) => recipient.email === signer.email,
);
const newRecipient = response.recipients.find((recipient) => recipient.email === signer.email);
if (newRecipient) {
return {
...signer,
@@ -258,9 +241,7 @@ export const AddSignersFormPartial = ({
(signer) => signer.email.toLowerCase() === user?.email?.toLowerCase(),
);
const hasDocumentBeenSent = recipients.some(
(recipient) => recipient.sendStatus === SendStatus.SENT,
);
const hasDocumentBeenSent = recipients.some((recipient) => recipient.sendStatus === SendStatus.SENT);
const canRecipientBeModified = (recipientId?: number) => {
if (recipientId === undefined) {
@@ -335,8 +316,7 @@ export const AddSignersFormPartial = ({
email: user?.email ?? '',
role: RecipientRole.SIGNER,
actionAuth: [],
signingOrder:
signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
},
{
shouldFocus: true,
@@ -347,10 +327,7 @@ export const AddSignersFormPartial = ({
}
};
const handleRecipientAutoCompleteSelect = (
index: number,
suggestion: RecipientAutoCompleteOption,
) => {
const handleRecipientAutoCompleteSelect = (index: number, suggestion: RecipientAutoCompleteOption) => {
setValue(`signers.${index}.email`, suggestion.email, {
shouldValidate: true,
shouldDirty: true,
@@ -363,7 +340,9 @@ export const AddSignersFormPartial = ({
const onDragEnd = useCallback(
async (result: DropResult) => {
if (!result.destination) return;
if (!result.destination) {
return;
}
const items = Array.from(watchedSigners);
const [reorderedSigner] = items.splice(result.source.index, 1);
@@ -514,10 +493,7 @@ export const AddSignersFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
{isDocumentPdfLoaded && (
<DocumentReadOnlyFields
@@ -545,9 +521,7 @@ export const AddSignersFormPartial = ({
return;
}
field.onChange(
checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL,
);
field.onChange(checked ? DocumentSigningOrder.SEQUENTIAL : DocumentSigningOrder.PARALLEL);
// If sequential signing is turned off, disable dictate next signer
if (!checked) {
@@ -623,8 +597,8 @@ export const AddSignersFormPartial = ({
<TooltipContent className="max-w-80 p-4">
<p>
<Trans>
When enabled, signers can choose who should sign next in the sequence
instead of following the predefined order.
When enabled, signers can choose who should sign next in the sequence instead of following
the predefined order.
</Trans>
</p>
</TooltipContent>
@@ -644,11 +618,7 @@ export const AddSignersFormPartial = ({
>
<Droppable droppableId="signers">
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className="flex w-full flex-col gap-y-2"
>
<div {...provided.droppableProps} ref={provided.innerRef} className="flex w-full flex-col gap-y-2">
{signers.map((signer, index) => (
<Draggable
key={`${signer.id}-${signer.signingOrder}`}
@@ -667,8 +637,7 @@ export const AddSignersFormPartial = ({
{...provided.draggableProps}
{...provided.dragHandleProps}
className={cn('py-1', {
'pointer-events-none rounded-md bg-widget-foreground pt-2':
snapshot.isDragging,
'pointer-events-none rounded-md bg-widget-foreground pt-2': snapshot.isDragging,
})}
>
<motion.fieldset
@@ -685,14 +654,11 @@ export const AddSignersFormPartial = ({
name={`signers.${index}.signingOrder`}
render={({ field }) => (
<FormItem
className={cn(
'col-span-2 mt-auto flex items-center gap-x-1 space-y-0',
{
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.signingOrder,
},
)}
className={cn('col-span-2 mt-auto flex items-center gap-x-1 space-y-0', {
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.signingOrder,
})}
>
<GripVerticalIcon className="h-5 w-5 flex-shrink-0 opacity-40" />
<FormControl>
@@ -758,9 +724,7 @@ export const AddSignersFormPartial = ({
!canRecipientBeModified(signer.nativeId)
}
options={recipientSuggestions}
onSelect={(suggestion) =>
handleRecipientAutoCompleteSelect(index, suggestion)
}
onSelect={(suggestion) => handleRecipientAutoCompleteSelect(index, suggestion)}
onSearchQueryChange={(query) => {
field.onChange(query);
setRecipientSearchQuery(query);
@@ -807,9 +771,7 @@ export const AddSignersFormPartial = ({
!canRecipientBeModified(signer.nativeId)
}
options={recipientSuggestions}
onSelect={(suggestion) =>
handleRecipientAutoCompleteSelect(index, suggestion)
}
onSelect={(suggestion) => handleRecipientAutoCompleteSelect(index, suggestion)}
onSearchQueryChange={(query) => {
field.onChange(query);
setRecipientSearchQuery(query);
@@ -825,37 +787,36 @@ export const AddSignersFormPartial = ({
)}
/>
{showAdvancedSettings &&
organisation.organisationClaim.flags.cfr21 && (
<FormField
control={form.control}
name={`signers.${index}.actionAuth`}
render={({ field }) => (
<FormItem
className={cn('col-span-8', {
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.actionAuth,
'col-span-10': isSigningOrderSequential,
})}
>
<FormControl>
<RecipientActionAuthSelect
{...field}
onValueChange={field.onChange}
disabled={
snapshot.isDragging ||
isSubmitting ||
!canRecipientBeModified(signer.nativeId)
}
/>
</FormControl>
{showAdvancedSettings && organisation.organisationClaim.flags.cfr21 && (
<FormField
control={form.control}
name={`signers.${index}.actionAuth`}
render={({ field }) => (
<FormItem
className={cn('col-span-8', {
'mb-6':
form.formState.errors.signers?.[index] &&
!form.formState.errors.signers[index]?.actionAuth,
'col-span-10': isSigningOrderSequential,
})}
>
<FormControl>
<RecipientActionAuthSelect
{...field}
onValueChange={field.onChange}
disabled={
snapshot.isDragging ||
isSubmitting ||
!canRecipientBeModified(signer.nativeId)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
<FormMessage />
</FormItem>
)}
/>
)}
<div className="col-span-2 flex gap-x-2">
<FormField
@@ -940,7 +901,7 @@ export const AddSignersFormPartial = ({
disabled={isSubmitting || signers.length >= remaining.recipients}
onClick={() => onAddSigner()}
>
<Plus className="-ml-1 mr-2 h-5 w-5" />
<Plus className="mr-2 -ml-1 h-5 w-5" />
<Trans>Add Signer</Trans>
</Button>
@@ -951,7 +912,7 @@ export const AddSignersFormPartial = ({
disabled={isSubmitting || isUserAlreadyARecipient}
onClick={() => onAddSelfSigner()}
>
<Plus className="-ml-1 mr-2 h-5 w-5" />
<Plus className="mr-2 -ml-1 h-5 w-5" />
<Trans>Add myself</Trans>
</Button>
</div>
@@ -965,10 +926,7 @@ export const AddSignersFormPartial = ({
onCheckedChange={(value) => setShowAdvancedSettings(Boolean(value))}
/>
<label
className="ml-2 text-sm text-muted-foreground"
htmlFor="showAdvancedRecipientSettings"
>
<label className="ml-2 text-muted-foreground text-sm" htmlFor="showAdvancedRecipientSettings">
<Trans>Show advanced settings</Trans>
</label>
</div>
@@ -1,10 +1,9 @@
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { zEmail } from '@documenso/lib/utils/zod';
import { msg } from '@lingui/core/macro';
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
import { z } from 'zod';
import { ZRecipientActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
import { zEmail } from '@documenso/lib/utils/zod';
export const ZAddSignersFormSchema = z.object({
signers: z.array(
z.object({
@@ -1,15 +1,3 @@
import { useEffect } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentDistributionMethod, DocumentStatus, RecipientRole } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { InfoIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
@@ -19,29 +7,23 @@ import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { formatSigningLink } from '@documenso/lib/utils/recipients';
import { trpc } from '@documenso/trpc/react';
import { DocumentSendEmailMessageHelper } from '@documenso/ui/components/document/document-send-email-message-helper';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@documenso/ui/primitives/form/form';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { Field } from '@prisma/client';
import { DocumentDistributionMethod, DocumentStatus, RecipientRole } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { InfoIcon } from 'lucide-react';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { CopyTextButton } from '../../components/common/copy-text-button';
import { DocumentEmailCheckboxes } from '../../components/document/document-email-checkboxes';
import {
DocumentReadOnlyFields,
mapFieldsWithRecipients,
} from '../../components/document/document-read-only-fields';
import { DocumentReadOnlyFields, mapFieldsWithRecipients } from '../../components/document/document-read-only-fields';
import { AvatarWithText } from '../avatar';
import { Input } from '../input';
import { useStep } from '../stepper';
@@ -70,8 +52,8 @@ export type AddSubjectFormProps = {
export const AddSubjectFormPartial = ({
documentFlow,
recipients: recipients,
fields: fields,
recipients,
fields,
document,
onSubmit,
onAutoSave,
@@ -89,8 +71,7 @@ export const AddSubjectFormPartial = ({
// emailReplyName: document.documentMeta?.emailReplyName || undefined,
subject: document.documentMeta?.subject ?? '',
message: document.documentMeta?.message ?? '',
distributionMethod:
document.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
distributionMethod: document.documentMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
emailSettings: ZDocumentEmailSettingsSchema.parse(document?.documentMeta?.emailSettings),
},
},
@@ -106,11 +87,10 @@ export const AddSubjectFormPartial = ({
formState: { isSubmitting },
} = form;
const { data: emailData, isLoading: isLoadingEmails } =
trpc.enterprise.organisation.email.find.useQuery({
organisationId: organisation.id,
perPage: 100,
});
const { data: emailData, isLoading: isLoadingEmails } = trpc.enterprise.organisation.email.find.useQuery({
organisationId: organisation.id,
perPage: 100,
});
const emails = emailData?.data || [];
@@ -168,10 +148,7 @@ export const AddSubjectFormPartial = ({
return (
<>
<DocumentFlowFormContainerHeader
title={documentFlow.title}
description={documentFlow.description}
/>
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
<DocumentFlowFormContainerContent>
<div className="flex flex-col">
{isDocumentPdfLoaded && (
@@ -226,9 +203,7 @@ export const AddSubjectFormPartial = ({
<Select
{...field}
value={field.value === null ? '-1' : field.value}
onValueChange={(value) =>
field.onChange(value === '-1' ? null : value)
}
onValueChange={(value) => field.onChange(value === '-1' ? null : value)}
>
<SelectTrigger loading={isLoadingEmails} className="bg-background">
<SelectValue />
@@ -259,8 +234,7 @@ export const AddSubjectFormPartial = ({
<FormItem>
<FormLabel>
<Trans>
Reply To Email{' '}
<span className="text-muted-foreground">(Optional)</span>
Reply To Email <span className="text-muted-foreground">(Optional)</span>
</Trans>
</FormLabel>
@@ -331,11 +305,7 @@ export const AddSubjectFormPartial = ({
</FormLabel>
<FormControl>
<Textarea
className="mt-2 h-16 resize-none bg-background"
{...field}
maxLength={5000}
/>
<Textarea className="mt-2 h-16 resize-none bg-background" {...field} maxLength={5000} />
</FormControl>
<FormMessage />
</FormItem>
@@ -361,15 +331,15 @@ export const AddSubjectFormPartial = ({
className="rounded-lg border"
>
{document.status === DocumentStatus.DRAFT ? (
<div className="py-16 text-center text-sm text-muted-foreground">
<div className="py-16 text-center text-muted-foreground text-sm">
<p>
<Trans>We won't send anything to notify recipients.</Trans>
</p>
<p className="mt-2">
<Trans>
We will generate signing links for you, which you can send to the recipients
through your method of choice.
We will generate signing links for you, which you can send to the recipients through your method
of choice.
</Trans>
</p>
</div>
@@ -382,17 +352,12 @@ export const AddSubjectFormPartial = ({
)}
{recipients.map((recipient) => (
<li
key={recipient.id}
className="flex items-center justify-between px-4 py-3 text-sm"
>
<li key={recipient.id} className="flex items-center justify-between px-4 py-3 text-sm">
<AvatarWithText
avatarFallback={recipient.email.slice(0, 1).toUpperCase()}
primaryText={
<p className="text-sm text-muted-foreground">{recipient.email}</p>
}
primaryText={<p className="text-muted-foreground text-sm">{recipient.email}</p>}
secondaryText={
<p className="text-xs text-muted-foreground/70">
<p className="text-muted-foreground/70 text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
</p>
}
@@ -404,9 +369,7 @@ export const AddSubjectFormPartial = ({
onCopySuccess={() => {
toast({
title: _(msg`Copied to clipboard`),
description: _(
msg`The signing link has been copied to your clipboard.`,
),
description: _(msg`The signing link has been copied to your clipboard.`),
});
}}
badgeContentUncopied={
@@ -1,8 +1,7 @@
import { DocumentDistributionMethod } from '@prisma/client';
import { z } from 'zod';
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
import { zEmail } from '@documenso/lib/utils/zod';
import { DocumentDistributionMethod } from '@prisma/client';
import { z } from 'zod';
export const ZAddSubjectFormSchema = z.object({
meta: z.object({
@@ -11,10 +10,7 @@ export const ZAddSubjectFormSchema = z.object({
// emailReplyName: z.string().optional(),
subject: z.string(),
message: z.string(),
distributionMethod: z
.nativeEnum(DocumentDistributionMethod)
.optional()
.default(DocumentDistributionMethod.EMAIL),
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional().default(DocumentDistributionMethod.EMAIL),
emailSettings: ZDocumentEmailSettingsSchema,
}),
});
@@ -1,11 +1,10 @@
import type { HTMLAttributes } from 'react';
import React from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import type React from 'react';
import type { HTMLAttributes } from 'react';
import { cn } from '../../lib/utils';
import { Button } from '../button';
@@ -39,19 +38,16 @@ export type DocumentFlowFormContainerHeaderProps = {
description: MessageDescriptor;
};
export const DocumentFlowFormContainerHeader = ({
title,
description,
}: DocumentFlowFormContainerHeaderProps) => {
export const DocumentFlowFormContainerHeader = ({ title, description }: DocumentFlowFormContainerHeaderProps) => {
const { _ } = useLingui();
return (
<>
<h3 className="text-2xl font-semibold text-foreground">{_(title)}</h3>
<h3 className="font-semibold text-2xl text-foreground">{_(title)}</h3>
<p className="mt-2 text-sm text-muted-foreground">{_(description)}</p>
<p className="mt-2 text-muted-foreground text-sm">{_(description)}</p>
<hr className="mb-8 mt-4 border-border" />
<hr className="mt-4 mb-8 border-border" />
</>
);
};
@@ -93,13 +89,10 @@ export type DocumentFlowFormContainerStepProps = {
maxStep: number;
};
export const DocumentFlowFormContainerStep = ({
step,
maxStep,
}: DocumentFlowFormContainerStepProps) => {
export const DocumentFlowFormContainerStep = ({ step, maxStep }: DocumentFlowFormContainerStepProps) => {
return (
<div>
<p className="text-sm text-muted-foreground">
<p className="text-muted-foreground text-sm">
<Trans>
Step <span>{`${step} of ${maxStep}`}</span>
</Trans>
@@ -1,16 +1,12 @@
import { convertToLocalSystemFormat, DEFAULT_DOCUMENT_DATE_FORMAT } from '@documenso/lib/constants/date-formats';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { DocumentMeta, Signature } from '@prisma/client';
import { FieldType } from '@prisma/client';
import { ChevronDown } from 'lucide-react';
import {
DEFAULT_DOCUMENT_DATE_FORMAT,
convertToLocalSystemFormat,
} from '@documenso/lib/constants/date-formats';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { fromCheckboxValue } from '@documenso/lib/universal/field-checkbox';
import { cn } from '../../lib/utils';
import { Checkbox } from '../checkbox';
import { Label } from '../label';
@@ -62,7 +58,7 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
>
<div className="flex items-center">
<Checkbox className="h-3 w-3" disabled />
<Label className="ml-1.5 text-xs font-normal text-foreground opacity-50">
<Label className="ml-1.5 font-normal text-foreground text-xs opacity-50">
<Trans>Checkbox option</Trans>
</Label>
</div>
@@ -88,10 +84,7 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
/>
{item.value && (
<Label
htmlFor={`checkbox-${index}`}
className="ml-1.5 text-xs font-normal text-foreground"
>
<Label htmlFor={`checkbox-${index}`} className="ml-1.5 font-normal text-foreground text-xs">
{item.value}
</Label>
)}
@@ -120,10 +113,7 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
checked={item.value === field.customText}
/>
{item.value && (
<Label
htmlFor={`option-${index}`}
className="ml-1.5 text-xs font-normal text-foreground"
>
<Label htmlFor={`option-${index}`} className="ml-1.5 font-normal text-foreground text-xs">
{item.value}
</Label>
)}
@@ -134,13 +124,9 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
);
}
if (
field.type === FieldType.DROPDOWN &&
field.fieldMeta?.type === 'dropdown' &&
!field.inserted
) {
if (field.type === FieldType.DROPDOWN && field.fieldMeta?.type === 'dropdown' && !field.inserted) {
return (
<div className="flex flex-row items-center py-0.5 text-[clamp(0.07rem,25cqw,0.825rem)] text-sm text-field-card-foreground">
<div className="flex flex-row items-center py-0.5 text-[clamp(0.07rem,25cqw,0.825rem)] text-field-card-foreground text-sm">
<p>
<Trans>Select</Trans>
</p>
@@ -149,25 +135,16 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
);
}
if (
field.type === FieldType.SIGNATURE &&
field.signature?.signatureImageAsBase64 &&
field.inserted
) {
if (field.type === FieldType.SIGNATURE && field.signature?.signatureImageAsBase64 && field.inserted) {
return (
<img
src={field.signature.signatureImageAsBase64}
alt="Signature"
className="h-full w-full object-contain"
/>
<img src={field.signature.signatureImageAsBase64} alt="Signature" className="h-full w-full object-contain" />
);
}
const labelToDisplay = fieldMeta?.label || _(FRIENDLY_FIELD_TYPE[type]) || '';
let textToDisplay: string | undefined;
const isSignatureField =
field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
const isSignatureField = field.type === FieldType.SIGNATURE || field.type === FieldType.FREE_SIGNATURE;
if (field.type === FieldType.TEXT && field.fieldMeta?.type === 'text' && field.fieldMeta?.text) {
textToDisplay = field.fieldMeta.text;
@@ -1,16 +1,10 @@
import { forwardRef, useEffect, useState } from 'react';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { FieldType } from '@prisma/client';
import { match } from 'ts-pattern';
import { useAutoSave } from '@documenso/lib/client-only/hooks/use-autosave';
import {
type TBaseFieldMeta as BaseFieldMeta,
type TCheckboxFieldMeta as CheckboxFieldMeta,
type TDateFieldMeta as DateFieldMeta,
DEFAULT_DATE_OVERFLOW_MODE,
DEFAULT_EMAIL_OVERFLOW_MODE,
type TDropdownFieldMeta as DropdownFieldMeta,
type TEmailFieldMeta as EmailFieldMeta,
type TFieldMetaSchema as FieldMeta,
@@ -22,6 +16,12 @@ import {
ZFieldMetaSchema,
} from '@documenso/lib/types/field-meta';
import { useToast } from '@documenso/ui/primitives/use-toast';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { FieldType } from '@prisma/client';
import { forwardRef, useEffect, useState } from 'react';
import { match } from 'ts-pattern';
import type { FieldFormType } from './add-fields';
import {
@@ -83,12 +83,14 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
type: 'email',
fontSize: 14,
textAlign: 'left',
overflow: DEFAULT_EMAIL_OVERFLOW_MODE,
};
case FieldType.DATE:
return {
type: 'date',
fontSize: 14,
textAlign: 'left',
overflow: DEFAULT_DATE_OVERFLOW_MODE,
};
case FieldType.TEXT:
return {
@@ -148,19 +150,7 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
};
export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSettingsProps>(
(
{
title,
description,
field,
fields,
onAdvancedSettings,
isDocumentPdfLoaded = true,
onSave,
onAutoSave,
},
ref,
) => {
({ title, description, field, fields, onAdvancedSettings, isDocumentPdfLoaded = true, onSave, onAutoSave }, ref) => {
const { _ } = useLingui();
const { toast } = useToast();
@@ -186,7 +176,6 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
...parsedFieldMeta,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fieldMeta]);
const { scheduleSave } = useAutoSave(onAutoSave || (async () => {}));
@@ -209,17 +198,10 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
const handleFieldChange = (
key: FieldMetaKeys,
value:
| string
| { checked: boolean; value: string }[]
| { value: string }[]
| boolean
| number,
value: string | { checked: boolean; value: string }[] | { value: string }[] | boolean | number,
) => {
setFieldState((prevState: FieldMeta) => {
if (
['characterLimit', 'minValue', 'maxValue', 'validationLength', 'fontSize'].includes(key)
) {
if (['characterLimit', 'minValue', 'maxValue', 'validationLength', 'fontSize'].includes(key)) {
const parsedValue = Number(value);
return {
@@ -268,9 +250,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
key={index}
field={localField}
disabled={true}
fieldClassName={
localField.formId === field.formId ? 'ring-red-400' : 'ring-neutral-200'
}
fieldClassName={localField.formId === field.formId ? 'ring-red-400' : 'ring-neutral-200'}
/>
</span>
))}
@@ -346,7 +326,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
<div className="mt-4">
<ul>
{errors.map((error, index) => (
<li className="text-sm text-red-500" key={index}>
<li className="text-red-500 text-sm" key={index}>
{error}
</li>
))}
@@ -355,10 +335,7 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
)}
</DocumentFlowFormContainerContent>
<DocumentFlowFormContainerFooter
className="mt-auto"
data-testid="field-advanced-settings-footer"
>
<DocumentFlowFormContainerFooter className="mt-auto" data-testid="field-advanced-settings-footer">
<DocumentFlowFormContainerActions
goNextLabel={msg`Save`}
goBackLabel={msg`Cancel`}
@@ -1,19 +1,17 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { FieldType } from '@prisma/client';
import { CopyPlus, Settings2, SquareStack, Trash } from 'lucide-react';
import { createPortal } from 'react-dom';
import { Rnd } from 'react-rnd';
import { useSearchParams } from 'react-router';
import { useElementBounds } from '@documenso/lib/client-only/hooks/use-element-bounds';
import { useIsPageInDom } from '@documenso/lib/client-only/hooks/use-is-page-in-dom';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { ZCheckboxFieldMeta, ZRadioFieldMeta } from '@documenso/lib/types/field-meta';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { FieldType } from '@prisma/client';
import { CopyPlus, Settings2, SquareStack, Trash } from 'lucide-react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Rnd } from 'react-rnd';
import { useSearchParams } from 'react-router';
import { getRecipientColorStyles } from '../../lib/recipient-colors';
import { cn } from '../../lib/utils';
@@ -96,9 +94,7 @@ const FieldItemInner = ({
const [settingsActive, setSettingsActive] = useState(false);
const $el = useRef<HTMLDivElement>(null);
const $pageBounds = useElementBounds(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
);
const $pageBounds = useElementBounds(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`);
const signerStyles = getRecipientColorStyles(recipientIndex);
@@ -275,20 +271,16 @@ const FieldItemInner = ({
onMove?.(d.node);
}}
>
{(field.type === FieldType.RADIO || field.type === FieldType.CHECKBOX) &&
field.fieldMeta?.label && (
<div
className={cn(
'absolute -top-16 left-0 right-0 rounded-md p-2 text-center text-xs text-gray-700',
{
'border border-primary bg-foreground/5': !fieldHasCheckedValues,
'border border-primary bg-documenso-200': fieldHasCheckedValues,
},
)}
>
{field.fieldMeta.label}
</div>
)}
{(field.type === FieldType.RADIO || field.type === FieldType.CHECKBOX) && field.fieldMeta?.label && (
<div
className={cn('absolute -top-16 right-0 left-0 rounded-md p-2 text-center text-gray-700 text-xs', {
'border border-primary bg-foreground/5': !fieldHasCheckedValues,
'border border-primary bg-documenso-200': fieldHasCheckedValues,
})}
>
{field.fieldMeta.label}
</div>
)}
<div
className={cn(
@@ -317,18 +309,17 @@ const FieldItemInner = ({
<FieldContent field={field} />
{/* On hover, display recipient initials on side of field. */}
<div className="absolute -right-5 top-0 z-20 hidden h-full w-5 items-center justify-center group-hover:flex">
<div className="absolute top-0 -right-5 z-20 hidden h-full w-5 items-center justify-center group-hover:flex">
<div
className={cn(
'flex h-5 w-5 flex-col items-center justify-center rounded-r-md text-[0.5rem] font-bold text-white opacity-0 transition duration-200 group-hover/field-item:opacity-100',
'flex h-5 w-5 flex-col items-center justify-center rounded-r-md font-bold text-[0.5rem] text-white opacity-0 transition duration-200 group-hover/field-item:opacity-100',
signerStyles.fieldItemInitials,
{
'!opacity-50': disabled || passive,
},
)}
>
{(field.signerEmail?.charAt(0)?.toUpperCase() ?? '') +
(field.signerEmail?.charAt(1)?.toUpperCase() ?? '')}
{(field.signerEmail?.charAt(0)?.toUpperCase() ?? '') + (field.signerEmail?.charAt(1)?.toUpperCase() ?? '')}
</div>
</div>
@@ -1,24 +1,16 @@
import { useEffect, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
import { type TCheckboxFieldMeta as CheckboxFieldMeta } from '@documenso/lib/types/field-meta';
import type { TCheckboxFieldMeta as CheckboxFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { useEffect, useState } from 'react';
import { checkboxValidationLength, checkboxValidationRules } from './constants';
@@ -44,19 +36,14 @@ export const CheckboxFieldAdvancedSettings = ({
const [required, setRequired] = useState(fieldState.required ?? false);
const [validationLength, setValidationLength] = useState(fieldState.validationLength ?? 0);
const [validationRule, setValidationRule] = useState(fieldState.validationRule ?? '');
const [direction, setDirection] = useState<'vertical' | 'horizontal'>(
fieldState.direction ?? 'vertical',
);
const [direction, setDirection] = useState<'vertical' | 'horizontal'>(fieldState.direction ?? 'vertical');
const handleToggleChange = (field: keyof CheckboxFieldMeta, value: string | boolean) => {
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
const required = field === 'required' ? Boolean(value) : Boolean(fieldState.required);
const validationRule =
field === 'validationRule' ? String(value) : String(fieldState.validationRule);
const validationLength =
field === 'validationLength' ? Number(value) : Number(fieldState.validationLength);
const currentDirection =
field === 'direction' && String(value) === 'horizontal' ? 'horizontal' : 'vertical';
const validationRule = field === 'validationRule' ? String(value) : String(fieldState.validationRule);
const validationLength = field === 'validationLength' ? Number(value) : Number(fieldState.validationLength);
const currentDirection = field === 'direction' && String(value) === 'horizontal' ? 'horizontal' : 'vertical';
setReadOnly(readOnly);
setRequired(required);
@@ -102,7 +89,9 @@ export const CheckboxFieldAdvancedSettings = ({
}, [values]);
const removeValue = (index: number) => {
if (values.length === 1) return;
if (values.length === 1) {
return;
}
const newValues = [...values];
newValues.splice(index, 1);
@@ -110,11 +99,7 @@ export const CheckboxFieldAdvancedSettings = ({
handleFieldChange('values', newValues);
};
const handleCheckboxValue = (
index: number,
property: 'value' | 'checked',
newValue: string | boolean,
) => {
const handleCheckboxValue = (index: number, property: 'value' | 'checked', newValue: string | boolean) => {
const newValues = [...values];
if (property === 'checked') {
@@ -139,7 +124,7 @@ export const CheckboxFieldAdvancedSettings = ({
</Label>
<Input
id="label"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={_(msg`Field label`)}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
@@ -154,7 +139,7 @@ export const CheckboxFieldAdvancedSettings = ({
value={fieldState.direction ?? 'vertical'}
onValueChange={(val) => handleToggleChange('direction', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={_(msg`Select direction`)} />
</SelectTrigger>
<SelectContent position="popper">
@@ -173,11 +158,8 @@ export const CheckboxFieldAdvancedSettings = ({
<Label>
<Trans>Validation</Trans>
</Label>
<Select
value={fieldState.validationRule}
onValueChange={(val) => handleToggleChange('validationRule', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<Select value={fieldState.validationRule} onValueChange={(val) => handleToggleChange('validationRule', val)}>
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={_(msg`Select at least`)} />
</SelectTrigger>
<SelectContent position="popper">
@@ -194,7 +176,7 @@ export const CheckboxFieldAdvancedSettings = ({
value={fieldState.validationLength ? String(fieldState.validationLength) : ''}
onValueChange={(val) => handleToggleChange('validationLength', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={_(msg`Pick a number`)} />
</SelectTrigger>
<SelectContent position="popper">
@@ -230,7 +212,7 @@ export const CheckboxFieldAdvancedSettings = ({
</div>
</div>
<Button
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 mt-2 border"
className="mt-2 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={() => setShowValidation((prev) => !prev)}
>
@@ -247,7 +229,7 @@ export const CheckboxFieldAdvancedSettings = ({
{values.map((value, index) => (
<div key={index} className="mt-2 flex items-center gap-4">
<Checkbox
className="data-[state=checked]:bg-primary border-foreground/30 h-5 w-5"
className="h-5 w-5 border-foreground/30 data-[state=checked]:bg-primary"
checked={value.checked}
onCheckedChange={(checked) => handleCheckboxValue(index, 'checked', checked)}
/>
@@ -266,7 +248,7 @@ export const CheckboxFieldAdvancedSettings = ({
</div>
))}
<Button
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 ml-9 mt-4 border"
className="mt-4 ml-9 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={addValue}
>
@@ -1,16 +1,9 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateDateFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TDateFieldMeta as DateFieldMeta } from '@documenso/lib/types/field-meta';
import { type TDateFieldMeta as DateFieldMeta, DEFAULT_DATE_OVERFLOW_MODE } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans, useLingui } from '@lingui/react/macro';
type DateFieldAdvancedSettingsProps = {
fieldState: DateFieldMeta;
@@ -48,6 +41,7 @@ export const DateFieldAdvancedSettings = ({
const errors = validateDateFields({
fontSize,
overflow: fieldState.overflow ?? DEFAULT_DATE_OVERFLOW_MODE,
type: 'date',
});
@@ -64,7 +58,7 @@ export const DateFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -78,11 +72,8 @@ export const DateFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,30 +1,19 @@
import { useEffect, useState } from 'react';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import type { TDropdownFieldMeta as DropdownFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
import { type TDropdownFieldMeta as DropdownFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { useEffect, useState } from 'react';
type DropdownFieldAdvancedSettingsProps = {
fieldState: DropdownFieldMeta;
handleFieldChange: (
key: keyof DropdownFieldMeta,
value: string | { value: string }[] | boolean,
) => void;
handleFieldChange: (key: keyof DropdownFieldMeta, value: string | { value: string }[] | boolean) => void;
handleErrors: (errors: string[]) => void;
};
@@ -47,7 +36,9 @@ export const DropdownFieldAdvancedSettings = ({
};
const removeValue = (index: number) => {
if (values.length === 1) return;
if (values.length === 1) {
return;
}
const newValues = [...values];
newValues.splice(index, 1);
@@ -98,7 +89,7 @@ export const DropdownFieldAdvancedSettings = ({
}, [fieldState.defaultValue]);
return (
<div className="text-dark flex flex-col gap-4">
<div className="flex flex-col gap-4 text-dark">
<div>
<Label>
<Trans>Select default option</Trans>
@@ -168,11 +159,7 @@ export const DropdownFieldAdvancedSettings = ({
<div>
{values.map((value, index) => (
<div key={index} className="mt-2 flex items-center gap-4">
<Input
className="w-1/2"
value={value.value}
onChange={(e) => handleValueChange(index, e.target.value)}
/>
<Input className="w-1/2" value={value.value} onChange={(e) => handleValueChange(index, e.target.value)} />
<button
type="button"
className="col-span-1 mt-auto inline-flex h-10 w-10 items-center text-slate-500 hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
@@ -183,7 +170,7 @@ export const DropdownFieldAdvancedSettings = ({
</div>
))}
<Button
className="ml-9 mt-4 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
className="mt-4 ml-9 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={addValue}
>
@@ -1,16 +1,9 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateEmailFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TEmailFieldMeta as EmailFieldMeta } from '@documenso/lib/types/field-meta';
import { DEFAULT_EMAIL_OVERFLOW_MODE, type TEmailFieldMeta as EmailFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans, useLingui } from '@lingui/react/macro';
type EmailFieldAdvancedSettingsProps = {
fieldState: EmailFieldMeta;
@@ -30,6 +23,7 @@ export const EmailFieldAdvancedSettings = ({
const errors = validateEmailFields({
fontSize,
overflow: fieldState.overflow ?? DEFAULT_EMAIL_OVERFLOW_MODE,
type: 'email',
});
@@ -46,7 +40,7 @@ export const EmailFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -60,11 +54,8 @@ export const EmailFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,9 +1,8 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateInitialsFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TInitialsFieldMeta as InitialsFieldMeta } from '@documenso/lib/types/field-meta';
import type { TInitialsFieldMeta as InitialsFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { Trans, useLingui } from '@lingui/react/macro';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../select';
@@ -41,7 +40,7 @@ export const InitialsFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -55,11 +54,8 @@ export const InitialsFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,16 +1,9 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateFields as validateNameFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TNameFieldMeta as NameFieldMeta } from '@documenso/lib/types/field-meta';
import type { TNameFieldMeta as NameFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans, useLingui } from '@lingui/react/macro';
type NameFieldAdvancedSettingsProps = {
fieldState: NameFieldMeta;
@@ -46,7 +39,7 @@ export const NameFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -60,11 +53,8 @@ export const NameFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,21 +1,13 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
import { type TNumberFieldMeta as NumberFieldMeta } from '@documenso/lib/types/field-meta';
import type { TNumberFieldMeta as NumberFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { Trans, useLingui } from '@lingui/react/macro';
import { ChevronDown, ChevronUp } from 'lucide-react';
import { useState } from 'react';
import { numberFormatValues } from './constants';
@@ -65,7 +57,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="label"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Label`}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
@@ -77,7 +69,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="placeholder"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Placeholder`}
value={fieldState.placeholder}
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
@@ -89,7 +81,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="value"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Value`}
value={fieldState.value}
onChange={(e) => handleInput('value', e.target.value)}
@@ -99,11 +91,8 @@ export const NumberFieldAdvancedSettings = ({
<Label>
<Trans>Number format</Trans>
</Label>
<Select
value={fieldState.numberFormat ?? ''}
onValueChange={(val) => handleInput('numberFormat', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<Select value={fieldState.numberFormat ?? ''} onValueChange={(val) => handleInput('numberFormat', val)}>
<SelectTrigger className="mt-2 w-full bg-background text-muted-foreground">
<SelectValue placeholder={t`Field format`} />
</SelectTrigger>
<SelectContent position="popper">
@@ -123,7 +112,7 @@ export const NumberFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -137,11 +126,8 @@ export const NumberFieldAdvancedSettings = ({
<Trans>Text Align</Trans>
</Label>
<Select
value={fieldState.textAlign}
onValueChange={(value) => handleInput('textAlign', value)}
>
<SelectTrigger className="bg-background mt-2">
<Select value={fieldState.textAlign} onValueChange={(value) => handleInput('textAlign', value)}>
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -176,7 +162,7 @@ export const NumberFieldAdvancedSettings = ({
</div>
</div>
<Button
className="bg-foreground/10 hover:bg-foreground/5 border-foreground/10 mt-2 border"
className="mt-2 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={() => setShowValidation((prev) => !prev)}
>
@@ -195,7 +181,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="minValue"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`E.g. 0`}
value={fieldState.minValue ?? ''}
onChange={(e) => handleInput('minValue', e.target.value)}
@@ -207,7 +193,7 @@ export const NumberFieldAdvancedSettings = ({
</Label>
<Input
id="maxValue"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`E.g. 100`}
value={fieldState.maxValue ?? ''}
onChange={(e) => handleInput('maxValue', e.target.value)}
@@ -1,17 +1,15 @@
import { useEffect, useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { validateRadioField } from '@documenso/lib/advanced-fields-validation/validate-radio';
import { type TRadioFieldMeta as RadioFieldMeta } from '@documenso/lib/types/field-meta';
import type { TRadioFieldMeta as RadioFieldMeta } from '@documenso/lib/types/field-meta';
import { Button } from '@documenso/ui/primitives/button';
import { Checkbox } from '@documenso/ui/primitives/checkbox';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import { Switch } from '@documenso/ui/primitives/switch';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { ChevronDown, ChevronUp, Trash } from 'lucide-react';
import { useEffect, useState } from 'react';
export type RadioFieldAdvancedSettingsProps = {
fieldState: RadioFieldMeta;
@@ -30,9 +28,7 @@ export const RadioFieldAdvancedSettings = ({
const { _ } = useLingui();
const [showValidation, setShowValidation] = useState(false);
const [values, setValues] = useState(
fieldState.values ?? [{ id: 1, checked: false, value: _(msg`Default value`) }],
);
const [values, setValues] = useState(fieldState.values ?? [{ id: 1, checked: false, value: _(msg`Default value`) }]);
const [readOnly, setReadOnly] = useState(fieldState.readOnly ?? false);
const [required, setRequired] = useState(fieldState.required ?? false);
@@ -46,7 +42,9 @@ export const RadioFieldAdvancedSettings = ({
};
const removeValue = (id: number) => {
if (values.length === 1) return;
if (values.length === 1) {
return;
}
const newValues = values.filter((val) => val.id !== id);
setValues(newValues);
@@ -186,7 +184,7 @@ export const RadioFieldAdvancedSettings = ({
</div>
))}
<Button
className="ml-9 mt-4 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
className="mt-4 ml-9 border border-foreground/10 bg-foreground/10 hover:bg-foreground/5"
variant="outline"
onClick={addValue}
>
@@ -1,18 +1,11 @@
import { Trans, useLingui } from '@lingui/react/macro';
import { validateTextField } from '@documenso/lib/advanced-fields-validation/validate-text';
import { type TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
import type { TTextFieldMeta as TextFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Switch } from '@documenso/ui/primitives/switch';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { Trans, useLingui } from '@lingui/react/macro';
type TextFieldAdvancedSettingsProps = {
fieldState: TextFieldMeta;
@@ -29,8 +22,7 @@ export const TextFieldAdvancedSettings = ({
const handleInput = (field: keyof TextFieldMeta, value: string | boolean) => {
const text = field === 'text' ? String(value) : (fieldState.text ?? '');
const limit =
field === 'characterLimit' ? Number(value) : Number(fieldState.characterLimit ?? 0);
const limit = field === 'characterLimit' ? Number(value) : Number(fieldState.characterLimit ?? 0);
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
const required = field === 'required' ? Boolean(value) : Boolean(fieldState.required);
@@ -55,7 +47,7 @@ export const TextFieldAdvancedSettings = ({
</Label>
<Input
id="label"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field label`}
value={fieldState.label}
onChange={(e) => handleFieldChange('label', e.target.value)}
@@ -67,7 +59,7 @@ export const TextFieldAdvancedSettings = ({
</Label>
<Input
id="placeholder"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field placeholder`}
value={fieldState.placeholder}
onChange={(e) => handleFieldChange('placeholder', e.target.value)}
@@ -80,7 +72,7 @@ export const TextFieldAdvancedSettings = ({
</Label>
<Textarea
id="text"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Add text to the field`}
value={fieldState.text}
onChange={(e) => handleInput('text', e.target.value)}
@@ -95,7 +87,7 @@ export const TextFieldAdvancedSettings = ({
id="characterLimit"
type="number"
min={0}
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field character limit`}
value={fieldState.characterLimit}
onChange={(e) => handleInput('characterLimit', e.target.value)}
@@ -109,7 +101,7 @@ export const TextFieldAdvancedSettings = ({
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
className="mt-2 bg-background"
placeholder={t`Field font size`}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
@@ -133,7 +125,7 @@ export const TextFieldAdvancedSettings = ({
handleInput('textAlign', value);
}}
>
<SelectTrigger className="bg-background mt-2">
<SelectTrigger className="mt-2 bg-background">
<SelectValue placeholder={t`Select text align`} />
</SelectTrigger>
@@ -1,6 +1,3 @@
import { Trans } from '@lingui/react/macro';
import { DialogClose } from '@radix-ui/react-dialog';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
@@ -10,16 +7,15 @@ import {
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import { Trans } from '@lingui/react/macro';
import { DialogClose } from '@radix-ui/react-dialog';
export type MissingSignatureFieldDialogProps = {
isOpen: boolean;
onOpenChange: (value: boolean) => void;
};
export const MissingSignatureFieldDialog = ({
isOpen,
onOpenChange,
}: MissingSignatureFieldDialogProps) => {
export const MissingSignatureFieldDialog = ({ isOpen, onOpenChange }: MissingSignatureFieldDialogProps) => {
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg" position="center">
@@ -30,8 +26,8 @@ export const MissingSignatureFieldDialog = ({
<DialogDescription>
<p className="mt-2">
<Trans>
Some signers have not been assigned a signature field. Please assign at least 1
signature field to each signer before proceeding.
Some signers have not been assigned a signature field. Please assign at least 1 signature field to each
signer before proceeding.
</Trans>
</p>
</DialogDescription>
@@ -1,7 +1,6 @@
import { useState } from 'react';
import { Trans } from '@lingui/react/macro';
import { Loader } from 'lucide-react';
import { useState } from 'react';
import type { ButtonProps } from '../button';
import { Button } from '../button';
@@ -19,38 +18,31 @@ export type SendDocumentActionDialogProps = ButtonProps & {
loading?: boolean;
};
export const SendDocumentActionDialog = ({
loading,
className,
...props
}: SendDocumentActionDialogProps) => {
export const SendDocumentActionDialog = ({ loading, className, ...props }: SendDocumentActionDialogProps) => {
const [open, setOpen] = useState(false);
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button type="button" className={className}>
{loading && <Loader className="text-documenso mr-2 h-5 w-5 animate-spin" />}
{loading && <Loader className="mr-2 h-5 w-5 animate-spin text-documenso" />}
<Trans>Send</Trans>
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle className="text-center text-lg font-semibold">
<DialogTitle className="text-center font-semibold text-lg">
<Trans>Send Document</Trans>
</DialogTitle>
<DialogDescription className="text-center text-base">
<Trans>
You are about to send this document to the recipients. Are you sure you want to
continue?
</Trans>
<Trans>You are about to send this document to the recipients. Are you sure you want to continue?</Trans>
</DialogDescription>
</DialogHeader>
<DialogFooter className="mt-4 flex items-center gap-x-4">
<Button
className="dark:bg-muted dark:hover:bg-muted/80 dark:focus-visible:ring-muted/80 flex-1 border-none bg-black/5 hover:bg-black/10 focus-visible:ring-black/10"
className="flex-1 border-none bg-black/5 hover:bg-black/10 focus-visible:ring-black/10 dark:bg-muted dark:focus-visible:ring-muted/80 dark:hover:bg-muted/80"
type="button"
variant="secondary"
onClick={() => setOpen(false)}
@@ -15,19 +15,15 @@ export type SigningOrderConfirmationProps = {
onConfirm: () => void;
};
export function SigningOrderConfirmation({
open,
onOpenChange,
onConfirm,
}: SigningOrderConfirmationProps) {
export function SigningOrderConfirmation({ open, onOpenChange, onConfirm }: SigningOrderConfirmationProps) {
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Warning</AlertDialogTitle>
<AlertDialogDescription>
You have an assistant role on the signers list, removing the signing order will change
the assistant role to signer.
You have an assistant role on the signers list, removing the signing order will change the assistant role to
signer.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
@@ -1,11 +1,10 @@
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { zEmail } from '@documenso/lib/utils/zod';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { FieldType } from '@prisma/client';
import { z } from 'zod';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { zEmail } from '@documenso/lib/utils/zod';
export const ZDocumentFlowFormSchema = z.object({
title: z.string().min(1),
@@ -1,3 +1,9 @@
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
@@ -8,13 +14,6 @@ import type { DropEvent, FileRejection } from 'react-dropzone';
import { useDropzone } from 'react-dropzone';
import { Link } from 'react-router';
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT, IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { ALLOWED_UPLOAD_MIME_TYPES } from '@documenso/lib/constants/upload';
import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import { Button } from './button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip';
@@ -67,10 +66,8 @@ export const DocumentUploadButton = ({
});
const heading = {
[EnvelopeType.DOCUMENT]:
internalVersion === '1' ? msg`Document (Legacy)` : msg`Upload Document`,
[EnvelopeType.TEMPLATE]:
internalVersion === '1' ? msg`Template (Legacy)` : msg`Upload Template`,
[EnvelopeType.DOCUMENT]: internalVersion === '1' ? msg`Document (Legacy)` : msg`Upload Document`,
[EnvelopeType.TEMPLATE]: internalVersion === '1' ? msg`Template (Legacy)` : msg`Upload Template`,
};
if (disabled && IS_BILLING_ENABLED()) {
@@ -79,13 +76,7 @@ export const DocumentUploadButton = ({
<Tooltip>
<TooltipTrigger asChild>
<Button className="bg-warning hover:bg-warning/80" asChild>
<Link
to={
isPersonalLayoutMode
? `/settings/billing`
: `/o/${organisation.url}/settings/billing`
}
>
<Link to={isPersonalLayoutMode ? `/settings/billing` : `/o/${organisation.url}/settings/billing`}>
<Trans>Upgrade</Trans>
</Link>
</Button>
+17 -24
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -26,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && 'pl-8',
className,
)}
@@ -46,7 +45,7 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -64,7 +63,7 @@ const DropdownMenuContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -83,7 +82,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
@@ -100,7 +99,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
@@ -124,7 +123,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -148,7 +147,7 @@ const DropdownMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn('px-2 py-1.5 font-semibold text-sm', inset && 'pl-8', className)}
{...props}
/>
));
@@ -159,37 +158,31 @@ const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('bg-muted -mx-1 my-1 h-px', className)}
{...props}
/>
<DropdownMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />
);
return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />;
};
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
DropdownMenuTrigger,
};
+2 -1
View File
@@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import type React from 'react';
import { useEffect, useState } from 'react';
export type ElementVisibleProps = {
target: string;
+5 -18
View File
@@ -1,16 +1,6 @@
import { Trans } from '@lingui/react/macro';
import { FieldType } from '@prisma/client';
import {
CalendarDays,
CheckSquare,
ChevronDown,
Contact,
Disc,
Hash,
Mail,
Type,
User,
} from 'lucide-react';
import { CalendarDays, CheckSquare, ChevronDown, Contact, Disc, Hash, Mail, Type, User } from 'lucide-react';
import { cn } from '../lib/utils';
import { Card, CardContent } from './card';
@@ -96,18 +86,15 @@ export const FieldSelector = ({
data-selected={selectedField === field.type ? true : undefined}
>
<Card
className={cn(
'flex w-full cursor-pointer items-center justify-center group-disabled:opacity-50',
{
'border-primary': selectedField === field.type,
},
)}
className={cn('flex w-full cursor-pointer items-center justify-center group-disabled:opacity-50', {
'border-primary': selectedField === field.type,
})}
>
<CardContent className="relative flex items-center justify-center gap-x-2 px-6 py-4">
{Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
<span
className={cn(
'text-sm text-muted-foreground group-data-[selected]:text-foreground',
'text-muted-foreground text-sm group-data-[selected]:text-foreground',
field.type === FieldType.SIGNATURE && 'invisible',
)}
>
@@ -38,7 +38,7 @@ export const FormErrorMessage = ({ error, className }: FormErrorMessageProps) =>
opacity: 0,
y: 10,
}}
className={cn('text-xs text-red-500', className)}
className={cn('text-red-500 text-xs', className)}
>
{errorMessage}
</motion.p>
+57 -82
View File
@@ -1,9 +1,8 @@
import * as React from 'react';
import { useLingui } from '@lingui/react';
import type * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import { AnimatePresence, motion } from 'framer-motion';
import * as React from 'react';
import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
import { Controller, FormProvider, useFormContext } from 'react-hook-form';
@@ -98,97 +97,73 @@ const FormLabel = React.forwardRef<
});
FormLabel.displayName = 'FormLabel';
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
const FormControl = React.forwardRef<React.ElementRef<typeof Slot>, React.ComponentPropsWithoutRef<typeof Slot>>(
({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
);
});
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>
);
},
);
FormControl.displayName = 'FormControl';
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
const FormDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
});
return <p ref={ref} id={formDescriptionId} className={cn('text-muted-foreground text-sm', className)} {...props} />;
},
);
FormDescription.displayName = 'FormDescription';
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { i18n } = useLingui();
const FormMessage = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, children, ...props }, ref) => {
const { i18n } = useLingui();
const { error, formMessageId } = useFormField();
const { error, formMessageId } = useFormField();
let body = error ? String(error?.message) : children;
let body = error ? String(error?.message) : children;
if (!body) {
return null;
}
if (!body) {
return null;
}
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
if (typeof body === 'string' && i18n.t(body)) {
body = i18n.t(body);
}
// Checks to see if there's a translation for the string, since we're passing IDs for Zod errors.
if (typeof body === 'string' && i18n.t(body)) {
body = i18n.t(body);
}
return (
<AnimatePresence>
<motion.div
initial={{
opacity: 0,
y: -10,
}}
animate={{
opacity: 1,
y: 0,
}}
exit={{
opacity: 0,
y: 10,
}}
>
<p
ref={ref}
id={formMessageId}
className={cn('text-xs text-red-500', className)}
{...props}
return (
<AnimatePresence>
<motion.div
initial={{
opacity: 0,
y: -10,
}}
animate={{
opacity: 1,
y: 0,
}}
exit={{
opacity: 0,
y: 10,
}}
>
{body}
</p>
</motion.div>
</AnimatePresence>
);
});
<p ref={ref} id={formMessageId} className={cn('text-red-500 text-xs', className)} {...props}>
{body}
</p>
</motion.div>
</AnimatePresence>
);
},
);
FormMessage.displayName = 'FormMessage';
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
};
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
+3 -4
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -17,7 +16,7 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in zoom-in-90 z-50 w-64 rounded-md border p-4 shadow-md outline-none',
'zoom-in-90 z-50 w-64 animate-in rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
className,
)}
{...props}
@@ -26,4 +25,4 @@ const HoverCardContent = React.forwardRef<
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName;
export { HoverCard, HoverCardTrigger, HoverCardContent };
export { HoverCard, HoverCardContent, HoverCardTrigger };
+16 -18
View File
@@ -4,24 +4,22 @@ import { cn } from '../lib/utils';
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'bg-background border-input ring-offset-background placeholder:text-muted-foreground/40 focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-base file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
{
'ring-2 !ring-red-500 transition-all': props['aria-invalid'],
},
)}
ref={ref}
{...props}
/>
);
},
);
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
{
'!ring-red-500 ring-2 transition-all': props['aria-invalid'],
},
)}
ref={ref}
{...props}
/>
);
});
Input.displayName = 'Input';
+3 -6
View File
@@ -1,14 +1,11 @@
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import * as React from 'react';
import { cn } from '../lib/utils';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
);
const labelVariants = cva('text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70');
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
@@ -17,7 +14,7 @@ const Label = React.forwardRef<
>(({ className, children, required, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props}>
{children}
{required && <span className="text-destructive ml-1 inline-block font-medium">*</span>}
{required && <span className="ml-1 inline-block font-medium text-destructive">*</span>}
</LabelPrimitive.Root>
));
+22 -35
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as MenubarPrimitive from '@radix-ui/react-menubar';
import { Check, ChevronRight, Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -21,10 +20,7 @@ const Menubar = React.forwardRef<
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Root
ref={ref}
className={cn(
'bg-background flex h-10 items-center space-x-1 rounded-md border p-1',
className,
)}
className={cn('flex h-10 items-center space-x-1 rounded-md border bg-background p-1', className)}
{...props}
/>
));
@@ -38,7 +34,7 @@ const MenubarTrigger = React.forwardRef<
<MenubarPrimitive.Trigger
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none',
'flex cursor-default select-none items-center rounded-sm px-3 py-1.5 font-medium text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
className,
)}
{...props}
@@ -56,7 +52,7 @@ const MenubarSubTrigger = React.forwardRef<
<MenubarPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
inset && 'pl-8',
className,
)}
@@ -76,7 +72,7 @@ const MenubarSubContent = React.forwardRef<
<MenubarPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
'data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 z-50 min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -96,7 +92,7 @@ const MenubarContent = React.forwardRef<
alignOffset={alignOffset}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in slide-in-from-top-1 z-50 min-w-[12rem] overflow-hidden rounded-md border p-1 shadow-md',
'slide-in-from-top-1 z-50 min-w-[12rem] animate-in overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
className,
)}
{...props}
@@ -115,7 +111,7 @@ const MenubarItem = React.forwardRef<
<MenubarPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className,
)}
@@ -132,7 +128,7 @@ const MenubarCheckboxItem = React.forwardRef<
<MenubarPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
checked={checked}
@@ -156,7 +152,7 @@ const MenubarRadioItem = React.forwardRef<
<MenubarPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -180,7 +176,7 @@ const MenubarLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<MenubarPrimitive.Label
ref={ref}
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
className={cn('px-2 py-1.5 font-semibold text-sm', inset && 'pl-8', className)}
{...props}
/>
));
@@ -191,41 +187,32 @@ const MenubarSeparator = React.forwardRef<
React.ElementRef<typeof MenubarPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
>(({ className, ...props }, ref) => (
<MenubarPrimitive.Separator
ref={ref}
className={cn('bg-muted -mx-1 my-1 h-px', className)}
{...props}
/>
<MenubarPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
));
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
const MenubarShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
return <span className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)} {...props} />;
};
MenubarShortcut.displayname = 'MenubarShortcut';
export {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
MenubarSeparator,
MenubarLabel,
MenubarCheckboxItem,
MenubarContent,
MenubarGroup,
MenubarItem,
MenubarLabel,
MenubarMenu,
MenubarPortal,
MenubarRadioGroup,
MenubarRadioItem,
MenubarPortal,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarGroup,
MenubarSub,
MenubarShortcut,
MenubarTrigger,
};
@@ -1,4 +1,4 @@
import * as React from 'react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import type { MessageDescriptor } from '@lingui/core';
import { t } from '@lingui/core/macro';
@@ -6,8 +6,7 @@ import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { AnimatePresence } from 'framer-motion';
import { Check, ChevronsUpDown, Loader, XIcon } from 'lucide-react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Button } from './button';
@@ -141,12 +140,12 @@ export function MultiSelectCombobox<T = OptionValue>({
{/* This is placed outside the trigger since we can't have nested buttons. */}
{showClearButton && !loading && (
<div className="absolute bottom-0 right-8 top-0 flex items-center justify-center">
<div className="absolute top-0 right-8 bottom-0 flex items-center justify-center">
<button
className="flex h-4 w-4 items-center justify-center rounded-full bg-gray-300 dark:bg-neutral-700"
onClick={() => onChange([])}
>
<XIcon className="text-muted-foreground h-3.5 w-3.5" />
<XIcon className="h-3.5 w-3.5 text-muted-foreground" />
</button>
</div>
)}
@@ -162,10 +161,7 @@ export function MultiSelectCombobox<T = OptionValue>({
{options.map((option, i) => (
<CommandItem key={i} onSelect={() => handleSelect(option.value)}>
<Check
className={cn(
'mr-2 h-4 w-4',
selectedValues.includes(option.value) ? 'opacity-100' : 'opacity-0',
)}
className={cn('mr-2 h-4 w-4', selectedValues.includes(option.value) ? 'opacity-100' : 'opacity-0')}
/>
{option.label}
</CommandItem>
+62 -69
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import { useEffect } from 'react';
import { Command as CommandPrimitive, useCommandState } from 'cmdk';
import { XIcon } from 'lucide-react';
import * as React from 'react';
import { useEffect } from 'react';
import { useDebounce } from '../lib/use-debounce';
import { cn } from '../lib/utils';
@@ -126,21 +125,15 @@ function isOptionsExist(groupOption: GroupOption, targetOption: Option[]) {
return false;
}
const CommandEmpty = ({
className,
...props
}: React.ComponentProps<typeof CommandPrimitive.Empty>) => {
const CommandEmpty = ({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) => {
const render = useCommandState((state) => state.filtered.count === 0);
if (!render) return null;
if (!render) {
return null;
}
return (
<div
className={cn('px-2 py-4 text-center text-sm', className)}
cmdk-empty=""
role="presentation"
{...props}
/>
<div className={cn('px-2 py-4 text-center text-sm', className)} cmdk-empty="" role="presentation" {...props} />
);
};
@@ -179,9 +172,7 @@ const MultiSelect = ({
const dropdownRef = React.useRef<HTMLDivElement>(null); // Added this
const [selected, setSelected] = React.useState<Option[]>(value || []);
const [options, setOptions] = React.useState<GroupOption>(
transToGroupOption(arrayDefaultOptions, groupBy),
);
const [options, setOptions] = React.useState<GroupOption>(transToGroupOption(arrayDefaultOptions, groupBy));
const [inputValue, setInputValue] = React.useState('');
const debouncedSearchTerm = useDebounce(inputValue, delay || 500);
@@ -270,7 +261,9 @@ const MultiSelect = ({
// eslint-disable-next-line @typescript-eslint/require-await
const exec = async () => {
if (!onSearchSync || !open) return;
if (!onSearchSync || !open) {
return;
}
if (triggerSearchOnFocus) {
doSearchSync();
@@ -296,7 +289,9 @@ const MultiSelect = ({
};
const exec = async () => {
if (!onSearch || !open) return;
if (!onSearch || !open) {
return;
}
if (triggerSearchOnFocus) {
await doSearch();
@@ -312,7 +307,9 @@ const MultiSelect = ({
}, [debouncedSearchTerm, groupBy, open, triggerSearchOnFocus]);
const CreatableItem = () => {
if (!creatable) return undefined;
if (!creatable) {
return undefined;
}
if (
isOptionsExist(options, [{ value: inputValue, label: inputValue }]) ||
selected.find((s) => s.value === inputValue)
@@ -357,7 +354,9 @@ const MultiSelect = ({
};
const EmptyItem = React.useCallback(() => {
if (!emptyIndicator) return undefined;
if (!emptyIndicator) {
return undefined;
}
// For async search that showing emptyIndicator
if (onSearch && !creatable && Object.keys(options).length === 0) {
@@ -371,10 +370,7 @@ const MultiSelect = ({
return <CommandEmpty>{emptyIndicator}</CommandEmpty>;
}, [creatable, emptyIndicator, onSearch, options]);
const selectables = React.useMemo<GroupOption>(
() => removePickedOption(options, selected),
[options, selected],
);
const selectables = React.useMemo<GroupOption>(() => removePickedOption(options, selected), [options, selected]);
/** Avoid Creatable Selector freezing or lagging when paste a long string. */
const commandFilter = React.useCallback(() => {
@@ -400,15 +396,13 @@ const MultiSelect = ({
commandProps?.onKeyDown?.(e);
}}
className={cn('h-auto overflow-visible bg-transparent', commandProps?.className)}
shouldFilter={
commandProps?.shouldFilter !== undefined ? commandProps.shouldFilter : !onSearch
} // When onSearch is provided, we don&lsquo;t want to filter the options. You can still override it.
shouldFilter={commandProps?.shouldFilter !== undefined ? commandProps.shouldFilter : !onSearch} // When onSearch is provided, we don&lsquo;t want to filter the options. You can still override it.
filter={commandFilter()}
data-testid={dataTestId}
>
<div
className={cn(
'border-input focus-within:border-ring focus-within:ring-ring/50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-disabled:opacity-50 relative min-h-[38px] rounded-md border text-sm outline-none transition-[color,box-shadow] focus-within:ring-[3px]',
'relative min-h-[38px] rounded-md border border-input text-sm outline-none transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 has-disabled:pointer-events-none has-disabled:cursor-not-allowed has-aria-invalid:border-destructive has-disabled:opacity-50 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40',
{
'p-1': selected.length !== 0,
'cursor-text': !disabled && selected.length !== 0,
@@ -417,7 +411,9 @@ const MultiSelect = ({
className,
)}
onClick={() => {
if (disabled) return;
if (disabled) {
return;
}
inputRef?.current?.focus();
}}
>
@@ -427,7 +423,7 @@ const MultiSelect = ({
<div
key={option.value}
className={cn(
'animate-fadeIn bg-background text-secondary-foreground hover:bg-background data-fixed:pe-2 relative inline-flex h-7 cursor-default items-center rounded-md border pe-7 pl-2 ps-2 text-xs font-medium transition-all disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
'relative inline-flex h-7 animate-fadeIn cursor-default items-center rounded-md border bg-background ps-2 pe-7 pl-2 font-medium text-secondary-foreground text-xs transition-all hover:bg-background disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 data-fixed:pe-2',
badgeClassName,
)}
data-fixed={option.fixed}
@@ -435,7 +431,7 @@ const MultiSelect = ({
>
{option.label}
<button
className="text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-md border border-transparent p-0 outline-none transition-[color,box-shadow] focus-visible:ring-[3px]"
className="absolute -inset-y-px -end-px flex size-7 items-center justify-center rounded-e-md border border-transparent p-0 text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleUnselect(option);
@@ -478,7 +474,7 @@ const MultiSelect = ({
}}
placeholder={hidePlaceholderWhenSelected && selected.length !== 0 ? '' : placeholder}
className={cn(
'placeholder:text-muted-foreground/70 flex-1 bg-transparent outline-none disabled:cursor-not-allowed',
'flex-1 bg-transparent outline-none placeholder:text-muted-foreground/70 disabled:cursor-not-allowed',
{
'w-full': hidePlaceholderWhenSelected,
'px-3 py-2': selected.length === 0,
@@ -494,7 +490,7 @@ const MultiSelect = ({
onChange?.(selected.filter((s) => s.fixed));
}}
className={cn(
'text-muted-foreground/80 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 absolute end-0 top-0 flex size-9 items-center justify-center rounded-md border border-transparent outline-none transition-[color,box-shadow] focus-visible:ring-[3px]',
'absolute end-0 top-0 flex size-9 items-center justify-center rounded-md border border-transparent text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50',
(hideClearAllButton ||
disabled ||
selected.length < 1 ||
@@ -510,8 +506,8 @@ const MultiSelect = ({
<div className="relative">
<div
className={cn(
'border-input absolute top-2 z-10 w-full overflow-hidden rounded-md border',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
'absolute top-2 z-10 w-full overflow-hidden rounded-md border border-input',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=open]:animate-in',
!open && 'hidden',
)}
data-state={open ? 'open' : 'closed'}
@@ -530,7 +526,7 @@ const MultiSelect = ({
}}
>
{isLoading ? (
<>{loadingIndicator}</>
loadingIndicator
) : (
<>
{EmptyItem()}
@@ -538,38 +534,35 @@ const MultiSelect = ({
{!selectFirstItem && <CommandItem value="-" className="hidden" />}
{Object.entries(selectables).map(([key, dropdowns]) => (
<CommandGroup key={key} heading={key} className="h-full overflow-auto">
<>
{dropdowns.map((option) => {
return (
<CommandItem
key={option.value}
value={option.value}
disabled={option.disable}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={() => {
if (selected.length >= maxSelected) {
onMaxSelected?.(selected.length);
return;
}
setInputValue('');
const newOptions = [...selected, option];
setSelected(newOptions);
onChange?.(newOptions);
}}
className={cn(
'cursor-pointer',
option.disable &&
'pointer-events-none cursor-not-allowed opacity-50',
)}
>
{option.label}
</CommandItem>
);
})}
</>
{dropdowns.map((option) => {
return (
<CommandItem
key={option.value}
value={option.value}
disabled={option.disable}
onMouseDown={(e) => {
e.preventDefault();
e.stopPropagation();
}}
onSelect={() => {
if (selected.length >= maxSelected) {
onMaxSelected?.(selected.length);
return;
}
setInputValue('');
const newOptions = [...selected, option];
setSelected(newOptions);
onChange?.(newOptions);
}}
className={cn(
'cursor-pointer',
option.disable && 'pointer-events-none cursor-not-allowed opacity-50',
)}
>
{option.label}
</CommandItem>
);
})}
</CommandGroup>
))}
</>
+11 -12
View File
@@ -1,8 +1,7 @@
import * as React from 'react';
import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu';
import { cva } from 'class-variance-authority';
import { ChevronDown } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -67,7 +66,7 @@ const NavigationMenuContent = React.forwardRef<
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
'data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 left-0 top-0 w-full md:absolute md:w-auto',
'data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out md:absolute md:w-auto',
className,
)}
{...props}
@@ -82,10 +81,10 @@ const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn('absolute left-0 top-full flex justify-center')}>
<div className={cn('absolute top-full left-0 flex justify-center')}>
<NavigationMenuPrimitive.Viewport
className={cn(
'origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow-lg md:w-[var(--radix-navigation-menu-viewport-width)]',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full origin-top-center overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=closed]:animate-out data-[state=open]:animate-in md:w-[var(--radix-navigation-menu-viewport-width)]',
className,
)}
ref={ref}
@@ -103,25 +102,25 @@ const NavigationMenuIndicator = React.forwardRef<
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
'data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden',
'data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=visible]:animate-in',
className,
)}
{...props}
>
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
));
NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName;
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
NavigationMenuViewport,
navigationMenuTriggerStyle,
};
+22 -30
View File
@@ -1,42 +1,34 @@
import * as React from 'react';
import { Eye, EyeOff } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
import { Button } from './button';
import type { InputProps } from './input';
import { Input } from './input';
const PasswordInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'type'>>(
({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
const PasswordInput = React.forwardRef<HTMLInputElement, Omit<InputProps, 'type'>>(({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = React.useState(false);
return (
<div className="relative">
<Input
type={showPassword ? 'text' : 'password'}
className={cn('pr-10', className)}
ref={ref}
{...props}
/>
return (
<div className="relative">
<Input type={showPassword ? 'text' : 'password'} className={cn('pr-10', className)} ref={ref} {...props} />
<Button
variant="link"
type="button"
className="absolute right-0 top-0 flex h-full items-center justify-center pr-3"
aria-label={showPassword ? 'Mask password' : 'Reveal password'}
onClick={() => setShowPassword((show) => !show)}
>
{showPassword ? (
<EyeOff aria-hidden className="text-muted-foreground h-5 w-5" />
) : (
<Eye aria-hidden className="text-muted-foreground h-5 w-5" />
)}
</Button>
</div>
);
},
);
<Button
variant="link"
type="button"
className="absolute top-0 right-0 flex h-full items-center justify-center pr-3"
aria-label={showPassword ? 'Mask password' : 'Reveal password'}
onClick={() => setShowPassword((show) => !show)}
>
{showPassword ? (
<EyeOff aria-hidden className="h-5 w-5 text-muted-foreground" />
) : (
<Eye aria-hidden className="h-5 w-5 text-muted-foreground" />
)}
</Button>
</div>
);
});
PasswordInput.displayName = 'PasswordInput';
+25 -34
View File
@@ -1,33 +1,25 @@
import * as React from 'react';
import { OTPInput, OTPInputContext } from 'input-otp';
import { Minus } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
const PinInput = React.forwardRef<
React.ElementRef<typeof OTPInput>,
React.ComponentPropsWithoutRef<typeof OTPInput>
>(({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn(
'flex items-center gap-2 has-[:disabled]:opacity-50',
containerClassName,
)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
));
const PinInput = React.forwardRef<React.ElementRef<typeof OTPInput>, React.ComponentPropsWithoutRef<typeof OTPInput>>(
({ className, containerClassName, ...props }, ref) => (
<OTPInput
ref={ref}
containerClassName={cn('flex items-center gap-2 has-[:disabled]:opacity-50', containerClassName)}
className={cn('disabled:cursor-not-allowed', className)}
{...props}
/>
),
);
PinInput.displayName = 'PinInput';
const PinInputGroup = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center', className)} {...props} />
));
const PinInputGroup = React.forwardRef<React.ElementRef<'div'>, React.ComponentPropsWithoutRef<'div'>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('flex items-center', className)} {...props} />,
);
PinInputGroup.displayName = 'PinInputGroup';
@@ -42,8 +34,8 @@ const PinInputSlot = React.forwardRef<
<div
ref={ref}
className={cn(
'border-input relative flex h-10 w-10 items-center justify-center border-y border-r font-mono shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'ring-ring z-10 ring-1',
'relative flex h-10 w-10 items-center justify-center border-input border-y border-r font-mono shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
isActive && 'z-10 ring-1 ring-ring',
className,
)}
{...props}
@@ -51,7 +43,7 @@ const PinInputSlot = React.forwardRef<
{char}
{hasFakeCaret && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
<div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
</div>
)}
</div>
@@ -60,15 +52,14 @@ const PinInputSlot = React.forwardRef<
PinInputSlot.displayName = 'PinInputSlot';
const PinInputSeparator = React.forwardRef<
React.ElementRef<'div'>,
React.ComponentPropsWithoutRef<'div'>
>(({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Minus className="h-5 w-5" />
</div>
));
const PinInputSeparator = React.forwardRef<React.ElementRef<'div'>, React.ComponentPropsWithoutRef<'div'>>(
({ ...props }, ref) => (
<div ref={ref} role="separator" {...props}>
<Minus className="h-5 w-5" />
</div>
),
);
PinInputSeparator.displayName = 'PinInputSeparator';
export { PinInput, PinInputGroup, PinInputSlot, PinInputSeparator };
export { PinInput, PinInputGroup, PinInputSeparator, PinInputSlot };
+4 -10
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -20,7 +19,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 animate-in rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
className,
)}
{...props}
@@ -81,16 +80,11 @@ const PopoverHover = ({ trigger, children, contentProps, side = 'top' }: Popover
{trigger}
</PopoverTrigger>
<PopoverContent
side={side}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
{...contentProps}
>
<PopoverContent side={side} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...contentProps}>
{children}
</PopoverContent>
</Popover>
);
};
export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent, PopoverHover };
export { Popover, PopoverAnchor, PopoverContent, PopoverHover, PopoverTrigger };
+3 -4
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -10,11 +9,11 @@ const Progress = React.forwardRef<
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn('bg-secondary relative h-4 w-full overflow-hidden rounded-full', className)}
className={cn('relative h-4 w-full overflow-hidden rounded-full bg-secondary', className)}
{...props}
>
<ProgressPrimitive.Indicator
className="bg-primary h-full w-full flex-1 transition-all"
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
+3 -4
View File
@@ -1,7 +1,6 @@
import * as React from 'react';
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import { Circle } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -22,13 +21,13 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'border-primary text-primary focus-visible:ring-ring aspect-square h-4 w-4 rounded-full border shadow focus:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<Circle className="fill-primary h-2.5 w-2.5" />
<Circle className="h-2.5 w-2.5 fill-primary" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
+12 -28
View File
@@ -1,14 +1,12 @@
import { useCallback, useMemo, useState } from 'react';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { RecipientRole, SendStatus, SigningStatus } from '@prisma/client';
import { Check, ChevronsUpDown, Info } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { sortBy } from 'remeda';
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { getRecipientColorStyles } from '../lib/recipient-colors';
import { cn } from '../lib/utils';
import { Button } from './button';
@@ -53,20 +51,13 @@ export const RecipientSelector = ({
const recipientsByRoleToDisplay = useMemo(() => {
return Object.entries(recipientsByRole)
.filter(
([role]) =>
role !== RecipientRole.CC &&
role !== RecipientRole.VIEWER &&
role !== RecipientRole.ASSISTANT,
([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER && role !== RecipientRole.ASSISTANT,
)
.map(
([role, roleRecipients]) =>
[
role,
sortBy(
roleRecipients,
[(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
[(r) => r.id, 'asc'],
),
sortBy(roleRecipients, [(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'], [(r) => r.id, 'asc']),
] as [RecipientRole, TRecipientLite[]],
);
}, [recipientsByRole]);
@@ -102,15 +93,12 @@ export const RecipientSelector = ({
role="combobox"
className={cn(
'justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id))
.comboBoxTrigger,
getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id)).comboBoxTrigger,
className,
)}
>
{selectedRecipient && (
<span className="flex-1 truncate text-left">
{getRecipientLabel(selectedRecipient)}
</span>
<span className="flex-1 truncate text-left">{getRecipientLabel(selectedRecipient)}</span>
)}
<ChevronsUpDown className="ml-2 h-4 w-4" />
@@ -129,15 +117,12 @@ export const RecipientSelector = ({
{recipientsByRoleToDisplay.map(([role, roleRecipients], roleIndex) => (
<CommandGroup key={roleIndex}>
<div className="mb-1 ml-2 mt-2 text-xs font-medium text-muted-foreground">
<div className="mt-2 mb-1 ml-2 font-medium text-muted-foreground text-xs">
{_(RECIPIENT_ROLES_DESCRIPTION[role].roleNamePlural)}
</div>
{roleRecipients.length === 0 && (
<div
key={`${role}-empty`}
className="px-4 pb-4 pt-2.5 text-center text-xs text-muted-foreground/80"
>
<div key={`${role}-empty`} className="px-4 pt-2.5 pb-4 text-center text-muted-foreground/80 text-xs">
<Trans>No recipients with this role</Trans>
</div>
)}
@@ -147,8 +132,7 @@ export const RecipientSelector = ({
key={recipient.id}
className={cn(
'px-2 last:mb-1 [&:not(:first-child)]:mt-1',
getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id))
.comboBoxItem,
getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id)).comboBoxItem,
{
'text-muted-foreground': recipient.sendStatus === SendStatus.SENT,
},
@@ -184,8 +168,8 @@ export const RecipientSelector = ({
<TooltipContent className="max-w-xs text-muted-foreground">
<Trans>
This document has already been sent to this recipient. You can no longer
edit this recipient.
This document has already been sent to this recipient. You can no longer edit this
recipient.
</Trans>
</TooltipContent>
</Tooltip>
+4 -11
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -8,14 +7,8 @@ const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn('relative overflow-hidden', className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollAreaPrimitive.Root ref={ref} className={cn('relative overflow-hidden', className)} {...props}>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">{children}</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
@@ -38,7 +31,7 @@ const ScrollBar = React.forwardRef<
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="bg-border relative flex-1 rounded-full" />
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
));
+7 -25
View File
@@ -1,10 +1,9 @@
import * as React from 'react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import * as SelectPrimitive from '@radix-ui/react-select';
import { AnimatePresence } from 'framer-motion';
import { Check, ChevronDown, Loader } from 'lucide-react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -56,7 +55,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-[1001] min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md animate-in fade-in-80',
'fade-in-80 relative z-[1001] min-w-[8rem] animate-in overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md',
position === 'popper' && 'translate-y-1',
className,
)}
@@ -82,11 +81,7 @@ const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
{...props}
/>
<SelectPrimitive.Label ref={ref} className={cn('py-1.5 pr-2 pl-8 font-semibold text-sm', className)} {...props} />
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
@@ -98,7 +93,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className,
)}
{...props}
@@ -119,22 +114,9 @@ const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
<SelectPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
};
export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue };
+2 -7
View File
@@ -1,6 +1,5 @@
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -12,11 +11,7 @@ const Separator = React.forwardRef<
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
'bg-border shrink-0',
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
className,
)}
className={cn('shrink-0 bg-border', orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]', className)}
{...props}
/>
));
+104 -134
View File
@@ -1,10 +1,9 @@
import * as React from 'react';
import { Trans } from '@lingui/react/macro';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import { X } from 'lucide-react';
import * as React from 'react';
import { cn } from '../lib/utils';
@@ -24,9 +23,7 @@ const portalVariants = cva('fixed inset-0 z-[61] flex', {
defaultVariants: { position: 'right' },
});
interface SheetPortalProps
extends SheetPrimitive.DialogPortalProps,
VariantProps<typeof portalVariants> {}
interface SheetPortalProps extends SheetPrimitive.DialogPortalProps, VariantProps<typeof portalVariants> {}
const SheetPortal = ({ position, children, ...props }: SheetPortalProps) => (
<SheetPrimitive.Portal {...props}>
@@ -42,7 +39,7 @@ const SheetOverlay = React.forwardRef<
>(({ className, children: _children, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
'fixed inset-0 z-[61] bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=open]:fade-in',
'data-[state=closed]:fade-out data-[state=open]:fade-in fixed inset-0 z-[61] bg-background/80 backdrop-blur-sm transition-all duration-100 data-[state=closed]:animate-out',
className,
)}
{...props}
@@ -52,93 +49,90 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
'fixed z-[61] scale-100 gap-4 bg-background p-6 opacity-100 shadow-lg border',
{
variants: {
position: {
top: 'animate-in slide-in-from-top w-full duration-300',
bottom: 'animate-in slide-in-from-bottom w-full duration-300',
left: 'animate-in slide-in-from-left h-full duration-300',
right: 'animate-in slide-in-from-right h-full duration-300',
},
size: {
content: '',
default: '',
sm: '',
lg: '',
xl: '',
full: '',
},
const sheetVariants = cva('fixed z-[61] scale-100 gap-4 bg-background p-6 opacity-100 shadow-lg border', {
variants: {
position: {
top: 'animate-in slide-in-from-top w-full duration-300',
bottom: 'animate-in slide-in-from-bottom w-full duration-300',
left: 'animate-in slide-in-from-left h-full duration-300',
right: 'animate-in slide-in-from-right h-full duration-300',
},
compoundVariants: [
{
position: ['top', 'bottom'],
size: 'content',
class: 'max-h-screen',
},
{
position: ['top', 'bottom'],
size: 'default',
class: 'h-1/3',
},
{
position: ['top', 'bottom'],
size: 'sm',
class: 'h-1/4',
},
{
position: ['top', 'bottom'],
size: 'lg',
class: 'h-1/2',
},
{
position: ['top', 'bottom'],
size: 'xl',
class: 'h-5/6',
},
{
position: ['top', 'bottom'],
size: 'full',
class: 'h-screen',
},
{
position: ['right', 'left'],
size: 'content',
class: 'max-w-screen',
},
{
position: ['right', 'left'],
size: 'default',
class: 'w-1/3',
},
{
position: ['right', 'left'],
size: 'sm',
class: 'w-1/4',
},
{
position: ['right', 'left'],
size: 'lg',
class: 'w-1/2',
},
{
position: ['right', 'left'],
size: 'xl',
class: 'w-5/6',
},
{
position: ['right', 'left'],
size: 'full',
class: 'w-screen',
},
],
defaultVariants: {
position: 'right',
size: 'default',
size: {
content: '',
default: '',
sm: '',
lg: '',
xl: '',
full: '',
},
},
);
compoundVariants: [
{
position: ['top', 'bottom'],
size: 'content',
class: 'max-h-screen',
},
{
position: ['top', 'bottom'],
size: 'default',
class: 'h-1/3',
},
{
position: ['top', 'bottom'],
size: 'sm',
class: 'h-1/4',
},
{
position: ['top', 'bottom'],
size: 'lg',
class: 'h-1/2',
},
{
position: ['top', 'bottom'],
size: 'xl',
class: 'h-5/6',
},
{
position: ['top', 'bottom'],
size: 'full',
class: 'h-screen',
},
{
position: ['right', 'left'],
size: 'content',
class: 'max-w-screen',
},
{
position: ['right', 'left'],
size: 'default',
class: 'w-1/3',
},
{
position: ['right', 'left'],
size: 'sm',
class: 'w-1/4',
},
{
position: ['right', 'left'],
size: 'lg',
class: 'w-1/2',
},
{
position: ['right', 'left'],
size: 'xl',
class: 'w-5/6',
},
{
position: ['right', 'left'],
size: 'full',
class: 'w-screen',
},
],
defaultVariants: {
position: 'right',
size: 'default',
},
});
export interface DialogContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
@@ -147,27 +141,22 @@ export interface DialogContentProps
sheetClass?: string;
}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
DialogContentProps
>(({ position, size, className, sheetClass, showOverlay = true, children, ...props }, ref) => (
<SheetPortal position={position}>
{showOverlay && <SheetOverlay className={sheetClass} />}
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ position, size }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, DialogContentProps>(
({ position, size, className, sheetClass, showOverlay = true, children, ...props }, ref) => (
<SheetPortal position={position}>
{showOverlay && <SheetOverlay className={sheetClass} />}
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ position, size }), className)} {...props}>
{children}
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">
<Trans>Close</Trans>
</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
),
);
SheetContent.displayName = SheetPrimitive.Content.displayName;
@@ -178,10 +167,7 @@ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElemen
SheetHeader.displayName = 'SheetHeader';
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
);
SheetFooter.displayName = 'SheetFooter';
@@ -190,11 +176,7 @@ const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn('text-lg font-semibold text-foreground', className)}
{...props}
/>
<SheetPrimitive.Title ref={ref} className={cn('font-semibold text-foreground text-lg', className)} {...props} />
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
@@ -203,21 +185,9 @@ const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
<SheetPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};
export { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger };
@@ -194,12 +194,9 @@ export class Canvas {
const smoothedPoints = this.smoothSignature(this.points);
let velocity = point.velocityFrom(lastPoint);
velocity =
this.VELOCITY_FILTER_WEIGHT * velocity +
(1 - this.VELOCITY_FILTER_WEIGHT) * this.lastVelocity;
velocity = this.VELOCITY_FILTER_WEIGHT * velocity + (1 - this.VELOCITY_FILTER_WEIGHT) * this.lastVelocity;
const newWidth =
velocity > 0 && this.lastVelocity > 0 ? this.strokeWidth(velocity) : this.minStrokeWidth();
const newWidth = velocity > 0 && this.lastVelocity > 0 ? this.strokeWidth(velocity) : this.minStrokeWidth();
this.drawSmoothSignature(smoothedPoints, newWidth);
+3 -15
View File
@@ -11,13 +11,7 @@ export type PointLike = {
};
const isTouchEvent = (
event:
| ReactMouseEvent
| ReactPointerEvent
| ReactTouchEvent
| MouseEvent
| PointerEvent
| TouchEvent,
event: ReactMouseEvent | ReactPointerEvent | ReactTouchEvent | MouseEvent | PointerEvent | TouchEvent,
): event is TouchEvent | ReactTouchEvent => {
return 'touches' in event;
};
@@ -34,7 +28,7 @@ export class Point implements PointLike {
}
public distanceTo(point: PointLike): number {
return Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));
return Math.sqrt((point.x - this.x) ** 2 + (point.y - this.y) ** 2);
}
public equals(point: PointLike): boolean {
@@ -56,13 +50,7 @@ export class Point implements PointLike {
}
public static fromEvent(
event:
| ReactMouseEvent
| ReactPointerEvent
| ReactTouchEvent
| MouseEvent
| PointerEvent
| TouchEvent,
event: ReactMouseEvent | ReactPointerEvent | ReactTouchEvent | MouseEvent | PointerEvent | TouchEvent,
dpi = 1,
el?: HTMLElement | null,
): Point {
@@ -1,13 +1,6 @@
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
import { Trans } from '@lingui/react/macro';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@documenso/ui/primitives/select';
import { cn } from '../../lib/utils';
export type SignaturePadColorPickerProps = {
@@ -22,7 +15,7 @@ export const SignaturePadColorPicker = ({
className,
}: SignaturePadColorPickerProps) => {
return (
<div className={cn('text-foreground absolute right-2 top-2 filter', className)}>
<div className={cn('absolute top-2 right-2 text-foreground filter', className)}>
<Select defaultValue={selectedColor} onValueChange={(value) => setSelectedColor(value)}>
<SelectTrigger className="h-auto w-auto border-none p-0.5">
<SelectValue placeholder="" />
@@ -30,29 +23,29 @@ export const SignaturePadColorPicker = ({
<SelectContent className="w-[100px]" align="end">
<SelectItem value="black">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-black shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-black shadow-sm" />
<Trans>Black</Trans>
</div>
</SelectItem>
<SelectItem value="red">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[red] shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-[red] shadow-sm" />
<Trans>Red</Trans>
</div>
</SelectItem>
<SelectItem value="blue">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[blue] shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-[blue] shadow-sm" />
<Trans>Blue</Trans>
</div>
</SelectItem>
<SelectItem value="green">
<div className="text-muted-foreground flex items-center text-[0.688rem]">
<div className="border-border mr-1 h-4 w-4 rounded-full border-2 bg-[green] shadow-sm" />
<div className="flex items-center text-[0.688rem] text-muted-foreground">
<div className="mr-1 h-4 w-4 rounded-full border-2 border-border bg-[green] shadow-sm" />
<Trans>Green</Trans>
</div>
</SelectItem>
@@ -1,12 +1,11 @@
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { Dialog, DialogClose, DialogContent, DialogFooter } from '@documenso/ui/primitives/dialog';
import type { MessageDescriptor } from '@lingui/core';
import { Trans, useLingui } from '@lingui/react/macro';
import { motion } from 'framer-motion';
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
import { Dialog, DialogClose, DialogContent, DialogFooter } from '@documenso/ui/primitives/dialog';
import type { HTMLAttributes } from 'react';
import { useState } from 'react';
import { cn } from '../../lib/utils';
import { Button } from '../button';
@@ -139,11 +138,7 @@ export const SignaturePadDialog = ({
setShowSignatureModal(false);
}}
>
{dialogConfirmText ? (
parseMessageDescriptor(i18n._, dialogConfirmText)
) : (
<Trans>Next</Trans>
)}
{dialogConfirmText ? parseMessageDescriptor(i18n._, dialogConfirmText) : <Trans>Next</Trans>}
</Button>
</DialogFooter>
</DialogContent>
@@ -1,16 +1,12 @@
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
import { useMemo, useRef, useState } from 'react';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import { SIGNATURE_CANVAS_DPI, SIGNATURE_MIN_COVERAGE_THRESHOLD } from '@documenso/lib/constants/signatures';
import { Trans } from '@lingui/react/macro';
import { Undo2 } from 'lucide-react';
import type { StrokeOptions } from 'perfect-freehand';
import { getStroke } from 'perfect-freehand';
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
import {
SIGNATURE_CANVAS_DPI,
SIGNATURE_MIN_COVERAGE_THRESHOLD,
} from '@documenso/lib/constants/signatures';
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
import { useMemo, useRef, useState } from 'react';
import { cn } from '../../lib/utils';
import { getSvgPathFromStroke } from './helper';
@@ -34,7 +30,9 @@ const checkSignatureValidity = (element: RefObject<HTMLCanvasElement>) => {
const totalPixels = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] > 0) filledPixels++;
if (data[i + 3] > 0) {
filledPixels++;
}
}
const filledPercentage = filledPixels / totalPixels;
@@ -49,12 +47,7 @@ export type SignaturePadDrawProps = {
onChange: (_signatureDataUrl: string) => void;
};
export const SignaturePadDraw = ({
className,
value,
onChange,
...props
}: SignaturePadDrawProps) => {
export const SignaturePadDraw = ({ className, value, onChange, ...props }: SignaturePadDrawProps) => {
const $el = useRef<HTMLCanvasElement>(null);
const $imageData = useRef<ImageData | null>(null);
@@ -119,16 +112,12 @@ export const SignaturePadDraw = ({
ctx.fillStyle = selectedColor;
lines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)));
ctx.fill(pathData);
});
const pathData = new Path2D(
getSvgPathFromStroke(getStroke([...currentLine, point], perfectFreehandOptions)),
);
const pathData = new Path2D(getSvgPathFromStroke(getStroke([...currentLine, point], perfectFreehandOptions)));
ctx.fill(pathData);
}
}
@@ -163,9 +152,7 @@ export const SignaturePadDraw = ({
ctx.fillStyle = selectedColor;
newLines.forEach((line) => {
const pathData = new Path2D(
getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)),
);
const pathData = new Path2D(getSvgPathFromStroke(getStroke(line, perfectFreehandOptions)));
ctx.fill(pathData);
});
@@ -291,7 +278,7 @@ export const SignaturePadDraw = ({
<SignaturePadColorPicker selectedColor={selectedColor} setSelectedColor={setSelectedColor} />
<div className="absolute bottom-3 right-3 flex gap-2">
<div className="absolute right-3 bottom-3 flex gap-2">
<button
type="button"
className="rounded-full p-0 text-[0.688rem] text-muted-foreground/60 ring-offset-background hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
@@ -303,7 +290,7 @@ export const SignaturePadDraw = ({
{isSignatureValid === false && (
<div className="absolute bottom-4 left-4 flex gap-2">
<span className="text-xs text-destructive">
<span className="text-destructive text-xs">
<Trans>Signature is too small</Trans>
</span>
</div>

Some files were not shown because too many files have changed in this diff Show More