Merge branch 'main' into fix/replace-hardcoded-grays-with-theme-tokens

This commit is contained in:
Ephraim Duncan
2026-08-19 10:21:45 +00:00
committed by GitHub
417 changed files with 30071 additions and 13944 deletions
@@ -1,3 +1,4 @@
import { formatPath } from '@documenso/lib/constants/app';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { cn } from '@documenso/ui/lib/utils';
@@ -23,7 +24,7 @@ export const LanguageSwitcherDialog = ({ open, setOpen }: LanguageSwitcherDialog
formData.append('lang', lang);
await fetch('/api/locale', {
await fetch(formatPath('/api/locale'), {
method: 'post',
body: formData,
});
@@ -2,16 +2,27 @@ import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-c
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 { FieldType, SigningStatus } from '@prisma/client';
import {
CalendarDaysIcon,
CheckSquareIcon,
ChevronDownIcon,
ContactIcon,
DiscIcon,
EyeOffIcon,
HashIcon,
LockIcon,
MailIcon,
TypeIcon,
UserIcon,
} from 'lucide-react';
import { type ElementType, useCallback, useEffect, useState } from 'react';
import { isTemplateRecipientEmailPlaceholder } from '../../../lib/constants/template';
import { extractInitials } from '../../../lib/utils/recipient-formatter';
import { SignatureIcon } from '../../icons/signature';
import { cn } from '../../lib/utils';
import { Avatar, AvatarFallback } from '../../primitives/avatar';
import { Badge } from '../../primitives/badge';
import { FRIENDLY_FIELD_TYPE } from '../../primitives/document-flow/types';
import { PopoverHover } from '../../primitives/popover';
@@ -27,16 +38,18 @@ interface EnvelopeRecipientFieldTooltipProps {
showRecipientColors?: boolean;
}
const getRecipientDisplayText = (recipient: { name: string; email: string }) => {
if (recipient.name && !isTemplateRecipientEmailPlaceholder(recipient.email)) {
return `${recipient.name} (${recipient.email})`;
}
if (recipient.name && isTemplateRecipientEmailPlaceholder(recipient.email)) {
return recipient.name;
}
return recipient.email;
const FIELD_TYPE_ICONS: Record<FieldType, ElementType> = {
[FieldType.SIGNATURE]: SignatureIcon,
[FieldType.FREE_SIGNATURE]: SignatureIcon,
[FieldType.INITIALS]: ContactIcon,
[FieldType.TEXT]: TypeIcon,
[FieldType.DATE]: CalendarDaysIcon,
[FieldType.EMAIL]: MailIcon,
[FieldType.NAME]: UserIcon,
[FieldType.NUMBER]: HashIcon,
[FieldType.RADIO]: DiscIcon,
[FieldType.CHECKBOX]: CheckSquareIcon,
[FieldType.DROPDOWN]: ChevronDownIcon,
};
/**
@@ -50,6 +63,8 @@ export function EnvelopeRecipientFieldTooltip({
}: EnvelopeRecipientFieldTooltipProps) {
const { t } = useLingui();
const FieldIcon = FIELD_TYPE_ICONS[field.type];
const [hideField, setHideField] = useState<boolean>(!showRecipientTooltip);
const [coords, setCoords] = useState({
@@ -138,54 +153,64 @@ export function EnvelopeRecipientFieldTooltip({
</Avatar>
}
contentProps={{
className: 'relative flex mb-4 w-fit flex-col p-4 text-sm',
className: 'flex w-64 flex-col overflow-hidden p-0 text-sm',
sideOffset: 20,
onOpenAutoFocus: (event) => event.preventDefault(),
}}
>
{showFieldStatus && (
<Badge
className="mx-auto mb-1 py-0.5"
variant={
field?.fieldMeta?.readOnly
? 'neutral'
: field.recipient.signingStatus === SigningStatus.SIGNED
? 'default'
: 'secondary'
}
>
{field?.fieldMeta?.readOnly ? (
<>
<LockIcon className="mr-1 h-3 w-3" />
<Trans>Read Only</Trans>
</>
) : field.recipient.signingStatus === SigningStatus.SIGNED ? (
<>
<SignatureIcon className="mr-1 h-3 w-3" />
<Trans>Signed</Trans>
</>
) : (
<>
<ClockIcon className="mr-1 h-3 w-3" />
<Trans>Pending</Trans>
</>
)}
</Badge>
)}
<div className="flex items-center gap-2 p-3">
<FieldIcon className="h-4 w-4 shrink-0 text-muted-foreground" />
<p className="text-center font-semibold">
<span>
<p className="min-w-0 flex-1 truncate font-medium">
<Trans>{t(FRIENDLY_FIELD_TYPE[field.type])} field</Trans>
</span>
</p>
</p>
<p className="mt-1 text-center text-muted-foreground text-xs">{getRecipientDisplayText(field.recipient)}</p>
{showFieldStatus && (
<div className="flex shrink-0 items-center gap-1.5 text-xs">
{field?.fieldMeta?.readOnly ? (
<>
<LockIcon className="h-3 w-3 text-muted-foreground" />
<span className="text-muted-foreground">
<Trans>Read Only</Trans>
</span>
</>
) : field.recipient.signingStatus === SigningStatus.SIGNED ? (
<>
<span className="h-1.5 w-1.5 rounded-full bg-green-500" />
<span className="text-green-600 dark:text-green-400">
<Trans>Signed</Trans>
</span>
</>
) : (
<>
<span className="h-1.5 w-1.5 rounded-full bg-amber-400" />
<span className="text-amber-600 dark:text-amber-400">
<Trans>Pending</Trans>
</span>
</>
)}
</div>
)}
</div>
<button
className="absolute top-0 right-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
onClick={() => setHideField(true)}
title="Hide field"
>
<EyeOffIcon className="h-3 w-3" />
</button>
<div className="flex items-center gap-3 border-border/50 border-t bg-muted/50 px-3 py-2.5">
<div className="min-w-0 flex-1">
<p className="truncate font-medium text-xs">{field.recipient.name || field.recipient.email}</p>
{!isTemplateRecipientEmailPlaceholder(field.recipient.email) && field.recipient.name && (
<p className="truncate text-muted-foreground text-xs">{field.recipient.email}</p>
)}
</div>
<button
type="button"
className="-m-1 shrink-0 rounded-sm p-1 text-muted-foreground hover:bg-background hover:text-foreground"
onClick={() => setHideField(true)}
title={t`Hide field`}
>
<EyeOffIcon className="h-3.5 w-3.5" />
</button>
</div>
</PopoverHover>
</div>
);
+1 -1
View File
@@ -35,7 +35,7 @@ export const SigningCard3D = ({ className, name, signature, signingCelebrationIm
const [trackMouse, setTrackMouse] = useState(false);
const timeoutRef = useRef<number | undefined>();
const timeoutRef = useRef<number | undefined>(undefined);
const cardX = useMotionValue(0);
const cardY = useMotionValue(0);
+8 -35
View File
@@ -18,59 +18,32 @@
"@documenso/tailwind-config": "*",
"@documenso/tsconfig": "*",
"@types/luxon": "^3.7.1",
"@types/react": "18.3.27",
"@types/react-dom": "^18",
"react": "^18",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"react": "^19.2.7",
"typescript": "5.6.2"
},
"dependencies": {
"@documenso/lib": "*",
"@hello-pangea/dnd": "^16.6.0",
"@hello-pangea/dnd": "^18.0.1",
"@hookform/resolvers": "^3",
"@lingui/macro": "^5.6.0",
"@lingui/react": "^5.6.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.8",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toast": "^1.2.15",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@scure/base": "^1.2.6",
"@tanstack/react-table": "^8.21.3",
"class-variance-authority": "^0.7.1",
"clsx": "^1.2.1",
"cmdk": "^0.2.1",
"cmdk": "^1.1.1",
"colord": "^2.9.3",
"framer-motion": "^12.23.24",
"framer-motion": "^12.43.0",
"lucide-react": "^0.554.0",
"luxon": "^3.7.2",
"pdfjs-dist": "5.4.296",
"perfect-freehand": "^1.2.2",
"react": "^18",
"react": "^19.2.7",
"react-colorful": "^5.6.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18",
"react-dom": "^19.2.7",
"react-hook-form": "^7.66.1",
"react-rnd": "^10.5.2",
"remeda": "^2.32.0",
+1 -1
View File
@@ -41,7 +41,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
'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',
'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 focus:outline-none sm:rounded-lg md:w-full',
className,
)}
{...props}
+1 -1
View File
@@ -68,7 +68,7 @@ const AvatarWithText = ({
<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>
{secondaryText && <span className="truncate text-muted-foreground text-xs">{secondaryText}</span>}
</div>
{rightSideComponent}
+3 -1
View File
@@ -145,7 +145,9 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
'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',
// cmdk 1.x always renders data-disabled="true|false", so the variant must
// check the value (bare data-[disabled] matches attribute presence).
'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=true]:pointer-events-none data-[disabled=true]:opacity-50',
className,
)}
{...props}
+1 -1
View File
@@ -61,7 +61,7 @@ const DialogContent = React.forwardRef<
<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',
'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 focus:outline-none sm:max-w-lg sm:rounded-lg',
{
'rounded-b-xl': position === 'start',
'rounded-t-xl': position === 'end',
@@ -577,7 +577,7 @@ export const AddFieldsFormPartial = ({
{selectedField && (
<div
className={cn(
'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',
'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',
selectedSignerStyles?.base,
{
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
@@ -6,7 +6,13 @@ import { useSession } from '@documenso/lib/client-only/providers/session';
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
import type { TRecipientLite } from '@documenso/lib/types/recipient';
import { nanoid } from '@documenso/lib/universal/id';
import { canRecipientBeModified as utilCanRecipientBeModified } from '@documenso/lib/utils/recipients';
import {
isAssistantLastSigner,
isCcRecipient,
normalizeRecipientSigningOrders,
sortRecipientsForSigningOrder,
canRecipientBeModified as utilCanRecipientBeModified,
} from '@documenso/lib/utils/recipients';
import { trpc } from '@documenso/trpc/react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import { RecipientActionAuthSelect } from '@documenso/ui/components/recipient/recipient-action-auth-select';
@@ -24,7 +30,6 @@ 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 type { RecipientAutoCompleteOption } from '../../components/recipient/recipient-autocomplete-input';
@@ -118,18 +123,18 @@ export const AddSignersFormPartial = ({
defaultValues: {
signers:
recipients.length > 0
? sortBy(
recipients.map((recipient, index) => ({
nativeId: recipient.id,
formId: String(recipient.id),
name: recipient.name,
email: recipient.email,
role: recipient.role,
signingOrder: recipient.signingOrder ?? index + 1,
actionAuth: ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
})),
[prop('signingOrder'), 'asc'],
[prop('nativeId'), 'asc'],
? normalizeRecipientSigningOrders(
sortRecipientsForSigningOrder(
recipients.map((recipient, index) => ({
nativeId: recipient.id,
formId: String(recipient.id),
name: recipient.name,
email: recipient.email,
role: recipient.role,
signingOrder: isCcRecipient(recipient) ? undefined : (recipient.signingOrder ?? index + 1),
actionAuth: ZRecipientAuthOptionsSchema.parse(recipient.authOptions)?.actionAuth ?? undefined,
})),
),
)
: defaultRecipients,
signingOrder: signingOrder || DocumentSigningOrder.PARALLEL,
@@ -168,18 +173,14 @@ export const AddSignersFormPartial = ({
}, [watchedSigners]);
const normalizeSigningOrders = (signers: typeof watchedSigners) => {
return signers
.sort((a, b) => (a.signingOrder ?? 0) - (b.signingOrder ?? 0))
.map((signer, index) => ({ ...signer, signingOrder: index + 1 }));
return normalizeRecipientSigningOrders(signers, (signer) => canRecipientBeModified(signer.nativeId));
};
const activeRecipientCount = watchedSigners.filter((signer) => !isCcRecipient(signer)).length;
const onFormSubmit = form.handleSubmit(onSubmit);
const {
append: appendSigner,
fields: signers,
remove: removeSigner,
} = useFieldArray({
const { fields: signers, remove: removeSigner } = useFieldArray({
control,
name: 'signers',
});
@@ -258,14 +259,31 @@ export const AddSignersFormPartial = ({
return utilCanRecipientBeModified(recipient, fields);
};
const appendNormalizedSigner = (signer: (typeof watchedSigners)[number], shouldFocus = false) => {
const updatedSigners = normalizeSigningOrders([...form.getValues('signers'), signer]);
form.setValue('signers', updatedSigners, {
shouldValidate: true,
shouldDirty: true,
});
if (shouldFocus) {
const signerIndex = updatedSigners.findIndex((updatedSigner) => updatedSigner.formId === signer.formId);
if (signerIndex !== -1) {
requestAnimationFrame(() => form.setFocus(`signers.${signerIndex}.email`));
}
}
};
const onAddSigner = () => {
appendSigner({
appendNormalizedSigner({
formId: nanoid(12),
name: '',
email: '',
role: RecipientRole.SIGNER,
actionAuth: [],
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
signingOrder: activeRecipientCount + 1,
});
};
@@ -310,18 +328,16 @@ export const AddSignersFormPartial = ({
form.setFocus(`signers.${emptySignerIndex}.email`);
} else {
appendSigner(
appendNormalizedSigner(
{
formId: nanoid(12),
name: user?.name ?? '',
email: user?.email ?? '',
role: RecipientRole.SIGNER,
actionAuth: [],
signingOrder: signers.length > 0 ? (signers[signers.length - 1]?.signingOrder ?? 0) + 1 : 1,
},
{
shouldFocus: true,
signingOrder: activeRecipientCount + 1,
},
true,
);
void form.trigger('signers');
@@ -356,18 +372,14 @@ export const AddSignersFormPartial = ({
items.splice(insertIndex, 0, reorderedSigner);
const updatedSigners = items.map((signer, index) => ({
...signer,
signingOrder: !canRecipientBeModified(signer.nativeId) ? signer.signingOrder : index + 1,
}));
const updatedSigners = normalizeSigningOrders(items);
form.setValue('signers', updatedSigners, {
shouldValidate: true,
shouldDirty: true,
});
const lastSigner = updatedSigners[updatedSigners.length - 1];
if (lastSigner.role === RecipientRole.ASSISTANT) {
if (isAssistantLastSigner(updatedSigners)) {
toast({
title: _(msg`Warning: Assistant as last signer`),
description: _(
@@ -402,18 +414,19 @@ export const AddSignersFormPartial = ({
return;
}
const updatedSigners = currentSigners.map((signer, idx) => ({
...signer,
role: idx === index ? role : signer.role,
signingOrder: !canRecipientBeModified(signer.nativeId) ? signer.signingOrder : idx + 1,
}));
const updatedSigners = normalizeSigningOrders(
currentSigners.map((signer, idx) => ({
...signer,
role: idx === index ? role : signer.role,
})),
);
form.setValue('signers', updatedSigners, {
shouldValidate: true,
shouldDirty: true,
});
if (role === RecipientRole.ASSISTANT && index === updatedSigners.length - 1) {
if (role === RecipientRole.ASSISTANT && isAssistantLastSigner(updatedSigners)) {
toast({
title: _(msg`Warning: Assistant as last signer`),
description: _(
@@ -440,22 +453,30 @@ export const AddSignersFormPartial = ({
const currentSigners = form.getValues('signers');
const signer = currentSigners[index];
// Remove signer from current position and insert at new position
const remainingSigners = currentSigners.filter((_, idx) => idx !== index);
const newPosition = Math.min(Math.max(0, newOrder - 1), currentSigners.length - 1);
remainingSigners.splice(newPosition, 0, signer);
if (isCcRecipient(signer)) {
return;
}
const updatedSigners = remainingSigners.map((s, idx) => ({
...s,
signingOrder: !canRecipientBeModified(s.nativeId) ? s.signingOrder : idx + 1,
}));
const nonCcSigners = currentSigners.filter((s) => !isCcRecipient(s));
const ccSigners = currentSigners.filter((s) => isCcRecipient(s));
const currentSigningOrderIndex = nonCcSigners.findIndex((s) => s.formId === signer.formId);
if (currentSigningOrderIndex === -1) {
return;
}
const [reorderedSigner] = nonCcSigners.splice(currentSigningOrderIndex, 1);
const newPosition = Math.min(Math.max(0, newOrder - 1), nonCcSigners.length);
nonCcSigners.splice(newPosition, 0, reorderedSigner);
const updatedSigners = normalizeSigningOrders([...nonCcSigners, ...ccSigners]);
form.setValue('signers', updatedSigners, {
shouldValidate: true,
shouldDirty: true,
});
if (signer.role === RecipientRole.ASSISTANT && newPosition === remainingSigners.length - 1) {
if (signer.role === RecipientRole.ASSISTANT && isAssistantLastSigner(updatedSigners)) {
toast({
title: _(msg`Warning: Assistant as last signer`),
description: _(
@@ -471,10 +492,12 @@ export const AddSignersFormPartial = ({
setShowSigningOrderConfirmation(false);
const currentSigners = form.getValues('signers');
const updatedSigners = currentSigners.map((signer) => ({
...signer,
role: signer.role === RecipientRole.ASSISTANT ? RecipientRole.SIGNER : signer.role,
}));
const updatedSigners = normalizeSigningOrders(
currentSigners.map((signer) => ({
...signer,
role: signer.role === RecipientRole.ASSISTANT ? RecipientRole.SIGNER : signer.role,
})),
);
form.setValue('signers', updatedSigners, {
shouldValidate: true,
@@ -642,6 +665,7 @@ export const AddSignersFormPartial = ({
isDragDisabled={
!isSigningOrderSequential ||
isSubmitting ||
isCcRecipient(signer) ||
!canRecipientBeModified(signer.nativeId) ||
!signer.signingOrder
}
@@ -663,7 +687,9 @@ export const AddSignersFormPartial = ({
'grid-cols-12 pr-3': isSigningOrderSequential,
})}
>
{isSigningOrderSequential && (
{isSigningOrderSequential && isCcRecipient(signer) && <div className="col-span-2" />}
{isSigningOrderSequential && !isCcRecipient(signer) && (
<FormField
control={form.control}
name={`signers.${index}.signingOrder`}
@@ -679,7 +705,7 @@ export const AddSignersFormPartial = ({
<FormControl>
<Input
type="number"
max={signers.length}
max={activeRecipientCount}
data-testid="signing-order-input"
className={cn(
'w-full text-center',
@@ -103,15 +103,10 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
) {
return (
<div className="flex flex-col gap-y-2 py-0.5">
<RadioGroup className="gap-y-1">
<RadioGroup value={field.customText ?? ''} className="gap-y-1">
{field.fieldMeta.values.map((item, index) => (
<div key={index} className="flex items-center">
<RadioGroupItem
className="pointer-events-none h-3 w-3"
value={item.value}
id={`option-${index}`}
checked={item.value === field.customText}
/>
<RadioGroupItem className="pointer-events-none h-3 w-3" value={item.value} id={`option-${index}`} />
{item.value && (
<Label htmlFor={`option-${index}`} className="ml-1.5 font-normal text-foreground text-xs">
{item.value}
@@ -89,7 +89,7 @@ export const DropdownFieldAdvancedSettings = ({
}, [fieldState.defaultValue]);
return (
<div className="flex flex-col gap-4 text-dark">
<div className="flex flex-col gap-4">
<div>
<Label>
<Trans>Select default option</Trans>
@@ -1,9 +1,7 @@
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 { getAllowedUploadMimeTypes } from '@documenso/lib/constants/document-conversion';
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';
@@ -45,12 +43,8 @@ export const DocumentUploadButton = ({
}: DocumentUploadButtonProps) => {
const { _ } = useLingui();
const { organisations } = useSession();
const organisation = useCurrentOrganisation();
const isPersonalLayoutMode = isPersonalLayout(organisations);
const { getRootProps, getInputProps } = useDropzone({
accept: getAllowedUploadMimeTypes(),
multiple: internalVersion === '2',
@@ -76,7 +70,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={`/o/${organisation.url}/settings/billing`}>
<Trans>Upgrade</Trans>
</Link>
</Button>
+40 -1
View File
@@ -35,4 +35,43 @@ const RadioGroupItem = React.forwardRef<
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;
export { RadioGroup, RadioGroupItem };
/**
* A segmented-control style radio group where each item renders as a small
* toggle button rather than a radio circle.
*/
const RadioGroupSegmented = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn('inline-flex items-center gap-0.5 rounded-md bg-muted p-0.5', className)}
{...props}
ref={ref}
/>
);
});
RadioGroupSegmented.displayName = 'RadioGroupSegmented';
const RadioGroupSegmentedItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, children, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
'rounded-sm px-2 py-0.5 font-medium text-muted-foreground text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-background data-[state=checked]:text-foreground data-[state=checked]:shadow-sm',
className,
)}
{...props}
>
{children}
</RadioGroupPrimitive.Item>
);
});
RadioGroupSegmentedItem.displayName = 'RadioGroupSegmentedItem';
export { RadioGroup, RadioGroupItem, RadioGroupSegmented, RadioGroupSegmentedItem };
@@ -1,5 +1,6 @@
import type { RecipientRole } from '@prisma/client';
import { BadgeCheck, Copy, Eye, PencilLine, User } from 'lucide-react';
import type { JSX } from 'react';
export const ROLE_ICONS: Record<RecipientRole, JSX.Element> = {
SIGNER: <PencilLine className="h-4 w-4" />,
+84 -81
View File
@@ -49,90 +49,93 @@ 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',
const sheetVariants = cva(
'fixed z-[61] scale-100 gap-4 border bg-background p-6 opacity-100 shadow-lg focus:outline-none',
{
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: '',
},
},
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',
},
},
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>,
@@ -13,7 +13,7 @@ import { getSvgPathFromStroke } from './helper';
import { Point } from './point';
import { SignaturePadColorPicker } from './signature-pad-color-picker';
const checkSignatureValidity = (element: RefObject<HTMLCanvasElement>) => {
const checkSignatureValidity = (element: RefObject<HTMLCanvasElement | null>) => {
if (!element.current) {
return false;
}
@@ -520,7 +520,7 @@ export const AddTemplateFieldsFormPartial = ({
{selectedField && (
<div
className={cn(
'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',
'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',
selectedSignerStyles?.base,
{
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
@@ -603,9 +603,7 @@ export const AddTemplateFieldsFormPartial = ({
<span className="flex-1 truncate text-left">{selectedSigner?.name}</span>
)}
{!selectedSigner?.email && (
<span className="gradie flex-1 truncate text-left">No recipient selected</span>
)}
{!selectedSigner?.email && <span className="flex-1 truncate text-left">No recipient selected</span>}
<ChevronsUpDown className="ml-2 h-4 w-4" />
</Button>
+33 -1
View File
@@ -174,7 +174,7 @@
--accent-foreground: 95.08 71.08% 67.45%;
--destructive: 0 87% 62%;
--destructive-foreground: 0 87% 19%;
--destructive-foreground: 0 0% 98%;
--ring: 95.08 71.08% 67.45%;
@@ -243,6 +243,38 @@
display: none;
}
/*
* Scrollbar hidden until the element is hovered, then a thin thumb is revealed.
* The track keeps a constant width so revealing the thumb doesn't shift layout;
* the thumb colour transitions in on WebKit (Firefox snaps, which is fine).
*/
.hover-scrollbar {
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
.hover-scrollbar:hover {
scrollbar-color: hsl(var(--muted-foreground) / 0.4) transparent;
}
.hover-scrollbar::-webkit-scrollbar {
width: 8px;
height: 8px;
background: transparent;
}
.hover-scrollbar::-webkit-scrollbar-thumb {
background-color: transparent;
border: 2px solid transparent;
background-clip: padding-box;
border-radius: 9999px;
transition: background-color 200ms ease;
}
.hover-scrollbar:hover::-webkit-scrollbar-thumb {
background-color: hsl(var(--muted-foreground) / 0.4);
}
/* .custom-scrollbar::-webkit-scrollbar-track {
border-radius: 10px;
}