mirror of
https://github.com/documenso/documenso.git
synced 2026-07-23 16:33:57 +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:
@@ -6,11 +6,7 @@ type AnimateGenericFadeInOutProps = {
|
||||
motionKey?: string;
|
||||
};
|
||||
|
||||
export const AnimateGenericFadeInOut = ({
|
||||
children,
|
||||
className,
|
||||
motionKey,
|
||||
}: AnimateGenericFadeInOutProps) => {
|
||||
export const AnimateGenericFadeInOut = ({ children, className, motionKey }: AnimateGenericFadeInOutProps) => {
|
||||
return (
|
||||
<motion.section
|
||||
key={motionKey}
|
||||
|
||||
@@ -6,5 +6,5 @@ type ClientOnlyProps = {
|
||||
};
|
||||
|
||||
export const ClientOnly = ({ children, fallback = null }: ClientOnlyProps) => {
|
||||
return useHydrated() ? <>{children()}</> : <>{fallback}</>;
|
||||
return useHydrated() ? children() : fallback;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { motion } from 'framer-motion';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { CheckSquareIcon, CopyIcon } from 'lucide-react';
|
||||
|
||||
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { CheckSquareIcon, CopyIcon } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
@@ -66,11 +63,7 @@ export const CopyTextButton = ({
|
||||
)}
|
||||
>
|
||||
<div className="absolute">
|
||||
{copiedTimeout ? (
|
||||
<CheckSquareIcon className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<CopyIcon className="h-3.5 w-3.5" />
|
||||
)}
|
||||
{copiedTimeout ? <CheckSquareIcon className="h-3.5 w-3.5" /> : <CopyIcon className="h-3.5 w-3.5" />}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { CheckIcon } from 'lucide-react';
|
||||
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { dynamicActivate } from '@documenso/lib/utils/i18n';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from '@documenso/ui/primitives/command';
|
||||
import { CommandDialog, CommandGroup, CommandInput, CommandItem, CommandList } from '@documenso/ui/primitives/command';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { CheckIcon } from 'lucide-react';
|
||||
|
||||
type LanguageSwitcherDialogProps = {
|
||||
open: boolean;
|
||||
@@ -48,12 +41,7 @@ export const LanguageSwitcherDialog = ({ open, setOpen }: LanguageSwitcherDialog
|
||||
value={_(language.full)}
|
||||
onSelect={async () => setLanguage(language.short)}
|
||||
>
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
'mr-2 h-4 w-4',
|
||||
i18n.locale === language.short ? 'opacity-100' : 'opacity-0',
|
||||
)}
|
||||
/>
|
||||
<CheckIcon className={cn('mr-2 h-4 w-4', i18n.locale === language.short ? 'opacity-100' : 'opacity-0')} />
|
||||
{_(language.full)}
|
||||
</CommandItem>
|
||||
))}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { CheckIcon, CopyIcon } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../../primitives/popover';
|
||||
@@ -86,12 +85,7 @@ export const LocalTime = ({ date, className }: LocalTimeProps) => {
|
||||
isCopied={copiedField === 'Local'}
|
||||
onCopy={() => void onCopy('Local', local)}
|
||||
/>
|
||||
<TimeRow
|
||||
label="UTC"
|
||||
value={utc}
|
||||
isCopied={copiedField === 'UTC'}
|
||||
onCopy={() => void onCopy('UTC', utc)}
|
||||
/>
|
||||
<TimeRow label="UTC" value={utc} isCopied={copiedField === 'UTC'} onCopy={() => void onCopy('UTC', utc)} />
|
||||
<TimeRow
|
||||
label="Unix"
|
||||
value={unix}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import type { TDocumentEmailSettings } from '@documenso/lib/types/document-email';
|
||||
import { DocumentEmailEvents } from '@documenso/lib/types/document-email';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Checkbox } from '../../primitives/checkbox';
|
||||
@@ -16,11 +15,7 @@ type DocumentEmailCheckboxesProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const DocumentEmailCheckboxes = ({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
}: DocumentEmailCheckboxesProps) => {
|
||||
export const DocumentEmailCheckboxes = ({ value, onChange, className }: DocumentEmailCheckboxesProps) => {
|
||||
return (
|
||||
<div className={cn('space-y-3', className)}>
|
||||
<div className="flex flex-row items-center">
|
||||
@@ -28,13 +23,11 @@ export const DocumentEmailCheckboxes = ({
|
||||
id={DocumentEmailEvents.RecipientSigned}
|
||||
className="h-5 w-5"
|
||||
checked={value.recipientSigned}
|
||||
onCheckedChange={(checked) =>
|
||||
onChange({ ...value, [DocumentEmailEvents.RecipientSigned]: Boolean(checked) })
|
||||
}
|
||||
onCheckedChange={(checked) => onChange({ ...value, [DocumentEmailEvents.RecipientSigned]: Boolean(checked) })}
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.RecipientSigned}
|
||||
>
|
||||
<Trans>Email the owner when a recipient signs</Trans>
|
||||
@@ -52,9 +45,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This email is sent to the document owner when a recipient has signed the document.
|
||||
</Trans>
|
||||
<Trans>This email is sent to the document owner when a recipient has signed the document.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -72,7 +63,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.RecipientSigningRequest}
|
||||
>
|
||||
<Trans>Email recipients with a signing request</Trans>
|
||||
@@ -90,9 +81,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This email is sent to the recipient requesting them to sign the document.
|
||||
</Trans>
|
||||
<Trans>This email is sent to the recipient requesting them to sign the document.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -110,7 +99,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.RecipientRemoved}
|
||||
>
|
||||
<Trans>Email recipients when they're removed from a pending document</Trans>
|
||||
@@ -128,9 +117,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This email is sent to the recipient if they are removed from a pending document.
|
||||
</Trans>
|
||||
<Trans>This email is sent to the recipient if they are removed from a pending document.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -142,13 +129,11 @@ export const DocumentEmailCheckboxes = ({
|
||||
id={DocumentEmailEvents.DocumentPending}
|
||||
className="h-5 w-5"
|
||||
checked={value.documentPending}
|
||||
onCheckedChange={(checked) =>
|
||||
onChange({ ...value, [DocumentEmailEvents.DocumentPending]: Boolean(checked) })
|
||||
}
|
||||
onCheckedChange={(checked) => onChange({ ...value, [DocumentEmailEvents.DocumentPending]: Boolean(checked) })}
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.DocumentPending}
|
||||
>
|
||||
<Trans>Email the signer if the document is still pending</Trans>
|
||||
@@ -167,8 +152,8 @@ export const DocumentEmailCheckboxes = ({
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This email will be sent to the recipient who has just signed the document, if
|
||||
there are still other recipients who have not signed yet.
|
||||
This email will be sent to the recipient who has just signed the document, if there are still other
|
||||
recipients who have not signed yet.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -187,7 +172,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.DocumentCompleted}
|
||||
>
|
||||
<Trans>Email recipients when the document is completed</Trans>
|
||||
@@ -205,9 +190,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This will be sent to all recipients once the document has been fully completed.
|
||||
</Trans>
|
||||
<Trans>This will be sent to all recipients once the document has been fully completed.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -219,13 +202,11 @@ export const DocumentEmailCheckboxes = ({
|
||||
id={DocumentEmailEvents.DocumentDeleted}
|
||||
className="h-5 w-5"
|
||||
checked={value.documentDeleted}
|
||||
onCheckedChange={(checked) =>
|
||||
onChange({ ...value, [DocumentEmailEvents.DocumentDeleted]: Boolean(checked) })
|
||||
}
|
||||
onCheckedChange={(checked) => onChange({ ...value, [DocumentEmailEvents.DocumentDeleted]: Boolean(checked) })}
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.DocumentDeleted}
|
||||
>
|
||||
<Trans>Email recipients when a pending document is deleted</Trans>
|
||||
@@ -243,9 +224,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This will be sent to all recipients if a pending document has been deleted.
|
||||
</Trans>
|
||||
<Trans>This will be sent to all recipients if a pending document has been deleted.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -263,7 +242,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.OwnerDocumentCompleted}
|
||||
>
|
||||
<Trans>Email the owner when the document is completed</Trans>
|
||||
@@ -281,10 +260,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This will be sent to the document owner once the document has been fully
|
||||
completed.
|
||||
</Trans>
|
||||
<Trans>This will be sent to the document owner once the document has been fully completed.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -302,7 +278,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.OwnerDocumentCreated}
|
||||
>
|
||||
<Trans>Email the owner when a document is created from a direct template</Trans>
|
||||
@@ -321,8 +297,8 @@ export const DocumentEmailCheckboxes = ({
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This email is sent to the document owner when a recipient creates a document via a
|
||||
direct template link.
|
||||
This email is sent to the document owner when a recipient creates a document via a direct template
|
||||
link.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -341,7 +317,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
/>
|
||||
|
||||
<label
|
||||
className="ml-2 flex flex-row items-center text-sm text-muted-foreground"
|
||||
className="ml-2 flex flex-row items-center text-muted-foreground text-sm"
|
||||
htmlFor={DocumentEmailEvents.OwnerRecipientExpired}
|
||||
>
|
||||
<Trans>Send recipient expired email to the owner</Trans>
|
||||
@@ -359,10 +335,7 @@ export const DocumentEmailCheckboxes = ({
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
This will be sent to the document owner when a recipient's signing window has
|
||||
expired.
|
||||
</Trans>
|
||||
<Trans>This will be sent to the document owner when a recipient's signing window has expired.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
||||
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
|
||||
import { MultiSelect, type Option } from '@documenso/ui/primitives/multiselect';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
export interface DocumentGlobalAuthAccessSelectProps {
|
||||
value?: string[];
|
||||
@@ -41,15 +38,11 @@ export const DocumentGlobalAuthAccessSelect = ({
|
||||
|
||||
// Convert string array to Option array for MultiSelect
|
||||
const selectedOptions =
|
||||
(value
|
||||
?.map((val) => authOptions.find((option) => option.value === val))
|
||||
.filter(Boolean) as Option[]) || [];
|
||||
(value?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
|
||||
|
||||
// Convert default value to Option array
|
||||
const defaultOptions =
|
||||
(defaultValue
|
||||
?.map((val) => authOptions.find((option) => option.value === val))
|
||||
.filter(Boolean) as Option[]) || [];
|
||||
(defaultValue?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
|
||||
|
||||
const handleChange = (options: Option[]) => {
|
||||
const values = options.map((option) => option.value);
|
||||
@@ -102,8 +95,7 @@ export const DocumentGlobalAuthAccessTooltip = () => (
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>No restrictions</strong> - The document can be accessed directly by the URL sent
|
||||
to the recipient
|
||||
<strong>No restrictions</strong> - The document can be accessed directly by the URL sent to the recipient
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
||||
import { DocumentActionAuth, DocumentAuth } from '@documenso/lib/types/document-auth';
|
||||
import { MultiSelect, type Option } from '@documenso/ui/primitives/multiselect';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
export interface DocumentGlobalAuthActionSelectProps {
|
||||
value?: string[];
|
||||
@@ -41,15 +40,11 @@ export const DocumentGlobalAuthActionSelect = ({
|
||||
|
||||
// Convert string array to Option array for MultiSelect
|
||||
const selectedOptions =
|
||||
(value
|
||||
?.map((val) => authOptions.find((option) => option.value === val))
|
||||
.filter(Boolean) as Option[]) || [];
|
||||
(value?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
|
||||
|
||||
// Convert default value to Option array
|
||||
const defaultOptions =
|
||||
(defaultValue
|
||||
?.map((val) => authOptions.find((option) => option.value === val))
|
||||
.filter(Boolean) as Option[]) || [];
|
||||
(defaultValue?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
|
||||
|
||||
const handleChange = (options: Option[]) => {
|
||||
const values = options.map((option) => option.value);
|
||||
@@ -85,36 +80,33 @@ export const DocumentGlobalAuthActionTooltip = () => (
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
The authentication methods required for recipients to sign the signature field.
|
||||
</Trans>
|
||||
<Trans>The authentication methods required for recipients to sign the signature field.</Trans>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
These can be overriden by setting the authentication requirements directly on each
|
||||
recipient in the next step. Multiple methods can be selected.
|
||||
These can be overriden by setting the authentication requirements directly on each recipient in the next step.
|
||||
Multiple methods can be selected.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey
|
||||
configured via their settings
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey configured via their
|
||||
settings
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via
|
||||
their settings
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via their settings
|
||||
</Trans>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Require password</strong> - The recipient must have an account and password
|
||||
configured via their settings, the password will be verified during signing
|
||||
<strong>Require password</strong> - The recipient must have an account and password configured via their
|
||||
settings, the password will be verified during signing
|
||||
</Trans>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { DocumentMeta, Field } from '@prisma/client';
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
import { Clock, EyeOffIcon } from 'lucide-react';
|
||||
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { isTemplateRecipientEmailPlaceholder } from '@documenso/lib/constants/template';
|
||||
import type { TRecipientLite } from '@documenso/lib/types/recipient';
|
||||
@@ -18,6 +10,12 @@ import { Badge } from '@documenso/ui/primitives/badge';
|
||||
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
|
||||
import { ElementVisible } from '@documenso/ui/primitives/element-visible';
|
||||
import { PopoverHover } from '@documenso/ui/primitives/popover';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { DocumentMeta, Field } from '@prisma/client';
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
import { Clock, EyeOffIcon } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { getRecipientColorStyles } from '../../lib/recipient-colors';
|
||||
import { FieldContent } from '../../primitives/document-flow/field-content';
|
||||
@@ -67,10 +65,7 @@ export type DocumentReadOnlyFieldsProps = {
|
||||
showRecipientColors?: boolean;
|
||||
};
|
||||
|
||||
export const mapFieldsWithRecipients = (
|
||||
fields: Field[],
|
||||
recipients: TRecipientLite[],
|
||||
): DocumentField[] => {
|
||||
export const mapFieldsWithRecipients = (fields: Field[], recipients: TRecipientLite[]): DocumentField[] => {
|
||||
return fields.map((field) => {
|
||||
const recipient = recipients.find((recipient) => recipient.id === field.recipientId) || {
|
||||
id: field.recipientId,
|
||||
@@ -110,18 +105,16 @@ export const DocumentReadOnlyFields = ({
|
||||
readonly={true}
|
||||
color={
|
||||
showRecipientColors
|
||||
? getRecipientColorStyles(
|
||||
recipientIds.findIndex((id) => id === field.recipientId),
|
||||
)
|
||||
? getRecipientColorStyles(recipientIds.findIndex((id) => id === field.recipientId))
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{showRecipientTooltip && (
|
||||
<div className="absolute -right-3 -top-3">
|
||||
<div className="absolute -top-3 -right-3">
|
||||
<PopoverHover
|
||||
trigger={
|
||||
<Avatar className="h-6 w-6 border-2 border-solid border-gray-200/50 transition-colors hover:border-gray-200">
|
||||
<AvatarFallback className="bg-neutral-50 text-xs text-gray-400">
|
||||
<Avatar className="h-6 w-6 border-2 border-gray-200/50 border-solid transition-colors hover:border-gray-200">
|
||||
<AvatarFallback className="bg-neutral-50 text-gray-400 text-xs">
|
||||
{extractInitials(field.recipient.name || field.recipient.email)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -133,11 +126,7 @@ export const DocumentReadOnlyFields = ({
|
||||
{showFieldStatus && (
|
||||
<Badge
|
||||
className="mx-auto mb-1 py-0.5"
|
||||
variant={
|
||||
field.recipient.signingStatus === SigningStatus.SIGNED
|
||||
? 'default'
|
||||
: 'secondary'
|
||||
}
|
||||
variant={field.recipient.signingStatus === SigningStatus.SIGNED ? 'default' : 'secondary'}
|
||||
>
|
||||
{field.recipient.signingStatus === SigningStatus.SIGNED ? (
|
||||
<>
|
||||
@@ -154,17 +143,15 @@ export const DocumentReadOnlyFields = ({
|
||||
)}
|
||||
|
||||
<p className="text-center font-semibold">
|
||||
<span>
|
||||
{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])} field
|
||||
</span>
|
||||
<span>{parseMessageDescriptor(_, FRIENDLY_FIELD_TYPE[field.type])} field</span>
|
||||
</p>
|
||||
|
||||
<p className="mt-1 text-center text-xs text-muted-foreground">
|
||||
<p className="mt-1 text-center text-muted-foreground text-xs">
|
||||
{getRecipientDisplayText(field.recipient)}
|
||||
</p>
|
||||
|
||||
<button
|
||||
className="absolute right-0 top-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
|
||||
className="absolute top-0 right-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
|
||||
onClick={() => handleHideField(field.secondaryId)}
|
||||
title="Hide field"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
|
||||
export const DocumentSendEmailMessageHelper = () => {
|
||||
@@ -11,21 +9,15 @@ export const DocumentSendEmailMessageHelper = () => {
|
||||
|
||||
<ul className="mt-2 flex list-inside list-disc flex-col gap-y-2 text-sm">
|
||||
<li className="text-muted-foreground">
|
||||
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
|
||||
{'{signer.name}'}
|
||||
</code>{' '}
|
||||
- <Trans>The signer's name</Trans>
|
||||
<code className="rounded bg-muted-foreground/20 p-1 text-muted-foreground text-sm">{'{signer.name}'}</code> -{' '}
|
||||
<Trans>The signer's name</Trans>
|
||||
</li>
|
||||
<li className="text-muted-foreground">
|
||||
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
|
||||
{'{signer.email}'}
|
||||
</code>{' '}
|
||||
- <Trans>The signer's email</Trans>
|
||||
<code className="rounded bg-muted-foreground/20 p-1 text-muted-foreground text-sm">{'{signer.email}'}</code> -{' '}
|
||||
<Trans>The signer's email</Trans>
|
||||
</li>
|
||||
<li className="text-muted-foreground">
|
||||
<code className="text-muted-foreground bg-muted-foreground/20 rounded p-1 text-sm">
|
||||
{'{document.name}'}
|
||||
</code>{' '}
|
||||
<code className="rounded bg-muted-foreground/20 p-1 text-muted-foreground text-sm">{'{document.name}'}</code>{' '}
|
||||
- <Trans>The document's name</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Copy, Sparkles } from 'lucide-react';
|
||||
import { FaXTwitter } from 'react-icons/fa6';
|
||||
|
||||
import { useCopyShareLink } from '@documenso/lib/client-only/hooks/use-copy-share-link';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION } from '@documenso/lib/constants/trpc';
|
||||
import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Copy, Sparkles } from 'lucide-react';
|
||||
import type React from 'react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { FaXTwitter } from 'react-icons/fa6';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Button } from '../../primitives/button';
|
||||
@@ -31,12 +30,7 @@ export type DocumentShareButtonProps = HTMLAttributes<HTMLButtonElement> & {
|
||||
trigger?: (_props: { loading: boolean; disabled: boolean }) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const DocumentShareButton = ({
|
||||
token,
|
||||
documentId,
|
||||
className,
|
||||
trigger,
|
||||
}: DocumentShareButtonProps) => {
|
||||
export const DocumentShareButton = ({ token, documentId, className, trigger }: DocumentShareButtonProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -147,9 +141,8 @@ export const DocumentShareButton = ({
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>
|
||||
Rest assured, your document is strictly confidential and will never be shared. Only
|
||||
your signing experience will be highlighted. Share your personalized signing card to
|
||||
showcase your signature!
|
||||
Rest assured, your document is strictly confidential and will never be shared. Only your signing
|
||||
experience will be highlighted. Share your personalized signing card to showcase your signature!
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -157,8 +150,7 @@ export const DocumentShareButton = ({
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="rounded-md border p-4">
|
||||
I just {token ? 'signed' : 'sent'} a document in style with{' '}
|
||||
<span className="font-medium text-blue-400">@documenso</span>
|
||||
. Check it out!
|
||||
<span className="font-medium text-blue-400">@documenso</span>. Check it out!
|
||||
<span className="mt-2 block" />
|
||||
<span
|
||||
className={cn('break-all font-medium text-blue-400', {
|
||||
@@ -168,12 +160,9 @@ export const DocumentShareButton = ({
|
||||
{NEXT_PUBLIC_WEBAPP_URL()}/share/{shareLink?.slug || '...'}
|
||||
</span>
|
||||
<div
|
||||
className={cn(
|
||||
'mt-4 aspect-[1200/630] overflow-hidden rounded-lg border bg-muted/40',
|
||||
{
|
||||
'animate-pulse': !shareLink?.slug,
|
||||
},
|
||||
)}
|
||||
className={cn('mt-4 aspect-[1200/630] overflow-hidden rounded-lg border bg-muted/40', {
|
||||
'animate-pulse': !shareLink?.slug,
|
||||
})}
|
||||
>
|
||||
{shareLink?.slug && (
|
||||
<img
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
|
||||
export const DocumentSignatureSettingsTooltip = () => {
|
||||
return (
|
||||
<Tooltip>
|
||||
@@ -10,7 +9,7 @@ export const DocumentSignatureSettingsTooltip = () => {
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="text-foreground max-w-md space-y-2 p-4">
|
||||
<TooltipContent className="max-w-md space-y-2 p-4 text-foreground">
|
||||
<h2>
|
||||
<strong>
|
||||
<Trans>Signature types</Trans>
|
||||
@@ -18,9 +17,7 @@ export const DocumentSignatureSettingsTooltip = () => {
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
<Trans>
|
||||
The types of signatures that recipients are allowed to use when signing the document.
|
||||
</Trans>
|
||||
<Trans>The types of signatures that recipients are allowed to use when signing the document.</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { DOCUMENT_VISIBILITY } from '@documenso/lib/constants/document-visibility';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { TeamMemberRole } from '@prisma/client';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { DOCUMENT_VISIBILITY } from '@documenso/lib/constants/document-visibility';
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
export type DocumentVisibilitySelectType = SelectProps & {
|
||||
currentTeamMemberRole?: string;
|
||||
@@ -26,10 +18,7 @@ export type DocumentVisibilitySelectType = SelectProps & {
|
||||
};
|
||||
|
||||
export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVisibilitySelectType>(
|
||||
(
|
||||
{ currentTeamMemberRole, isTeamSettings = false, disabled, canUpdateVisibility, ...props },
|
||||
ref,
|
||||
) => {
|
||||
({ currentTeamMemberRole, isTeamSettings = false, disabled, canUpdateVisibility, ...props }, ref) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const isAdmin = currentTeamMemberRole === TeamMemberRole.ADMIN;
|
||||
@@ -43,13 +32,8 @@ export const DocumentVisibilitySelect = forwardRef<HTMLButtonElement, DocumentVi
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value={DocumentVisibility.EVERYONE}>
|
||||
{_(DOCUMENT_VISIBILITY.EVERYONE.value)}
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
value={DocumentVisibility.MANAGER_AND_ABOVE}
|
||||
disabled={!isAdmin && !isManager}
|
||||
>
|
||||
<SelectItem value={DocumentVisibility.EVERYONE}>{_(DOCUMENT_VISIBILITY.EVERYONE.value)}</SelectItem>
|
||||
<SelectItem value={DocumentVisibility.MANAGER_AND_ABOVE} disabled={!isAdmin && !isManager}>
|
||||
{_(DOCUMENT_VISIBILITY.MANAGER_AND_ABOVE.value)}
|
||||
</SelectItem>
|
||||
<SelectItem value={DocumentVisibility.ADMIN} disabled={!isAdmin}>
|
||||
@@ -89,8 +73,7 @@ export const DocumentVisibilityTooltip = () => {
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Managers and above</strong> - Only managers and above can access and view the
|
||||
document
|
||||
<strong>Managers and above</strong> - Only managers and above can access and view the document
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import { ClockIcon, EyeOffIcon, LockIcon } from 'lucide-react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import type { Field, Recipient } from '@prisma/client';
|
||||
import { SigningStatus } from '@prisma/client';
|
||||
import { ClockIcon, EyeOffIcon, LockIcon } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { isTemplateRecipientEmailPlaceholder } from '../../../lib/constants/template';
|
||||
import { extractInitials } from '../../../lib/utils/recipient-formatter';
|
||||
@@ -20,15 +18,7 @@ import { PopoverHover } from '../../primitives/popover';
|
||||
interface EnvelopeRecipientFieldTooltipProps {
|
||||
field: Pick<
|
||||
Field,
|
||||
| 'id'
|
||||
| 'inserted'
|
||||
| 'positionX'
|
||||
| 'positionY'
|
||||
| 'width'
|
||||
| 'height'
|
||||
| 'page'
|
||||
| 'type'
|
||||
| 'fieldMeta'
|
||||
'id' | 'inserted' | 'positionX' | 'positionY' | 'width' | 'height' | 'page' | 'type' | 'fieldMeta'
|
||||
> & {
|
||||
recipient: Pick<Recipient, 'name' | 'email' | 'signingStatus'>;
|
||||
};
|
||||
@@ -70,9 +60,7 @@ export function EnvelopeRecipientFieldTooltip({
|
||||
});
|
||||
|
||||
const calculateCoords = useCallback(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
|
||||
|
||||
if (!$page) {
|
||||
return;
|
||||
@@ -111,9 +99,7 @@ export function EnvelopeRecipientFieldTooltip({
|
||||
}, [calculateCoords]);
|
||||
|
||||
useEffect(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
|
||||
|
||||
if (!$page) {
|
||||
return;
|
||||
@@ -145,8 +131,8 @@ export function EnvelopeRecipientFieldTooltip({
|
||||
>
|
||||
<PopoverHover
|
||||
trigger={
|
||||
<Avatar className="absolute -left-3 -top-3 z-50 h-6 w-6 border-2 border-solid border-gray-200/50 transition-colors hover:border-gray-200">
|
||||
<AvatarFallback className="bg-neutral-50 text-xs text-gray-400">
|
||||
<Avatar className="absolute -top-3 -left-3 z-50 h-6 w-6 border-2 border-gray-200/50 border-solid transition-colors hover:border-gray-200">
|
||||
<AvatarFallback className="bg-neutral-50 text-gray-400 text-xs">
|
||||
{extractInitials(field.recipient.name || field.recipient.email)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -191,12 +177,10 @@ export function EnvelopeRecipientFieldTooltip({
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p className="mt-1 text-center text-xs text-muted-foreground">
|
||||
{getRecipientDisplayText(field.recipient)}
|
||||
</p>
|
||||
<p className="mt-1 text-center text-muted-foreground text-xs">{getRecipientDisplayText(field.recipient)}</p>
|
||||
|
||||
<button
|
||||
className="absolute right-0 top-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
|
||||
className="absolute top-0 right-0 my-1 p-2 focus:outline-none focus-visible:ring-0"
|
||||
onClick={() => setHideField(true)}
|
||||
title="Hide field"
|
||||
>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
|
||||
import type {
|
||||
TEnvelopeExpirationDurationPeriod,
|
||||
TEnvelopeExpirationPeriod,
|
||||
} from '@documenso/lib/constants/envelope-expiration';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
|
||||
type ExpirationMode = 'duration' | 'disabled' | 'inherit';
|
||||
|
||||
@@ -35,9 +28,7 @@ const getAmount = (value: TEnvelopeExpirationPeriod | null | undefined): number
|
||||
return 1;
|
||||
};
|
||||
|
||||
const getUnit = (
|
||||
value: TEnvelopeExpirationPeriod | null | undefined,
|
||||
): TEnvelopeExpirationDurationPeriod['unit'] => {
|
||||
const getUnit = (value: TEnvelopeExpirationPeriod | null | undefined): TEnvelopeExpirationDurationPeriod['unit'] => {
|
||||
if (value && 'unit' in value) {
|
||||
return value.unit;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
|
||||
import type {
|
||||
TEnvelopeReminderDurationPeriod,
|
||||
TEnvelopeReminderPeriod,
|
||||
@@ -7,13 +5,8 @@ import type {
|
||||
} from '@documenso/lib/constants/envelope-reminder';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
import { Label } from '@documenso/ui/primitives/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { Plural, Trans } from '@lingui/react/macro';
|
||||
|
||||
type ReminderMode = 'enabled' | 'disabled' | 'inherit';
|
||||
|
||||
@@ -37,9 +30,7 @@ const getPeriodAmount = (period: TEnvelopeReminderPeriod | undefined): number =>
|
||||
return 1;
|
||||
};
|
||||
|
||||
const getPeriodUnit = (
|
||||
period: TEnvelopeReminderPeriod | undefined,
|
||||
): TEnvelopeReminderDurationPeriod['unit'] => {
|
||||
const getPeriodUnit = (period: TEnvelopeReminderPeriod | undefined): TEnvelopeReminderDurationPeriod['unit'] => {
|
||||
if (period && 'unit' in period) {
|
||||
return period.unit;
|
||||
}
|
||||
@@ -87,9 +78,7 @@ export const ReminderSettingsPicker = ({
|
||||
});
|
||||
};
|
||||
|
||||
const updateSendAfter = (
|
||||
updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>,
|
||||
) => {
|
||||
const updateSendAfter = (updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>) => {
|
||||
const newAmount = Math.max(1, Math.floor(updates.amount ?? sendAfterAmount));
|
||||
const newUnit = updates.unit ?? sendAfterUnit;
|
||||
|
||||
@@ -99,9 +88,7 @@ export const ReminderSettingsPicker = ({
|
||||
});
|
||||
};
|
||||
|
||||
const updateRepeatEvery = (
|
||||
updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>,
|
||||
) => {
|
||||
const updateRepeatEvery = (updates: Partial<{ amount: number; unit: TEnvelopeReminderDurationPeriod['unit'] }>) => {
|
||||
const newAmount = Math.max(1, Math.floor(updates.amount ?? repeatEveryAmount));
|
||||
const newUnit = updates.unit ?? repeatEveryUnit;
|
||||
|
||||
@@ -151,7 +138,7 @@ export const ReminderSettingsPicker = ({
|
||||
{mode === 'enabled' && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-sm text-muted-foreground">
|
||||
<Label className="text-muted-foreground text-sm">
|
||||
<Trans>Send first reminder after</Trans>
|
||||
</Label>
|
||||
|
||||
@@ -181,7 +168,7 @@ export const ReminderSettingsPicker = ({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-sm text-muted-foreground">
|
||||
<Label className="text-muted-foreground text-sm">
|
||||
<Trans>Then repeat every</Trans>
|
||||
</Label>
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { TooltipArrow } from '@radix-ui/react-tooltip';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../primitives/tooltip';
|
||||
@@ -26,21 +24,13 @@ const tooltipVariants = cva('font-semibold', {
|
||||
interface EnvelopeFieldToolTipProps extends VariantProps<typeof tooltipVariants> {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
field: Pick<
|
||||
Field,
|
||||
'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'
|
||||
>;
|
||||
field: Pick<Field, 'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a tooltip for a given field.
|
||||
*/
|
||||
export function EnvelopeFieldToolTip({
|
||||
children,
|
||||
color,
|
||||
className = '',
|
||||
field,
|
||||
}: EnvelopeFieldToolTipProps) {
|
||||
export function EnvelopeFieldToolTip({ children, color, className = '', field }: EnvelopeFieldToolTipProps) {
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -49,9 +39,7 @@ export function EnvelopeFieldToolTip({
|
||||
});
|
||||
|
||||
const calculateCoords = useCallback(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
|
||||
|
||||
if (!$page) {
|
||||
return;
|
||||
@@ -90,9 +78,7 @@ export function EnvelopeFieldToolTip({
|
||||
}, [calculateCoords]);
|
||||
|
||||
useEffect(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
const $page = document.querySelector<HTMLElement>(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
|
||||
|
||||
if (!$page) {
|
||||
return;
|
||||
@@ -124,10 +110,7 @@ export function EnvelopeFieldToolTip({
|
||||
<Tooltip delayDuration={0} open={!field.inserted || !field.fieldMeta}>
|
||||
<TooltipTrigger className="absolute inset-0 w-full"></TooltipTrigger>
|
||||
|
||||
<TooltipContent
|
||||
className={tooltipVariants({ color, className: cn(className, 'z-40') })}
|
||||
sideOffset={2}
|
||||
>
|
||||
<TooltipContent className={tooltipVariants({ color, className: cn(className, 'z-40') })} sideOffset={2}>
|
||||
{children}
|
||||
<TooltipArrow />
|
||||
</TooltipContent>
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
import type { Field } from '@prisma/client';
|
||||
import { TooltipArrow } from '@radix-ui/react-tooltip';
|
||||
import type { VariantProps } from 'class-variance-authority';
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
|
||||
import { cn } from '../..//lib/utils';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '../..//primitives/tooltip';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../..//primitives/tooltip';
|
||||
|
||||
const tooltipVariants = cva('font-semibold', {
|
||||
variants: {
|
||||
@@ -29,10 +23,7 @@ const tooltipVariants = cva('font-semibold', {
|
||||
interface FieldToolTipProps extends VariantProps<typeof tooltipVariants> {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
field: Pick<
|
||||
Field,
|
||||
'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'
|
||||
>;
|
||||
field: Pick<Field, 'id' | 'inserted' | 'fieldMeta' | 'positionX' | 'positionY' | 'width' | 'height' | 'page'>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { type Field, FieldType } from '@prisma/client';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import { useElementBounds } from '@documenso/lib/client-only/hooks/use-element-bounds';
|
||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||
import { useIsPageInDom } from '@documenso/lib/client-only/hooks/use-is-page-in-dom';
|
||||
import {
|
||||
PDF_VIEWER_CONTENT_SELECTOR,
|
||||
PDF_VIEWER_PAGE_SELECTOR,
|
||||
} from '@documenso/lib/constants/pdf-viewer';
|
||||
import { PDF_VIEWER_CONTENT_SELECTOR, PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||
import { type Field, FieldType } from '@prisma/client';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
import type { RecipientColorStyles } from '../../lib/recipient-colors';
|
||||
import { cn } from '../../lib/utils';
|
||||
@@ -21,17 +16,11 @@ export type FieldContainerPortalProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function FieldContainerPortal({
|
||||
field,
|
||||
children,
|
||||
className = '',
|
||||
}: FieldContainerPortalProps) {
|
||||
export function FieldContainerPortal({ field, children, className = '' }: FieldContainerPortalProps) {
|
||||
const alternativePortalRoot = document.getElementById('document-field-portal-root');
|
||||
|
||||
const coords = useFieldPageCoords(field);
|
||||
const $pageBounds = useElementBounds(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
const $pageBounds = useElementBounds(`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`);
|
||||
|
||||
const maxWidth = $pageBounds?.width ? $pageBounds.width - coords.x : undefined;
|
||||
|
||||
@@ -77,13 +66,7 @@ export type FieldRootContainerProps = {
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export function FieldRootContainer({
|
||||
field,
|
||||
children,
|
||||
color,
|
||||
className,
|
||||
readonly,
|
||||
}: FieldRootContainerProps) {
|
||||
export function FieldRootContainer({ field, children, color, className, readonly }: FieldRootContainerProps) {
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const isPageInDom = useIsPageInDom(field.page);
|
||||
|
||||
@@ -101,10 +84,7 @@ export function FieldRootContainer({
|
||||
// pick up the validation state immediately.
|
||||
const pdfContent = document.querySelector(PDF_VIEWER_CONTENT_SELECTOR);
|
||||
|
||||
if (
|
||||
pdfContent?.getAttribute('data-validate-fields') === 'true' &&
|
||||
isFieldUnsignedAndRequired(field)
|
||||
) {
|
||||
if (pdfContent?.getAttribute('data-validate-fields') === 'true' && isFieldUnsignedAndRequired(field)) {
|
||||
ref.current.setAttribute('data-validate', 'true');
|
||||
setIsValidating(true);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { DOCUMENT_AUTH_TYPES } from '@documenso/lib/constants/document-auth';
|
||||
import { RecipientActionAuth } from '@documenso/lib/types/document-auth';
|
||||
import { MultiSelect, type Option } from '@documenso/ui/primitives/multiselect';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
@@ -45,15 +42,11 @@ export const RecipientActionAuthSelect = ({
|
||||
|
||||
// Convert string array to Option array for MultiSelect
|
||||
const selectedOptions =
|
||||
(value
|
||||
?.map((val) => authOptions.find((option) => option.value === val))
|
||||
.filter(Boolean) as Option[]) || [];
|
||||
(value?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
|
||||
|
||||
// Convert default value to Option array
|
||||
const defaultOptions =
|
||||
(defaultValue
|
||||
?.map((val) => authOptions.find((option) => option.value === val))
|
||||
.filter(Boolean) as Option[]) || [];
|
||||
(defaultValue?.map((val) => authOptions.find((option) => option.value === val)).filter(Boolean) as Option[]) || [];
|
||||
|
||||
const handleChange = (options: Option[]) => {
|
||||
const values = options.map((option) => option.value);
|
||||
@@ -76,7 +69,7 @@ export const RecipientActionAuthSelect = ({
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
className={cn('absolute right-2 top-1/2 -translate-y-1/2', {
|
||||
className={cn('absolute top-1/2 right-2 -translate-y-1/2', {
|
||||
'right-8': selectedOptions.length > 0,
|
||||
})}
|
||||
>
|
||||
@@ -95,28 +88,25 @@ export const RecipientActionAuthSelect = ({
|
||||
</p>
|
||||
|
||||
<p className="mt-2">
|
||||
<Trans>
|
||||
These will override any global settings. Multiple methods can be selected.
|
||||
</Trans>
|
||||
<Trans>These will override any global settings. Multiple methods can be selected.</Trans>
|
||||
</p>
|
||||
|
||||
<ul className="ml-3.5 list-outside list-disc space-y-0.5 py-2">
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Inherit authentication method</strong> - Use the global action signing
|
||||
authentication method configured in the "General Settings" step
|
||||
<strong>Inherit authentication method</strong> - Use the global action signing authentication method
|
||||
configured in the "General Settings" step
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey
|
||||
configured via their settings
|
||||
<strong>Require passkey</strong> - The recipient must have an account and passkey configured via their
|
||||
settings
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<Trans>
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled
|
||||
via their settings
|
||||
<strong>Require 2FA</strong> - The recipient must have an account and 2FA enabled via their settings
|
||||
</Trans>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Popover, PopoverContent } from '@documenso/ui/primitives/popover';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { PopoverAnchor } from '@radix-ui/react-popover';
|
||||
|
||||
import { Popover, PopoverContent } from '@documenso/ui/primitives/popover';
|
||||
import type React from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { Command, CommandGroup, CommandItem } from '../../primitives/command';
|
||||
import { Input } from '../../primitives/input';
|
||||
@@ -77,11 +77,7 @@ export const RecipientAutoCompleteInput = ({
|
||||
{/* Not using <CommandEmpty /> here due to some weird behaviour */}
|
||||
{options.length === 0 && (
|
||||
<div className="px-2 py-1.5 text-sm">
|
||||
{loading ? (
|
||||
<Trans>Loading suggestions...</Trans>
|
||||
) : (
|
||||
<Trans>No suggestions found</Trans>
|
||||
)}
|
||||
{loading ? <Trans>Loading suggestions...</Trans> : <Trans>No suggestions found</Trans>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { ROLE_ICONS } from '@documenso/ui/primitives/recipient-role-icons';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { RecipientRole } from '@prisma/client';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import { ROLE_ICONS } from '@documenso/ui/primitives/recipient-role-icons';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
|
||||
@@ -21,17 +19,7 @@ export type RecipientRoleSelectProps = SelectProps & {
|
||||
};
|
||||
|
||||
export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSelectProps>(
|
||||
(
|
||||
{
|
||||
hideAssistantRole,
|
||||
hideCCerRole,
|
||||
hideViewerRole,
|
||||
hideApproverRole,
|
||||
isAssistantEnabled = true,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => (
|
||||
({ hideAssistantRole, hideCCerRole, hideViewerRole, hideApproverRole, isAssistantEnabled = true, ...props }, ref) => (
|
||||
<Select {...props}>
|
||||
<SelectTrigger ref={ref} className="w-[50px] bg-background p-2" title={props.value}>
|
||||
{/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */}
|
||||
@@ -51,9 +39,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
The recipient is required to sign the document for it to be completed.
|
||||
</Trans>
|
||||
<Trans>The recipient is required to sign the document for it to be completed.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -73,9 +59,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
The recipient is required to approve the document for it to be completed.
|
||||
</Trans>
|
||||
<Trans>The recipient is required to approve the document for it to be completed.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -96,9 +80,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
The recipient is required to view the document for it to be completed.
|
||||
</Trans>
|
||||
<Trans>The recipient is required to view the document for it to be completed.</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -120,8 +102,8 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
<TooltipContent className="z-9999 max-w-md p-4 text-foreground">
|
||||
<p>
|
||||
<Trans>
|
||||
The recipient is not required to take any action and receives a copy of the
|
||||
document after it is completed.
|
||||
The recipient is not required to take any action and receives a copy of the document after it is
|
||||
completed.
|
||||
</Trans>
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -134,10 +116,7 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
<SelectItem
|
||||
value={RecipientRole.ASSISTANT}
|
||||
disabled={!isAssistantEnabled}
|
||||
className={cn(
|
||||
!isAssistantEnabled &&
|
||||
'cursor-not-allowed opacity-50 data-[disabled]:pointer-events-auto',
|
||||
)}
|
||||
className={cn(!isAssistantEnabled && 'cursor-not-allowed opacity-50 data-[disabled]:pointer-events-auto')}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="flex w-[150px] items-center">
|
||||
@@ -152,14 +131,10 @@ export const RecipientRoleSelect = forwardRef<HTMLButtonElement, RecipientRoleSe
|
||||
<p>
|
||||
{isAssistantEnabled ? (
|
||||
<Trans>
|
||||
The recipient can prepare the document for later signers by pre-filling
|
||||
suggest values.
|
||||
The recipient can prepare the document for later signers by pre-filling suggest values.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
Assistant role is only available when the document is in sequential signing
|
||||
mode.
|
||||
</Trans>
|
||||
<Trans>Assistant role is only available when the document is in sequential signing mode.</Trans>
|
||||
)}
|
||||
</p>
|
||||
</TooltipContent>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { Signature } from '@prisma/client';
|
||||
import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion';
|
||||
import { P, match } from 'ts-pattern';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { match, P } from 'ts-pattern';
|
||||
|
||||
import { cn } from '../lib/utils';
|
||||
import { Card, CardContent } from '../primitives/card';
|
||||
@@ -17,19 +16,12 @@ export type SigningCardProps = {
|
||||
/**
|
||||
* 2D signing card.
|
||||
*/
|
||||
export const SigningCard = ({
|
||||
className,
|
||||
name,
|
||||
signature,
|
||||
signingCelebrationImage,
|
||||
}: SigningCardProps) => {
|
||||
export const SigningCard = ({ className, name, signature, signingCelebrationImage }: SigningCardProps) => {
|
||||
return (
|
||||
<div className={cn('relative w-full max-w-sm md:max-w-md', className)}>
|
||||
<SigningCardContent name={name} signature={signature} />
|
||||
|
||||
{signingCelebrationImage && (
|
||||
<SigningCardImage signingCelebrationImage={signingCelebrationImage} />
|
||||
)}
|
||||
{signingCelebrationImage && <SigningCardImage signingCelebrationImage={signingCelebrationImage} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -37,12 +29,7 @@ export const SigningCard = ({
|
||||
/**
|
||||
* 3D signing card that follows the mouse movement within a certain range.
|
||||
*/
|
||||
export const SigningCard3D = ({
|
||||
className,
|
||||
name,
|
||||
signature,
|
||||
signingCelebrationImage,
|
||||
}: SigningCardProps) => {
|
||||
export const SigningCard3D = ({ className, name, signature, signingCelebrationImage }: SigningCardProps) => {
|
||||
// Should use % based dimensions by calculating the window height/width.
|
||||
const boundary = 400;
|
||||
|
||||
@@ -127,12 +114,9 @@ export const SigningCard3D = ({
|
||||
}, [onMouseMove]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('relative w-full max-w-sm md:max-w-md', className)}
|
||||
style={{ perspective: 800 }}
|
||||
>
|
||||
<div className={cn('relative w-full max-w-sm md:max-w-md', className)} style={{ perspective: 800 }}>
|
||||
<motion.div
|
||||
className="bg-background w-full rounded-lg [--sheen-color:180_180_180] dark:[--sheen-color:200_200_200]"
|
||||
className="w-full rounded-lg bg-background [--sheen-color:180_180_180] dark:[--sheen-color:200_200_200]"
|
||||
ref={cardRef}
|
||||
style={{
|
||||
perspective: '800',
|
||||
@@ -145,9 +129,7 @@ export const SigningCard3D = ({
|
||||
<SigningCardContent className="bg-transparent" name={name} signature={signature} />
|
||||
</motion.div>
|
||||
|
||||
{signingCelebrationImage && (
|
||||
<SigningCardImage signingCelebrationImage={signingCelebrationImage} />
|
||||
)}
|
||||
{signingCelebrationImage && <SigningCardImage signingCelebrationImage={signingCelebrationImage} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -161,34 +143,25 @@ type SigningCardContentProps = {
|
||||
const SigningCardContent = ({ className, name, signature }: SigningCardContentProps) => {
|
||||
return (
|
||||
<Card
|
||||
className={cn(
|
||||
'group z-10 mx-auto flex aspect-[21/9] w-full items-center justify-center',
|
||||
className,
|
||||
)}
|
||||
className={cn('group z-10 mx-auto flex aspect-[21/9] w-full items-center justify-center', className)}
|
||||
degrees={-145}
|
||||
gradient
|
||||
>
|
||||
<CardContent
|
||||
className="font-signature p-6 text-center"
|
||||
className="p-6 text-center font-signature"
|
||||
style={{
|
||||
container: 'main',
|
||||
}}
|
||||
>
|
||||
{match(signature)
|
||||
.with({ signatureImageAsBase64: P.string }, (signature) => (
|
||||
<img
|
||||
src={signature.signatureImageAsBase64}
|
||||
alt="signature"
|
||||
className="h-full max-w-[100%] dark:invert"
|
||||
/>
|
||||
<img src={signature.signatureImageAsBase64} alt="signature" className="h-full max-w-[100%] dark:invert" />
|
||||
))
|
||||
.with({ typedSignature: P.string }, (signature) => (
|
||||
<span
|
||||
className="text-muted-foreground/60 group-hover:text-primary/80 break-all font-semibold duration-300"
|
||||
className="break-all font-semibold text-muted-foreground/60 duration-300 group-hover:text-primary/80"
|
||||
style={{
|
||||
fontSize: `max(min(4rem, ${(100 / signature.typedSignature.length / 2).toFixed(
|
||||
4,
|
||||
)}cqw), 1.875rem)`,
|
||||
fontSize: `max(min(4rem, ${(100 / signature.typedSignature.length / 2).toFixed(4)}cqw), 1.875rem)`,
|
||||
}}
|
||||
>
|
||||
{signature.typedSignature}
|
||||
@@ -196,7 +169,7 @@ const SigningCardContent = ({ className, name, signature }: SigningCardContentPr
|
||||
))
|
||||
.otherwise(() => (
|
||||
<span
|
||||
className="text-muted-foreground/60 group-hover:text-primary/80 break-all font-semibold duration-300"
|
||||
className="break-all font-semibold text-muted-foreground/60 duration-300 group-hover:text-primary/80"
|
||||
style={{
|
||||
fontSize: `max(min(4rem, ${(100 / name.length / 2).toFixed(4)}cqw), 1.875rem)`,
|
||||
}}
|
||||
|
||||
@@ -1,50 +1,36 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { TemplateType } from '@prisma/client';
|
||||
import type { SelectProps } from '@radix-ui/react-select';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
export type TemplateTypeSelectProps = SelectProps;
|
||||
|
||||
export const TemplateTypeSelect = forwardRef<HTMLButtonElement, TemplateTypeSelectProps>(
|
||||
({ ...props }, ref) => {
|
||||
useLingui();
|
||||
export const TemplateTypeSelect = forwardRef<HTMLButtonElement, TemplateTypeSelectProps>(({ ...props }, ref) => {
|
||||
useLingui();
|
||||
|
||||
return (
|
||||
<Select {...props}>
|
||||
<SelectTrigger ref={ref} className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
return (
|
||||
<Select {...props}>
|
||||
<SelectTrigger ref={ref} className="bg-background">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
<SelectItem value={TemplateType.PRIVATE}>{t`Private`}</SelectItem>
|
||||
<SelectItem value={TemplateType.PUBLIC}>{t`Public`}</SelectItem>
|
||||
<SelectItem value={TemplateType.ORGANISATION}>{t`Organisation`}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
);
|
||||
<SelectContent>
|
||||
<SelectItem value={TemplateType.PRIVATE}>{t`Private`}</SelectItem>
|
||||
<SelectItem value={TemplateType.PUBLIC}>{t`Public`}</SelectItem>
|
||||
<SelectItem value={TemplateType.ORGANISATION}>{t`Organisation`}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
});
|
||||
|
||||
TemplateTypeSelect.displayName = 'TemplateTypeSelect';
|
||||
|
||||
export const TemplateTypeTooltip = ({
|
||||
organisationTeamCount,
|
||||
}: {
|
||||
organisationTeamCount: number;
|
||||
}) => {
|
||||
export const TemplateTypeTooltip = ({ organisationTeamCount }: { organisationTeamCount: number }) => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
@@ -65,8 +51,8 @@ export const TemplateTypeTooltip = ({
|
||||
{organisationTeamCount >= 2 && (
|
||||
<p>
|
||||
<Trans>
|
||||
<strong>Organisation</strong> templates are shared across all teams in your
|
||||
organisation but can only be edited by the owning team.
|
||||
<strong>Organisation</strong> templates are shared across all teams in your organisation but can only be
|
||||
edited by the owning team.
|
||||
</Trans>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||
|
||||
export const SignatureIcon: LucideIcon = forwardRef(
|
||||
(
|
||||
{ size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props },
|
||||
ref,
|
||||
) => {
|
||||
({ size = 24, color = 'currentColor', strokeWidth = 1.33, absoluteStrokeWidth, ...props }, ref) => {
|
||||
return (
|
||||
<svg
|
||||
ref={ref}
|
||||
@@ -20,9 +16,7 @@ export const SignatureIcon: LucideIcon = forwardRef(
|
||||
<path
|
||||
d="M1.5 11H14.5M1.5 14C1.5 14 8.72 2 4.86938 2H4.875C2.01 2 1.97437 14.0694 8 6.51188V6.5C8 6.5 9 11.3631 11.5 7.52375V7.5C11.5 7.5 11.5 9 14.5 9"
|
||||
stroke={color}
|
||||
strokeWidth={
|
||||
absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth
|
||||
}
|
||||
strokeWidth={absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
@@ -1,31 +1,28 @@
|
||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||
|
||||
export const VerifiedIcon: LucideIcon = forwardRef(
|
||||
({ size = 24, color = 'currentColor', ...props }, ref) => {
|
||||
return (
|
||||
<svg
|
||||
ref={ref}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g id="badge, verified, award">
|
||||
<path
|
||||
id="Icon"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.5457 2.89094C11.5779 1.70302 13.4223 1.70302 14.4545 2.89094L15.2585 3.81628C15.3917 3.96967 15.5947 4.04354 15.7954 4.0117L17.0061 3.81965C18.5603 3.57309 19.9732 4.75869 20.0003 6.33214L20.0214 7.55778C20.0249 7.76096 20.1329 7.94799 20.3071 8.05261L21.358 8.6837C22.7071 9.49389 23.0274 11.3103 22.0368 12.5331L21.2651 13.4855C21.1372 13.6434 21.0997 13.8561 21.1659 14.0482L21.5652 15.2072C22.0779 16.695 21.1557 18.2923 19.6109 18.5922L18.4075 18.8258C18.208 18.8646 18.0426 19.0034 17.9698 19.1931L17.5308 20.3376C16.9672 21.8069 15.234 22.4378 13.8578 21.6745L12.7858 21.08C12.6081 20.9814 12.3921 20.9814 12.2144 21.08L11.1424 21.6745C9.76623 22.4378 8.033 21.8069 7.4694 20.3376L7.03038 19.1931C6.9576 19.0034 6.79216 18.8646 6.59268 18.8258L5.38932 18.5922C3.84448 18.2923 2.92224 16.695 3.43495 15.2072L3.83431 14.0482C3.90052 13.8561 3.86302 13.6434 3.7351 13.4855L2.96343 12.5331C1.97279 11.3103 2.29307 9.49389 3.64218 8.6837L4.69306 8.05261C4.86728 7.94799 4.97526 7.76096 4.97875 7.55778L4.99985 6.33214C5.02694 4.75869 6.43987 3.57309 7.99413 3.81965L9.20481 4.0117C9.40551 4.04354 9.60845 3.96967 9.74173 3.81628L10.5457 2.89094ZM15.7072 11.2071C16.0977 10.8166 16.0977 10.1834 15.7072 9.79289C15.3167 9.40237 14.6835 9.40237 14.293 9.79289L11.5001 12.5858L10.7072 11.7929C10.3167 11.4024 9.68351 11.4024 9.29298 11.7929C8.90246 12.1834 8.90246 12.8166 9.29298 13.2071L10.4394 14.3536C11.0252 14.9393 11.975 14.9393 12.5608 14.3536L15.7072 11.2071Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
},
|
||||
);
|
||||
export const VerifiedIcon: LucideIcon = forwardRef(({ size = 24, color = 'currentColor', ...props }, ref) => {
|
||||
return (
|
||||
<svg
|
||||
ref={ref}
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<g id="badge, verified, award">
|
||||
<path
|
||||
id="Icon"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10.5457 2.89094C11.5779 1.70302 13.4223 1.70302 14.4545 2.89094L15.2585 3.81628C15.3917 3.96967 15.5947 4.04354 15.7954 4.0117L17.0061 3.81965C18.5603 3.57309 19.9732 4.75869 20.0003 6.33214L20.0214 7.55778C20.0249 7.76096 20.1329 7.94799 20.3071 8.05261L21.358 8.6837C22.7071 9.49389 23.0274 11.3103 22.0368 12.5331L21.2651 13.4855C21.1372 13.6434 21.0997 13.8561 21.1659 14.0482L21.5652 15.2072C22.0779 16.695 21.1557 18.2923 19.6109 18.5922L18.4075 18.8258C18.208 18.8646 18.0426 19.0034 17.9698 19.1931L17.5308 20.3376C16.9672 21.8069 15.234 22.4378 13.8578 21.6745L12.7858 21.08C12.6081 20.9814 12.3921 20.9814 12.2144 21.08L11.1424 21.6745C9.76623 22.4378 8.033 21.8069 7.4694 20.3376L7.03038 19.1931C6.9576 19.0034 6.79216 18.8646 6.59268 18.8258L5.38932 18.5922C3.84448 18.2923 2.92224 16.695 3.43495 15.2072L3.83431 14.0482C3.90052 13.8561 3.86302 13.6434 3.7351 13.4855L2.96343 12.5331C1.97279 11.3103 2.29307 9.49389 3.64218 8.6837L4.69306 8.05261C4.86728 7.94799 4.97526 7.76096 4.97875 7.55778L4.99985 6.33214C5.02694 4.75869 6.43987 3.57309 7.99413 3.81965L9.20481 4.0117C9.40551 4.04354 9.60845 3.96967 9.74173 3.81628L10.5457 2.89094ZM15.7072 11.2071C16.0977 10.8166 16.0977 10.1834 15.7072 9.79289C15.3167 9.40237 14.6835 9.40237 14.293 9.79289L11.5001 12.5858L10.7072 11.7929C10.3167 11.4024 9.68351 11.4024 9.29298 11.7929C8.90246 12.1834 8.90246 12.8166 9.29298 13.2071L10.4394 14.3536C11.0252 14.9393 11.975 14.9393 12.5608 14.3536L15.7072 11.2071Z"
|
||||
fill={color}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
|
||||
VerifiedIcon.displayName = 'VerifiedIcon';
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { ErrorCode, type FileRejection } from 'react-dropzone';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app';
|
||||
|
||||
export const buildDropzoneRejectionDescription = (fileRejections: FileRejection[]) => {
|
||||
const errorCode = fileRejections[0]?.errors[0]?.code;
|
||||
|
||||
return match(errorCode)
|
||||
.with(
|
||||
ErrorCode.FileTooLarge,
|
||||
() => msg`File is larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`,
|
||||
)
|
||||
.with(ErrorCode.FileTooLarge, () => msg`File is larger than ${APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB`)
|
||||
.with(ErrorCode.FileInvalidType, () => msg`Only PDF files are allowed`)
|
||||
.with(ErrorCode.FileTooSmall, () => msg`File is too small`)
|
||||
.with(ErrorCode.TooManyFiles, () => msg`Only one file can be uploaded at a time`)
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
"index.tsx"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"clean": "rimraf node_modules"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user