feat: highlight unsigned required fields in red on signing page

Add a 'required' key to TRecipientColor with a red ring and translucent
hover fill, mirroring the recipient color shape.

Both the signing DOM container and the envelope canvas renderer now
route unsigned required fields to 'required', falling back to the
default green once a field is inserted.

On the envelope canvas, orange is now reserved for the single 'next'
field the tooltip points at (recipientFieldsRemaining[0]). Other
unsigned required fields stay red when the pending-field tooltip
is active.
This commit is contained in:
ephraimduncan
2026-04-23 16:43:48 +00:00
parent e063af628f
commit 4c486fc1e9
3 changed files with 25 additions and 4 deletions
@@ -7,6 +7,7 @@ import { X } from 'lucide-react';
import { type TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { FieldRootContainer } from '@documenso/ui/components/field/field';
import { getRecipientColorStyles } from '@documenso/ui/lib/recipient-colors';
@@ -131,7 +132,9 @@ export const DocumentSigningFieldContainer = ({
return (
<FieldRootContainer
color={getRecipientColorStyles(field.fieldMeta?.readOnly ? 'readOnly' : 0)}
color={getRecipientColorStyles(
field.fieldMeta?.readOnly ? 'readOnly' : isFieldUnsignedAndRequired(field) ? 'required' : 0,
)}
field={field}
>
{!field.inserted && !loading && !readOnlyField && (
@@ -138,10 +138,15 @@ export const EnvelopeSignerPageRenderer = ({ pageData }: { pageData: PageRenderD
const fieldToRender = ZFullFieldSchema.parse(unparsedField);
const isNextPendingField =
showPendingFieldTooltip && recipientFieldsRemaining[0]?.id === fieldToRender.id;
const color = fieldToRender.fieldMeta?.readOnly
? 'readOnly'
: showPendingFieldTooltip && isFieldUnsignedAndRequired(fieldToRender)
? 'orange'
: isFieldUnsignedAndRequired(fieldToRender)
? isNextPendingField
? 'orange'
: 'required'
: 'green';
const { fieldGroup } = renderField({
+14 -1
View File
@@ -1,7 +1,7 @@
import { colord } from 'colord';
import { once } from 'remeda';
export type TRecipientColor = 'readOnly' | (typeof AVAILABLE_RECIPIENT_COLORS)[number];
export type TRecipientColor = 'readOnly' | 'required' | (typeof AVAILABLE_RECIPIENT_COLORS)[number];
export type RecipientColorStyles = {
base: string;
@@ -33,6 +33,19 @@ const RECIPIENT_COLOR_STYLES: Record<TRecipientColor, () => RecipientColorStyles
'ring-2 ring-recipient-green shadow-[0_0_0_5px_hsl(var(--recipient-green)/10%),0_0_0_2px_hsl(var(--recipient-green)/60%),0_0_0_0.5px_hsl(var(--recipient-green))]',
comboBoxItem: '',
}),
required: (): RecipientColorStyles => ({
base: 'ring-red-400 hover:bg-red-400/30',
baseRing: 'rgba(248, 113, 113, 1)',
baseRingHover: 'rgba(248, 113, 113, 0.3)',
baseTextHover: 'rgba(248, 113, 113, 1)',
fieldButton: 'border-red-400 hover:border-red-400',
fieldButtonText: '',
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: '',
comboBoxTrigger:
'ring-2 ring-red-400 shadow-[0_0_0_5px_rgba(248,113,113,0.1),0_0_0_2px_rgba(248,113,113,0.6),0_0_0_0.5px_rgba(248,113,113,1)]',
comboBoxItem: '',
}),
green: once(() => generateStyles('green')),
blue: once(() => generateStyles('blue')),
purple: once(() => generateStyles('purple')),