mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 17:04:12 +10:00
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:
@@ -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 };
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,4 +6,4 @@ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
|
||||
|
||||
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
|
||||
|
||||
export { Collapsible, CollapsibleTrigger, CollapsibleContent };
|
||||
export { Collapsible, CollapsibleContent, CollapsibleTrigger };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
+23
-41
@@ -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>
|
||||
|
||||
|
||||
+15
-28
@@ -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>
|
||||
|
||||
|
||||
+5
-9
@@ -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>
|
||||
|
||||
|
||||
+16
-30
@@ -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)}
|
||||
|
||||
+11
-13
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>
|
||||
));
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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‘t want to filter the options. You can still override it.
|
||||
shouldFilter={commandProps?.shouldFilter !== undefined ? commandProps.shouldFilter : !onSearch} // When onSearch is provided, we don‘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>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
));
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
@@ -11,12 +10,7 @@ export type SignaturePadTypeProps = {
|
||||
onChange: (_value: string) => void;
|
||||
};
|
||||
|
||||
export const SignaturePadType = ({
|
||||
className,
|
||||
value,
|
||||
defaultValue,
|
||||
onChange,
|
||||
}: SignaturePadTypeProps) => {
|
||||
export const SignaturePadType = ({ className, value, defaultValue, onChange }: SignaturePadTypeProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const $isDirty = useRef(false);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { motion } from 'framer-motion';
|
||||
import { UploadCloudIcon } from 'lucide-react';
|
||||
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
@@ -71,12 +69,7 @@ export type SignaturePadUploadProps = {
|
||||
onChange: (_signatureDataUrl: string) => void;
|
||||
};
|
||||
|
||||
export const SignaturePadUpload = ({
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
...props
|
||||
}: SignaturePadUploadProps) => {
|
||||
export const SignaturePadUpload = ({ className, value, onChange, ...props }: SignaturePadUploadProps) => {
|
||||
const $el = useRef<HTMLCanvasElement>(null);
|
||||
const $imageData = useRef<ImageData | null>(null);
|
||||
const $fileInput = useRef<HTMLInputElement>(null);
|
||||
@@ -85,10 +78,14 @@ export const SignaturePadUpload = ({
|
||||
try {
|
||||
const img = await loadImage(event.target.files?.[0]);
|
||||
|
||||
if (!$el.current) return;
|
||||
if (!$el.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = $el.current.getContext('2d');
|
||||
if (!ctx) return;
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
$imageData.current = loadImageOntoCanvas(img, $el.current, ctx);
|
||||
onChange?.($el.current.toDataURL());
|
||||
@@ -133,13 +130,7 @@ export const SignaturePadUpload = ({
|
||||
{...props}
|
||||
/>
|
||||
|
||||
<input
|
||||
ref={$fileInput}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
<input ref={$fileInput} type="file" accept="image/*" className="hidden" onChange={handleImageUpload} />
|
||||
|
||||
<motion.button
|
||||
className="absolute inset-0 flex h-full w-full items-center justify-center"
|
||||
@@ -150,10 +141,10 @@ export const SignaturePadUpload = ({
|
||||
>
|
||||
{!value && (
|
||||
<motion.div>
|
||||
<div className="text-muted-foreground flex flex-col items-center justify-center">
|
||||
<div className="flex flex-col items-center justify-center text-muted-foreground">
|
||||
<div className="flex flex-col items-center">
|
||||
<UploadCloudIcon className="h-8 w-8" />
|
||||
<span className="text-lg font-semibold">
|
||||
<span className="font-semibold text-lg">
|
||||
<Trans>Upload Signature</Trans>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isBase64Image } from '@documenso/lib/constants/signatures';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { KeyboardIcon, UploadCloudIcon } from 'lucide-react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { DocumentSignatureType } from '@documenso/lib/constants/document';
|
||||
import { isBase64Image } from '@documenso/lib/constants/signatures';
|
||||
|
||||
import { SignatureIcon } from '../../icons/signature';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { SignaturePadDraw } from './signature-pad-draw';
|
||||
@@ -172,22 +171,14 @@ export const SignaturePad = ({
|
||||
value="draw"
|
||||
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
|
||||
>
|
||||
<SignaturePadDraw
|
||||
className="h-full w-full"
|
||||
onChange={onDrawSignatureChange}
|
||||
value={drawSignature}
|
||||
/>
|
||||
<SignaturePadDraw className="h-full w-full" onChange={onDrawSignatureChange} value={drawSignature} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="text"
|
||||
className="relative flex aspect-signature-pad items-center justify-center rounded-md border border-border bg-neutral-50 text-center dark:bg-background"
|
||||
>
|
||||
<SignaturePadType
|
||||
value={typedSignature}
|
||||
defaultValue={fullName}
|
||||
onChange={onTypedSignatureChange}
|
||||
/>
|
||||
<SignaturePadType value={typedSignature} defaultValue={fullName} onChange={onTypedSignatureChange} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { isBase64Image, SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { SIGNATURE_CANVAS_DPI, isBase64Image } from '@documenso/lib/constants/signatures';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
export type SignatureRenderProps = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
@@ -26,14 +25,7 @@ function useTabs() {
|
||||
return context;
|
||||
}
|
||||
|
||||
export function Tabs({
|
||||
defaultValue,
|
||||
value,
|
||||
onValueChange,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: TabsProps) {
|
||||
export function Tabs({ defaultValue, value, onValueChange, children, className, ...props }: TabsProps) {
|
||||
const [tabValue, setTabValue] = React.useState(defaultValue || '');
|
||||
|
||||
const handleValueChange = React.useCallback(
|
||||
@@ -67,11 +59,7 @@ interface TabsListProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
|
||||
export function TabsList({ children, className, ...props }: TabsListProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('border-border flex flex-wrap border-b', className)}
|
||||
role="tabslist"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn('flex flex-wrap border-border border-b', className)} role="tabslist" {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -95,8 +83,8 @@ export function TabsTrigger({ value, icon, children, className, ...props }: Tabs
|
||||
data-state={isSelected ? 'active' : 'inactive'}
|
||||
onClick={() => onValueChange(value)}
|
||||
className={cn(
|
||||
'relative flex items-center px-4 py-3 text-sm font-medium transition-all',
|
||||
'focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
||||
'relative flex items-center px-4 py-3 font-medium text-sm transition-all',
|
||||
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
isSelected ? 'text-foreground' : 'text-muted-foreground hover:text-foreground',
|
||||
className,
|
||||
)}
|
||||
@@ -107,7 +95,7 @@ export function TabsTrigger({ value, icon, children, className, ...props }: Tabs
|
||||
{isSelected && (
|
||||
<motion.div
|
||||
layoutId="activeTabIndicator"
|
||||
className="bg-foreground/40 absolute bottom-0 left-0 h-0.5 w-full rounded-full"
|
||||
className="absolute bottom-0 left-0 h-0.5 w-full rounded-full bg-foreground/40"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{
|
||||
@@ -135,12 +123,7 @@ export function TabsContent({ value, children, className, ...props }: TabsConten
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
data-state={isSelected ? 'active' : 'inactive'}
|
||||
className={cn('mt-4', className)}
|
||||
{...props}
|
||||
>
|
||||
<div role="tabpanel" data-state={isSelected ? 'active' : 'inactive'} className={cn('mt-4', className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn('bg-muted animate-pulse rounded-md', className)} {...props} />;
|
||||
return <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Skeleton };
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@@ -13,10 +12,10 @@ const Slider = React.forwardRef<
|
||||
className={cn('relative flex w-full touch-none select-none items-center', className)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="bg-secondary relative h-2 w-full grow overflow-hidden rounded-full">
|
||||
<SliderPrimitive.Range className="bg-primary absolute h-full" />
|
||||
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
|
||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="border-primary bg-background ring-offset-background focus-visible:ring-ring block h-5 w-5 rounded-full border-2 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
));
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { Loader } from 'lucide-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@@ -24,11 +23,9 @@ export interface SpinnerProps extends Omit<React.ComponentPropsWithoutRef<typeof
|
||||
size?: SpinnerSize;
|
||||
}
|
||||
|
||||
const Spinner = React.forwardRef<SVGSVGElement, SpinnerProps>(
|
||||
({ className, size = 'default', ...props }, ref) => {
|
||||
return <Loader ref={ref} className={cn(spinnerVariants({ size }), className)} {...props} />;
|
||||
},
|
||||
);
|
||||
const Spinner = React.forwardRef<SVGSVGElement, SpinnerProps>(({ className, size = 'default', ...props }, ref) => {
|
||||
return <Loader ref={ref} className={cn(spinnerVariants({ size }), className)} {...props} />;
|
||||
});
|
||||
|
||||
Spinner.displayName = 'Spinner';
|
||||
|
||||
@@ -36,19 +33,13 @@ export interface SpinnerBoxProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
spinnerProps?: SpinnerProps;
|
||||
}
|
||||
|
||||
const SpinnerBox = React.forwardRef<HTMLDivElement, SpinnerBoxProps>(
|
||||
({ className, spinnerProps, ...props }, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex items-center justify-center rounded-lg', className)}
|
||||
{...props}
|
||||
>
|
||||
<Spinner {...spinnerProps} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
const SpinnerBox = React.forwardRef<HTMLDivElement, SpinnerBoxProps>(({ className, spinnerProps, ...props }, ref) => {
|
||||
return (
|
||||
<div ref={ref} className={cn('flex items-center justify-center rounded-lg', className)} {...props}>
|
||||
<Spinner {...spinnerProps} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
SpinnerBox.displayName = 'SpinnerBox';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
|
||||
type StepContextValue = {
|
||||
isCompleting: boolean;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@@ -10,7 +9,7 @@ const Switch = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SwitchPrimitives.Root
|
||||
className={cn(
|
||||
'focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -18,7 +17,7 @@ const Switch = React.forwardRef<
|
||||
>
|
||||
<SwitchPrimitives.Thumb
|
||||
className={cn(
|
||||
'bg-background pointer-events-none block h-5 w-5 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
|
||||
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitives.Root>
|
||||
|
||||
@@ -15,34 +15,25 @@ const Table = React.forwardRef<
|
||||
|
||||
Table.displayName = 'Table';
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
|
||||
));
|
||||
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />,
|
||||
);
|
||||
|
||||
TableHeader.displayName = 'TableHeader';
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
|
||||
));
|
||||
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
|
||||
),
|
||||
);
|
||||
|
||||
TableBody.displayName = 'TableBody';
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn('bg-primary text-primary-foreground font-medium', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tfoot ref={ref} className={cn('bg-primary font-medium text-primary-foreground', className)} {...props} />
|
||||
),
|
||||
);
|
||||
|
||||
TableFooter.displayName = 'TableFooter';
|
||||
|
||||
@@ -50,10 +41,7 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
|
||||
({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
|
||||
className,
|
||||
)}
|
||||
className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
@@ -61,19 +49,18 @@ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTML
|
||||
|
||||
TableRow.displayName = 'TableRow';
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-muted-foreground h-12 px-4 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
);
|
||||
|
||||
TableHead.displayName = 'TableHead';
|
||||
|
||||
@@ -85,24 +72,19 @@ const TableCell = React.forwardRef<
|
||||
>(({ className, truncate = true, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'p-4 align-middle [&:has([role=checkbox])]:pr-0',
|
||||
truncate && 'truncate',
|
||||
className,
|
||||
)}
|
||||
className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', truncate && 'truncate', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
TableCell.displayName = 'TableCell';
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption ref={ref} className={cn('text-muted-foreground mt-4 text-sm', className)} {...props} />
|
||||
));
|
||||
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<caption ref={ref} className={cn('mt-4 text-muted-foreground text-sm', className)} {...props} />
|
||||
),
|
||||
);
|
||||
|
||||
TableCaption.displayName = 'TableCaption';
|
||||
|
||||
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
|
||||
export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@@ -13,7 +12,7 @@ const TabsList = React.forwardRef<
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'bg-muted text-muted-foreground inline-flex h-10 items-center justify-center rounded-md p-1',
|
||||
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -29,7 +28,7 @@ const TabsTrigger = React.forwardRef<
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 font-medium text-sm ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -45,7 +44,7 @@ const TabsContent = React.forwardRef<
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'ring-offset-background focus-visible:ring-ring mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
||||
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -54,4 +53,4 @@ const TabsContent = React.forwardRef<
|
||||
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
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 { getPdfPagesCount, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
|
||||
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';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '@documenso/ui/primitives/command';
|
||||
import {
|
||||
DocumentFlowFormContainerActions,
|
||||
DocumentFlowFormContainerContent,
|
||||
DocumentFlowFormContainerFooter,
|
||||
DocumentFlowFormContainerHeader,
|
||||
DocumentFlowFormContainerStep,
|
||||
} from '@documenso/ui/primitives/document-flow/document-flow-root';
|
||||
import { FieldItem } from '@documenso/ui/primitives/document-flow/field-item';
|
||||
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
|
||||
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
@@ -18,55 +43,16 @@ import {
|
||||
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 { 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 { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
|
||||
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';
|
||||
import { parseMessageDescriptor } from '@documenso/lib/utils/i18n';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Card, CardContent } from '@documenso/ui/primitives/card';
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
} from '@documenso/ui/primitives/command';
|
||||
import {
|
||||
DocumentFlowFormContainerActions,
|
||||
DocumentFlowFormContainerContent,
|
||||
DocumentFlowFormContainerFooter,
|
||||
DocumentFlowFormContainerHeader,
|
||||
DocumentFlowFormContainerStep,
|
||||
} from '@documenso/ui/primitives/document-flow/document-flow-root';
|
||||
import { FieldItem } from '@documenso/ui/primitives/document-flow/field-item';
|
||||
import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types';
|
||||
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
import { getRecipientColorStyles } from '../../lib/recipient-colors';
|
||||
import type { FieldFormType } from '../document-flow/add-fields';
|
||||
import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings';
|
||||
import { Form } from '../form/form';
|
||||
import { useStep } from '../stepper';
|
||||
import {
|
||||
type TAddTemplateFieldsFormSchema,
|
||||
ZAddTemplateFieldsFormSchema,
|
||||
} from './add-template-fields.types';
|
||||
import { type TAddTemplateFieldsFormSchema, ZAddTemplateFieldsFormSchema } from './add-template-fields.types';
|
||||
|
||||
const MIN_HEIGHT_PX = 12;
|
||||
const MIN_WIDTH_PX = 36;
|
||||
@@ -99,12 +85,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] = useState(false);
|
||||
const [currentField, setCurrentField] = useState<FieldFormType>();
|
||||
const [activeFieldId, setActiveFieldId] = useState<string | null>(null);
|
||||
const [lastActiveField, setLastActiveField] = useState<
|
||||
TAddTemplateFieldsFormSchema['fields'][0] | null
|
||||
>(null);
|
||||
const [fieldClipboard, setFieldClipboard] = useState<
|
||||
TAddTemplateFieldsFormSchema['fields'][0] | null
|
||||
>(null);
|
||||
const [lastActiveField, setLastActiveField] = useState<TAddTemplateFieldsFormSchema['fields'][0] | null>(null);
|
||||
const [fieldClipboard, setFieldClipboard] = useState<TAddTemplateFieldsFormSchema['fields'][0] | null>(null);
|
||||
|
||||
const form = useForm<TAddTemplateFieldsFormSchema>({
|
||||
defaultValues: {
|
||||
@@ -118,10 +100,8 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
pageWidth: Number(field.width),
|
||||
pageHeight: Number(field.height),
|
||||
recipientId: field.recipientId ?? -1,
|
||||
signerEmail:
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
|
||||
signerToken:
|
||||
recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
||||
signerEmail: recipients.find((recipient) => recipient.id === field.recipientId)?.email ?? '',
|
||||
signerToken: recipients.find((recipient) => recipient.id === field.recipientId)?.token ?? '',
|
||||
fieldMeta: field.fieldMeta ? ZFieldMetaSchema.parse(field.fieldMeta) : undefined,
|
||||
})),
|
||||
},
|
||||
@@ -223,15 +203,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
append,
|
||||
lastActiveField,
|
||||
selectedSigner?.email,
|
||||
selectedSigner?.id,
|
||||
selectedSigner?.token,
|
||||
toast,
|
||||
handleAutoSave,
|
||||
],
|
||||
[append, lastActiveField, selectedSigner?.email, selectedSigner?.id, selectedSigner?.token, toast, handleAutoSave],
|
||||
);
|
||||
|
||||
const onFieldPaste = useCallback(
|
||||
@@ -255,14 +227,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
void handleAutoSave();
|
||||
}
|
||||
},
|
||||
[
|
||||
append,
|
||||
fieldClipboard,
|
||||
selectedSigner?.email,
|
||||
selectedSigner?.id,
|
||||
selectedSigner?.token,
|
||||
handleAutoSave,
|
||||
],
|
||||
[append, fieldClipboard, selectedSigner?.email, selectedSigner?.id, selectedSigner?.token, handleAutoSave],
|
||||
);
|
||||
|
||||
useHotkeys(['ctrl+c', 'meta+c'], (evt) => onFieldCopy(evt));
|
||||
@@ -298,8 +263,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
[localFields],
|
||||
);
|
||||
|
||||
const hasErrors =
|
||||
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
||||
const hasErrors = emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
||||
|
||||
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
|
||||
const [coords, setCoords] = useState({
|
||||
@@ -315,12 +279,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
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({
|
||||
@@ -341,12 +300,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
|
||||
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;
|
||||
@@ -406,12 +360,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
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,
|
||||
@@ -509,23 +458,18 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
|
||||
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]);
|
||||
|
||||
const recipientsByRoleToDisplay = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return (Object.entries(recipientsByRole) as [RecipientRole, TRecipientLite[]][]).filter(
|
||||
([role]) =>
|
||||
role !== RecipientRole.CC &&
|
||||
role !== RecipientRole.VIEWER &&
|
||||
role !== RecipientRole.ASSISTANT,
|
||||
([role]) => role !== RecipientRole.CC && role !== RecipientRole.VIEWER && role !== RecipientRole.ASSISTANT,
|
||||
);
|
||||
}, [recipientsByRole]);
|
||||
|
||||
@@ -558,10 +502,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{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}
|
||||
@@ -573,16 +514,13 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<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,
|
||||
@@ -651,26 +589,22 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
className={cn(
|
||||
'mb-12 mt-2 justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
|
||||
'mt-2 mb-12 justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
|
||||
selectedSignerStyles?.comboBoxTrigger,
|
||||
)}
|
||||
>
|
||||
{selectedSigner?.email &&
|
||||
!isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
|
||||
<span className="flex-1 truncate text-left">
|
||||
{selectedSigner?.name} ({selectedSigner?.email})
|
||||
</span>
|
||||
)}
|
||||
{selectedSigner?.email && !isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
|
||||
<span className="flex-1 truncate text-left">
|
||||
{selectedSigner?.name} ({selectedSigner?.email})
|
||||
</span>
|
||||
)}
|
||||
|
||||
{selectedSigner?.email &&
|
||||
isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
|
||||
<span className="flex-1 truncate text-left">{selectedSigner?.name}</span>
|
||||
)}
|
||||
{selectedSigner?.email && isTemplateRecipientEmailPlaceholder(selectedSigner.email) && (
|
||||
<span className="flex-1 truncate text-left">{selectedSigner?.name}</span>
|
||||
)}
|
||||
|
||||
{!selectedSigner?.email && (
|
||||
<span className="gradie flex-1 truncate text-left">
|
||||
No recipient selected
|
||||
</span>
|
||||
<span className="gradie flex-1 truncate text-left">No recipient selected</span>
|
||||
)}
|
||||
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4" />
|
||||
@@ -690,14 +624,14 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{/* Note: This is duplicated in `add-fields.tsx` */}
|
||||
{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"
|
||||
className="px-4 pt-2.5 pb-4 text-center text-muted-foreground/80 text-xs"
|
||||
>
|
||||
<Trans>No recipients with this role</Trans>
|
||||
</div>
|
||||
@@ -708,9 +642,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
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,
|
||||
)}
|
||||
onSelect={() => {
|
||||
setSelectedSigner(recipient);
|
||||
@@ -722,22 +654,19 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
'text-foreground/80': recipient === selectedSigner,
|
||||
})}
|
||||
>
|
||||
{recipient.name &&
|
||||
!isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={`${recipient.name} (${recipient.email})`}>
|
||||
{recipient.name} ({recipient.email})
|
||||
</span>
|
||||
)}
|
||||
{recipient.name && !isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={`${recipient.name} (${recipient.email})`}>
|
||||
{recipient.name} ({recipient.email})
|
||||
</span>
|
||||
)}
|
||||
|
||||
{recipient.name &&
|
||||
isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={recipient.name}>{recipient.name}</span>
|
||||
)}
|
||||
{recipient.name && isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={recipient.name}>{recipient.name}</span>
|
||||
)}
|
||||
|
||||
{!recipient.name &&
|
||||
!isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={recipient.email}>{recipient.email}</span>
|
||||
)}
|
||||
{!recipient.name && !isTemplateRecipientEmailPlaceholder(recipient.email) && (
|
||||
<span title={recipient.email}>{recipient.email}</span>
|
||||
)}
|
||||
</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
@@ -766,7 +695,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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>
|
||||
@@ -791,7 +720,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -817,7 +746,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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',
|
||||
)}
|
||||
>
|
||||
<Mail className="h-4 w-4" />
|
||||
@@ -843,7 +772,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -869,7 +798,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -895,7 +824,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -921,7 +850,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -947,7 +876,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -973,7 +902,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -999,7 +928,7 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
<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" />
|
||||
@@ -1015,14 +944,10 @@ export const AddTemplateFieldsFormPartial = ({
|
||||
{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>
|
||||
|
||||
@@ -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 ZAddTemplateFieldsFormSchema = z.object({
|
||||
fields: z.array(
|
||||
z.object({
|
||||
|
||||
@@ -1,17 +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 { TemplateDirectLink } from '@prisma/client';
|
||||
import { DocumentSigningOrder, type Field, RecipientRole } from '@prisma/client';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVerticalIcon, HelpCircle, Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useFieldArray, 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 { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
@@ -28,11 +14,20 @@ import { Button } from '@documenso/ui/primitives/button';
|
||||
import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { toast } from '@documenso/ui/primitives/use-toast';
|
||||
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 { TemplateDirectLink } from '@prisma/client';
|
||||
import { DocumentSigningOrder, type Field, RecipientRole } from '@prisma/client';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GripVerticalIcon, HelpCircle, Link2Icon, Plus, Trash } from 'lucide-react';
|
||||
import { useCallback, useId, useMemo, useRef, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
|
||||
import {
|
||||
DocumentReadOnlyFields,
|
||||
mapFieldsWithRecipients,
|
||||
} from '../../components/document/document-read-only-fields';
|
||||
import { DocumentReadOnlyFields, mapFieldsWithRecipients } from '../../components/document/document-read-only-fields';
|
||||
import { Checkbox } from '../checkbox';
|
||||
import {
|
||||
DocumentFlowFormContainerActions,
|
||||
@@ -114,9 +109,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
}));
|
||||
|
||||
if (signingOrder === DocumentSigningOrder.SEQUENTIAL) {
|
||||
mappedRecipients = mappedRecipients.sort(
|
||||
(a, b) => (a.signingOrder ?? 0) - (b.signingOrder ?? 0),
|
||||
);
|
||||
mappedRecipients = mappedRecipients.sort((a, b) => (a.signingOrder ?? 0) - (b.signingOrder ?? 0));
|
||||
}
|
||||
|
||||
return mappedRecipients;
|
||||
@@ -131,10 +124,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
},
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@@ -157,9 +147,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
const currentSigners = form.getValues('signers');
|
||||
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
|
||||
@@ -171,9 +159,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
|
||||
// For new signers without nativeId, match by email and update with server ID
|
||||
if (!signer.nativeId) {
|
||||
const newRecipient = response.recipients.find(
|
||||
(recipient) => recipient.email === signer.email,
|
||||
);
|
||||
const newRecipient = response.recipients.find((recipient) => recipient.email === signer.email);
|
||||
if (newRecipient) {
|
||||
return {
|
||||
...signer,
|
||||
@@ -206,14 +192,10 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
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]);
|
||||
@@ -280,18 +262,15 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
void handleAutoSave();
|
||||
};
|
||||
|
||||
const isSignerDirectRecipient = (
|
||||
signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number],
|
||||
): boolean => {
|
||||
return (
|
||||
templateDirectLink !== null &&
|
||||
signer.nativeId === templateDirectLink?.directTemplateRecipientId
|
||||
);
|
||||
const isSignerDirectRecipient = (signer: TAddTemplatePlacholderRecipientsFormSchema['signers'][number]): boolean => {
|
||||
return templateDirectLink !== null && signer.nativeId === templateDirectLink?.directTemplateRecipientId;
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -443,10 +422,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentFlowFormContainerHeader
|
||||
title={documentFlow.title}
|
||||
description={documentFlow.description}
|
||||
/>
|
||||
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
|
||||
<DocumentFlowFormContainerContent>
|
||||
{isDocumentPdfLoaded && (
|
||||
<DocumentReadOnlyFields
|
||||
@@ -470,17 +446,12 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
id="signingOrder"
|
||||
checked={field.value === DocumentSigningOrder.SEQUENTIAL}
|
||||
onCheckedChange={(checked) => {
|
||||
if (
|
||||
!checked &&
|
||||
watchedSigners.some((s) => s.role === RecipientRole.ASSISTANT)
|
||||
) {
|
||||
if (!checked && watchedSigners.some((s) => s.role === RecipientRole.ASSISTANT)) {
|
||||
setShowSigningOrderConfirmation(true);
|
||||
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) {
|
||||
@@ -541,8 +512,8 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
<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>
|
||||
@@ -563,11 +534,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
>
|
||||
<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">
|
||||
{/* todo */}
|
||||
{signers.map((signer, index) => (
|
||||
<Draggable
|
||||
@@ -587,8 +554,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
{...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
|
||||
@@ -625,9 +591,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
handleSigningOrderChange(index, e.target.value);
|
||||
}}
|
||||
disabled={
|
||||
snapshot.isDragging ||
|
||||
isSubmitting ||
|
||||
isSignerDirectRecipient(signer)
|
||||
snapshot.isDragging || isSubmitting || isSignerDirectRecipient(signer)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -658,11 +622,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
type="email"
|
||||
placeholder={_(msg`Email`)}
|
||||
{...field}
|
||||
value={
|
||||
isTemplateRecipientEmailPlaceholder(field.value)
|
||||
? ''
|
||||
: field.value
|
||||
}
|
||||
value={isTemplateRecipientEmailPlaceholder(field.value) ? '' : field.value}
|
||||
disabled={
|
||||
field.disabled ||
|
||||
isSubmitting ||
|
||||
@@ -717,30 +677,29 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
{showAdvancedSettings &&
|
||||
organisation.organisationClaim.flags.cfr21 && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.actionAuth`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('col-span-8', {
|
||||
'col-span-10': isSigningOrderSequential,
|
||||
})}
|
||||
>
|
||||
<FormControl>
|
||||
<RecipientActionAuthSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
{showAdvancedSettings && organisation.organisationClaim.flags.cfr21 && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`signers.${index}.actionAuth`}
|
||||
render={({ field }) => (
|
||||
<FormItem
|
||||
className={cn('col-span-8', {
|
||||
'col-span-10': isSigningOrderSequential,
|
||||
})}
|
||||
>
|
||||
<FormControl>
|
||||
<RecipientActionAuthSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="col-span-2 flex gap-x-2">
|
||||
<FormField
|
||||
@@ -770,15 +729,14 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
<Link2Icon className="h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
<h3 className="font-semibold text-foreground text-lg">
|
||||
<Trans>Direct link receiver</Trans>
|
||||
</h3>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
<Trans>
|
||||
This field cannot be modified or deleted. When you share
|
||||
this template's direct link or add it to your public
|
||||
profile, anyone who accesses it can input their name and
|
||||
email, and fill in the fields assigned to them.
|
||||
This field cannot be modified or deleted. When you share this template's
|
||||
direct link or add it to your public profile, anyone who accesses it can input
|
||||
their name and email, and fill in the fields assigned to them.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -824,7 +782,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
disabled={isSubmitting}
|
||||
onClick={() => onAddPlaceholderRecipient()}
|
||||
>
|
||||
<Plus className="-ml-1 mr-2 h-5 w-5" />
|
||||
<Plus className="mr-2 -ml-1 h-5 w-5" />
|
||||
<Trans>Add Placeholder Recipient</Trans>
|
||||
</Button>
|
||||
|
||||
@@ -832,13 +790,10 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
type="button"
|
||||
className="bg-black/5 hover:bg-black/10 dark:bg-muted dark:hover:bg-muted/80"
|
||||
variant="secondary"
|
||||
disabled={
|
||||
isSubmitting ||
|
||||
form.getValues('signers').some((signer) => signer.email === user?.email)
|
||||
}
|
||||
disabled={isSubmitting || form.getValues('signers').some((signer) => signer.email === user?.email)}
|
||||
onClick={() => onAddPlaceholderSelfRecipient()}
|
||||
>
|
||||
<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>
|
||||
@@ -852,10 +807,7 @@ export const AddTemplatePlaceholderRecipientsFormPartial = ({
|
||||
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,8 +1,7 @@
|
||||
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';
|
||||
import { DocumentSigningOrder, RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
.object({
|
||||
@@ -33,6 +32,4 @@ export const ZAddTemplatePlacholderRecipientsFormSchema = z
|
||||
{ message: 'Signers must have unique names', path: ['signers__root'] },
|
||||
);
|
||||
|
||||
export type TAddTemplatePlacholderRecipientsFormSchema = z.infer<
|
||||
typeof ZAddTemplatePlacholderRecipientsFormSchema
|
||||
>;
|
||||
export type TAddTemplatePlacholderRecipientsFormSchema = z.infer<typeof ZAddTemplatePlacholderRecipientsFormSchema>;
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { DocumentVisibility, TeamMemberRole, TemplateType } from '@prisma/client';
|
||||
import { DocumentDistributionMethod, type Field } 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';
|
||||
import {
|
||||
DOCUMENT_DISTRIBUTION_METHODS,
|
||||
DOCUMENT_SIGNATURE_TYPES,
|
||||
} from '@documenso/lib/constants/document';
|
||||
import { DOCUMENT_DISTRIBUTION_METHODS, DOCUMENT_SIGNATURE_TYPES } from '@documenso/lib/constants/document';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
@@ -37,30 +24,25 @@ import {
|
||||
DocumentVisibilitySelect,
|
||||
DocumentVisibilityTooltip,
|
||||
} from '@documenso/ui/components/document/document-visibility-select';
|
||||
import { TemplateTypeSelect, TemplateTypeTooltip } from '@documenso/ui/components/template/template-type-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 { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
TemplateTypeSelect,
|
||||
TemplateTypeTooltip,
|
||||
} from '@documenso/ui/components/template/template-type-select';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@documenso/ui/primitives/accordion';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
DocumentDistributionMethod,
|
||||
DocumentVisibility,
|
||||
type Field,
|
||||
TeamMemberRole,
|
||||
TemplateType,
|
||||
} from '@prisma/client';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
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 { DocumentSignatureSettingsTooltip } from '../../components/document/document-signature-settings-tooltip';
|
||||
import { Combobox } from '../combobox';
|
||||
import {
|
||||
@@ -123,10 +105,8 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
message: template.templateMeta?.message ?? '',
|
||||
timezone: template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
dateFormat: (template.templateMeta?.dateFormat ??
|
||||
DEFAULT_DOCUMENT_DATE_FORMAT) as TDocumentMetaDateFormat,
|
||||
distributionMethod:
|
||||
template.templateMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
dateFormat: (template.templateMeta?.dateFormat ?? DEFAULT_DOCUMENT_DATE_FORMAT) as TDocumentMetaDateFormat,
|
||||
distributionMethod: template.templateMeta?.distributionMethod || DocumentDistributionMethod.EMAIL,
|
||||
redirectUrl: template.templateMeta?.redirectUrl ?? '',
|
||||
language: template.templateMeta?.language ?? 'en',
|
||||
emailId: template.templateMeta?.emailId ?? null,
|
||||
@@ -142,11 +122,10 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
const distributionMethod = form.watch('meta.distributionMethod');
|
||||
const emailSettings = form.watch('meta.emailSettings');
|
||||
|
||||
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 || [];
|
||||
|
||||
@@ -192,10 +171,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentFlowFormContainerHeader
|
||||
title={documentFlow.title}
|
||||
description={documentFlow.description}
|
||||
/>
|
||||
<DocumentFlowFormContainerHeader title={documentFlow.title} description={documentFlow.description} />
|
||||
|
||||
<DocumentFlowFormContainerContent>
|
||||
{isDocumentPdfLoaded && (
|
||||
@@ -207,10 +183,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
)}
|
||||
|
||||
<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"
|
||||
@@ -221,12 +194,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
{...field}
|
||||
maxLength={255}
|
||||
onBlur={handleAutoSave}
|
||||
/>
|
||||
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -246,9 +214,8 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
|
||||
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.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
@@ -375,30 +342,27 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This is how the document will reach the recipients once the document is
|
||||
ready for signing.
|
||||
This is how the document will reach the recipients once the document is ready for signing.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Email</strong> - The recipient will be emailed the document to
|
||||
sign, approve, etc.
|
||||
<strong>Email</strong> - The recipient will be emailed the document to sign, approve, etc.
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>None</strong> - We will generate links which you can send to
|
||||
the recipients manually.
|
||||
<strong>None</strong> - We will generate links which you can send to the recipients
|
||||
manually.
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Trans>
|
||||
<strong>Note</strong> - If you use Links in combination with direct
|
||||
templates, you will need to manually send the links to the remaining
|
||||
recipients.
|
||||
<strong>Note</strong> - If you use Links in combination with direct templates, you will need
|
||||
to manually send the links to the remaining recipients.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -417,13 +381,11 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
{Object.values(DOCUMENT_DISTRIBUTION_METHODS).map(
|
||||
({ value, description }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{t(description)}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
{Object.values(DOCUMENT_DISTRIBUTION_METHODS).map(({ value, description }) => (
|
||||
<SelectItem key={value} value={value}>
|
||||
{t(description)}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
@@ -496,7 +458,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Trans>Email Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="-mx-1 px-1 pt-4 text-sm leading-relaxed text-muted-foreground [&>div]:pb-0">
|
||||
<AccordionContent className="-mx-1 px-1 pt-4 text-muted-foreground text-sm leading-relaxed [&>div]:pb-0">
|
||||
<div className="flex flex-col space-y-6">
|
||||
{organisation.organisationClaim.flags.emailDomains && (
|
||||
<FormField
|
||||
@@ -512,14 +474,9 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<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"
|
||||
>
|
||||
<SelectTrigger loading={isLoadingEmails} className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -548,8 +505,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<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>
|
||||
|
||||
@@ -636,7 +592,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="-mx-1 px-1 pt-4 text-sm leading-relaxed text-muted-foreground">
|
||||
<AccordionContent className="-mx-1 px-1 pt-4 text-muted-foreground text-sm leading-relaxed">
|
||||
<div className="flex flex-col space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -652,20 +608,14 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
|
||||
<TooltipContent className="max-w-xs text-muted-foreground">
|
||||
<Trans>
|
||||
Add an external ID to the template. This can be used to identify
|
||||
in external systems.
|
||||
Add an external ID to the template. This can be used to identify in external systems.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
{...field}
|
||||
maxLength={255}
|
||||
onBlur={handleAutoSave}
|
||||
/>
|
||||
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@@ -748,20 +698,13 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
</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>
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
{...field}
|
||||
maxLength={255}
|
||||
onBlur={handleAutoSave}
|
||||
/>
|
||||
<Input className="bg-background" {...field} maxLength={255} onBlur={handleAutoSave} />
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentDistributionMethod } from '@prisma/client';
|
||||
import { DocumentVisibility, TemplateType } 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 { ZDocumentAccessAuthTypesSchema, ZDocumentActionAuthTypesSchema } from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { ZDocumentMetaDateFormatSchema, ZDocumentMetaTimezoneSchema } from '@documenso/lib/types/document-meta';
|
||||
import { isValidRedirectUrl } from '@documenso/lib/utils/is-valid-redirect-url';
|
||||
import { zEmail } from '@documenso/lib/utils/zod';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { DocumentDistributionMethod, DocumentVisibility, TemplateType } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
title: z.string().trim().min(1, { message: "Title can't be empty" }),
|
||||
@@ -35,16 +27,12 @@ export const ZAddTemplateSettingsFormSchema = z.object({
|
||||
message: z.string(),
|
||||
timezone: ZDocumentMetaTimezoneSchema.default(DEFAULT_DOCUMENT_TIME_ZONE),
|
||||
dateFormat: ZDocumentMetaDateFormatSchema.default(DEFAULT_DOCUMENT_DATE_FORMAT),
|
||||
distributionMethod: z
|
||||
.nativeEnum(DocumentDistributionMethod)
|
||||
.optional()
|
||||
.default(DocumentDistributionMethod.EMAIL),
|
||||
distributionMethod: z.nativeEnum(DocumentDistributionMethod).optional().default(DocumentDistributionMethod.EMAIL),
|
||||
redirectUrl: z
|
||||
.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)])
|
||||
|
||||
@@ -4,23 +4,21 @@ import { cn } from '../lib/utils';
|
||||
|
||||
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'border-input ring-offset-background placeholder:text-muted-foreground/40 focus-visible:ring-ring flex h-20 w-full rounded-md border bg-transparent px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
{
|
||||
'ring-2 !ring-red-500 transition-all': props['aria-invalid'],
|
||||
},
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'flex h-20 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background 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-red-500 ring-2 transition-all': props['aria-invalid'],
|
||||
},
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Monitor, MoonStar, Sun } from 'lucide-react';
|
||||
import { Theme, useTheme } from 'remix-themes';
|
||||
|
||||
import { useIsMounted } from '@documenso/lib/client-only/hooks/use-is-mounted';
|
||||
|
||||
export const ThemeSwitcher = () => {
|
||||
const [theme, setTheme] = useTheme();
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
return (
|
||||
<div className="bg-muted flex items-center gap-x-1 rounded-full p-1">
|
||||
<div className="flex items-center gap-x-1 rounded-full bg-muted p-1">
|
||||
<button
|
||||
className="text-muted-foreground relative z-10 flex h-8 w-8 items-center justify-center rounded-full"
|
||||
className="relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground"
|
||||
onClick={() => setTheme(Theme.LIGHT)}
|
||||
>
|
||||
{isMounted && theme === Theme.LIGHT && (
|
||||
<motion.div
|
||||
className="bg-background absolute inset-0 rounded-full mix-blend-color-burn"
|
||||
className="absolute inset-0 rounded-full bg-background mix-blend-color-burn"
|
||||
layoutId="selected-theme"
|
||||
/>
|
||||
)}
|
||||
@@ -24,12 +23,12 @@ export const ThemeSwitcher = () => {
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="text-muted-foreground relative z-10 flex h-8 w-8 items-center justify-center rounded-full"
|
||||
className="relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground"
|
||||
onClick={() => setTheme(Theme.DARK)}
|
||||
>
|
||||
{isMounted && theme === Theme.DARK && (
|
||||
<motion.div
|
||||
className="bg-background absolute inset-0 rounded-full mix-blend-exclusion"
|
||||
className="absolute inset-0 rounded-full bg-background mix-blend-exclusion"
|
||||
layoutId="selected-theme"
|
||||
/>
|
||||
)}
|
||||
@@ -38,12 +37,12 @@ export const ThemeSwitcher = () => {
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="text-muted-foreground relative z-10 flex h-8 w-8 items-center justify-center rounded-full"
|
||||
className="relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-muted-foreground"
|
||||
onClick={() => setTheme(null)}
|
||||
>
|
||||
{isMounted && theme === null && (
|
||||
<motion.div
|
||||
className="bg-background absolute inset-0 rounded-full mix-blend-exclusion"
|
||||
className="absolute inset-0 rounded-full bg-background mix-blend-exclusion"
|
||||
layoutId="selected-theme"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import * as ToastPrimitives from '@radix-ui/react-toast';
|
||||
import { cva } from 'class-variance-authority';
|
||||
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';
|
||||
|
||||
@@ -16,7 +15,7 @@ const ToastViewport = React.forwardRef<
|
||||
<ToastPrimitives.Viewport
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed top-0 z-[1001] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
|
||||
'fixed top-0 z-[1001] flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -31,8 +30,7 @@ const toastVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-background border',
|
||||
destructive:
|
||||
'group destructive border-destructive bg-destructive text-destructive-foreground',
|
||||
destructive: 'group destructive border-destructive bg-destructive text-destructive-foreground',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
@@ -45,13 +43,7 @@ const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
|
||||
>(({ className, variant, ...props }, ref) => {
|
||||
return (
|
||||
<ToastPrimitives.Root
|
||||
ref={ref}
|
||||
className={cn(toastVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
|
||||
});
|
||||
|
||||
Toast.displayName = ToastPrimitives.Root.displayName;
|
||||
@@ -63,7 +55,7 @@ const ToastAction = React.forwardRef<
|
||||
<ToastPrimitives.Action
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'ring-offset-background hover:bg-secondary focus:ring-ring group-[.destructive]:border-destructive/30 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 font-medium text-sm ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-destructive/30 group-[.destructive]:focus:ring-destructive group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -80,7 +72,7 @@ const ToastClose = React.forwardRef<
|
||||
ref={ref}
|
||||
data-testid="toast-close"
|
||||
className={cn(
|
||||
'text-foreground/50 hover:text-foreground absolute right-2 top-2 rounded-md p-1 opacity-100 transition-opacity focus:opacity-100 focus:outline-none focus:ring-2 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 md:opacity-0 group-hover:md:opacity-100',
|
||||
'absolute top-2 right-2 rounded-md p-1 text-foreground/50 opacity-100 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-[.destructive]:text-red-300 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 group-[.destructive]:hover:text-red-50 md:opacity-0 group-hover:md:opacity-100',
|
||||
className,
|
||||
)}
|
||||
toast-close=""
|
||||
@@ -96,7 +88,7 @@ const ToastTitle = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title ref={ref} className={cn('text-sm font-semibold', className)} {...props} />
|
||||
<ToastPrimitives.Title ref={ref} className={cn('font-semibold text-sm', className)} {...props} />
|
||||
));
|
||||
|
||||
ToastTitle.displayName = ToastPrimitives.Title.displayName;
|
||||
@@ -105,11 +97,7 @@ const ToastDescription = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn('text-sm opacity-90', className)}
|
||||
{...props}
|
||||
/>
|
||||
<ToastPrimitives.Description ref={ref} className={cn('text-sm opacity-90', className)} {...props} />
|
||||
));
|
||||
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
||||
@@ -119,13 +107,13 @@ type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
||||
|
||||
export {
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
type ToastActionElement,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
type ToastProps,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from './toast';
|
||||
import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from './toast';
|
||||
import { useToast } from './use-toast';
|
||||
|
||||
export function Toaster() {
|
||||
@@ -13,18 +6,16 @@ export function Toaster() {
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
{toasts.map(function ({ id, title, description, action, ...props }) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && <ToastDescription>{description}</ToastDescription>}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
);
|
||||
})}
|
||||
{toasts.map(({ id, title, description, action, ...props }) => (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && <ToastDescription>{description}</ToastDescription>}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
))}
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@@ -31,11 +30,7 @@ const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> & VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
<TogglePrimitive.Root ref={ref} className={cn(toggleVariants({ variant, size, className }))} {...props} />
|
||||
));
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
import * as React from 'react';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
|
||||
@@ -11,9 +10,7 @@ const Tooltip = TooltipPrimitive.Root;
|
||||
const TooltipTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>
|
||||
>(({ type = 'button', ...props }, ref) => (
|
||||
<TooltipPrimitive.Trigger ref={ref} type={type} {...props} />
|
||||
));
|
||||
>(({ type = 'button', ...props }, ref) => <TooltipPrimitive.Trigger ref={ref} type={type} {...props} />);
|
||||
|
||||
TooltipTrigger.displayName = TooltipPrimitive.Trigger.displayName;
|
||||
|
||||
@@ -26,7 +23,7 @@ const TooltipContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'bg-popover z-9999 text-popover-foreground animate-in fade-in-50 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 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md',
|
||||
'fade-in-50 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-9999 animate-in overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-popover-foreground text-sm shadow-md',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -36,4 +33,4 @@ const TooltipContent = React.forwardRef<
|
||||
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
||||
|
||||
@@ -152,7 +152,9 @@ function toast({ ...props }: Toast) {
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss();
|
||||
if (!open) {
|
||||
dismiss();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -184,4 +186,4 @@ function useToast() {
|
||||
};
|
||||
}
|
||||
|
||||
export { useToast, toast };
|
||||
export { toast, useToast };
|
||||
|
||||
Reference in New Issue
Block a user