refactor: avoid recipient color duplication (#2355)

This commit is contained in:
Ted Liang
2026-03-13 15:52:15 +11:00
committed by GitHub
parent 1ee6ec87a2
commit 83fbc70a1c
16 changed files with 128 additions and 220 deletions
@@ -16,7 +16,7 @@ import { type TFieldMetaSchema, ZFieldMetaSchema } from '@documenso/lib/types/fi
import { nanoid } from '@documenso/lib/universal/id'; import { nanoid } from '@documenso/lib/universal/id';
import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers'; import { ADVANCED_FIELD_TYPES_WITH_OPTIONAL_SETTING } from '@documenso/lib/utils/advanced-fields-helpers';
import { getDocumentDataUrlForPdfViewer } from '@documenso/lib/utils/envelope-download'; import { getDocumentDataUrlForPdfViewer } from '@documenso/lib/utils/envelope-download';
import { useRecipientColors } from '@documenso/ui/lib/recipient-colors'; import { getRecipientColorStyles } from '@documenso/ui/lib/recipient-colors';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button'; import { Button } from '@documenso/ui/primitives/button';
import { FieldItem } from '@documenso/ui/primitives/document-flow/field-item'; import { FieldItem } from '@documenso/ui/primitives/document-flow/field-item';
@@ -155,9 +155,7 @@ export const ConfigureFieldsView = ({
}); });
const selectedRecipientIndex = recipients.findIndex((r) => r.id === selectedRecipient?.id); const selectedRecipientIndex = recipients.findIndex((r) => r.id === selectedRecipient?.id);
const selectedRecipientStyles = useRecipientColors( const selectedRecipientStyles = getRecipientColorStyles(selectedRecipientIndex);
selectedRecipientIndex === -1 ? 0 : selectedRecipientIndex,
);
const form = useForm<TConfigureFieldsFormSchema>({ const form = useForm<TConfigureFieldsFormSchema>({
defaultValues: { defaultValues: {
@@ -9,7 +9,7 @@ import { type TRecipientActionAuth } from '@documenso/lib/types/document-auth';
import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta'; import { ZFieldMetaSchema } from '@documenso/lib/types/field-meta';
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { FieldRootContainer } from '@documenso/ui/components/field/field'; import { FieldRootContainer } from '@documenso/ui/components/field/field';
import { RECIPIENT_COLOR_STYLES } from '@documenso/ui/lib/recipient-colors'; import { getRecipientColorStyles } from '@documenso/ui/lib/recipient-colors';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
@@ -131,18 +131,16 @@ export const DocumentSigningFieldContainer = ({
return ( return (
<FieldRootContainer <FieldRootContainer
color={ color={getRecipientColorStyles(field.fieldMeta?.readOnly ? 'readOnly' : 0)}
field.fieldMeta?.readOnly ? RECIPIENT_COLOR_STYLES.readOnly : RECIPIENT_COLOR_STYLES.green field={field}
} >
field={field} {!field.inserted && !loading && !readOnlyField && (
> <button
{!field.inserted && !loading && !readOnlyField && ( type="submit"
<button className="absolute inset-0 z-10 h-full w-full rounded-[2px]"
type="submit" onClick={async () => handleInsertField()}
className="absolute inset-0 z-10 h-full w-full rounded-[2px]" />
onClick={async () => handleInsertField()} )}
/>
)}
{type === 'Checkbox' && field.inserted && !loading && !readOnlyField && ( {type === 'Checkbox' && field.inserted && !loading && !readOnlyField && (
<button <button
@@ -23,7 +23,7 @@ import { FIELD_META_DEFAULT_VALUES } from '@documenso/lib/types/field-meta';
import { nanoid } from '@documenso/lib/universal/id'; import { nanoid } from '@documenso/lib/universal/id';
import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients'; import { canRecipientFieldsBeModified } from '@documenso/lib/utils/recipients';
import { SignatureIcon } from '@documenso/ui/icons/signature'; import { SignatureIcon } from '@documenso/ui/icons/signature';
import { RECIPIENT_COLOR_STYLES } from '@documenso/ui/lib/recipient-colors'; import { getRecipientColorStyles } from '@documenso/ui/lib/recipient-colors';
import { cn } from '@documenso/ui/lib/utils'; import { cn } from '@documenso/ui/lib/utils';
import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types'; import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/types';
@@ -253,9 +253,10 @@ export const EnvelopeEditorFieldDragDrop = ({
}; };
}, [onMouseClick, onMouseMove, selectedField]); }, [onMouseClick, onMouseMove, selectedField]);
const selectedRecipientColor = useMemo(() => { const selectedRecipientStyles = useMemo(
return selectedRecipientId ? getRecipientColorKey(selectedRecipientId) : 'green'; () => getRecipientColorStyles(getRecipientColorKey(selectedRecipientId ?? -1)),
}, [selectedRecipientId, getRecipientColorKey]); [selectedRecipientId, getRecipientColorKey],
);
return ( return (
<> <>
@@ -270,21 +271,14 @@ export const EnvelopeEditorFieldDragDrop = ({
data-selected={selectedField === field.type ? true : undefined} data-selected={selectedField === field.type ? true : undefined}
className={cn( className={cn(
'group flex h-12 cursor-pointer items-center justify-center rounded-lg border border-border px-4 transition-colors', 'group flex h-12 cursor-pointer items-center justify-center rounded-lg border border-border px-4 transition-colors',
RECIPIENT_COLOR_STYLES[selectedRecipientColor].fieldButton, selectedRecipientStyles.fieldButton,
)} )}
> >
<p <p
className={cn( className={cn(
'flex items-center justify-center gap-x-1.5 font-noto text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground', 'flex items-center justify-center gap-x-1.5 font-noto text-sm font-normal text-muted-foreground group-data-[selected]:text-foreground',
field.className, field.className,
{ selectedRecipientStyles.fieldButtonText,
'group-hover:text-recipient-green': selectedRecipientColor === 'green',
'group-hover:text-recipient-blue': selectedRecipientColor === 'blue',
'group-hover:text-recipient-purple': selectedRecipientColor === 'purple',
'group-hover:text-recipient-orange': selectedRecipientColor === 'orange',
'group-hover:text-recipient-yellow': selectedRecipientColor === 'yellow',
'group-hover:text-recipient-pink': selectedRecipientColor === 'pink',
},
)} )}
> >
{field.type !== FieldType.SIGNATURE && <field.icon className="h-4 w-4" />} {field.type !== FieldType.SIGNATURE && <field.icon className="h-4 w-4" />}
@@ -298,7 +292,7 @@ export const EnvelopeEditorFieldDragDrop = ({
<div <div
className={cn( 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 font-noto text-muted-foreground ring-2 transition duration-200 [container-type:size]', 'dark:text-muted-background pointer-events-none fixed z-50 flex cursor-pointer flex-col items-center justify-center rounded-[2px] bg-white font-noto text-muted-foreground ring-2 transition duration-200 [container-type:size]',
RECIPIENT_COLOR_STYLES[selectedRecipientColor].base, selectedRecipientStyles.base,
selectedField === FieldType.SIGNATURE && 'font-signature', selectedField === FieldType.SIGNATURE && 'font-signature',
{ {
'-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds, '-rotate-6 scale-90 opacity-50 dark:bg-black/20': !isFieldWithinBounds,
@@ -59,12 +59,8 @@ export const EnvelopeRecipientSelector = ({
role="combobox" role="combobox"
className={cn( className={cn(
'justify-between bg-background font-normal text-muted-foreground hover:text-foreground', 'justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
getRecipientColorStyles( getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id))
Math.max( .comboBoxTrigger,
recipients.findIndex((r) => r.id === selectedRecipient?.id),
0,
),
).comboxBoxTrigger,
className, className,
)} )}
> >
@@ -197,12 +193,8 @@ export const EnvelopeRecipientSelectorCommand = ({
key={recipient.id} key={recipient.id}
className={cn( className={cn(
'px-2 last:mb-1 [&:not(:first-child)]:mt-1', 'px-2 last:mb-1 [&:not(:first-child)]:mt-1',
getRecipientColorStyles( getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id))
Math.max( .comboBoxItem,
recipients.findIndex((r) => r.id === recipient.id),
0,
),
).comboxBoxItem,
{ {
'text-muted-foreground': recipient.sendStatus === SendStatus.SENT, 'text-muted-foreground': recipient.sendStatus === SendStatus.SENT,
'cursor-not-allowed': isRecipientDisabled(recipient.id), 'cursor-not-allowed': isRecipientDisabled(recipient.id),
@@ -32,7 +32,6 @@ import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types'; import type { TSignEnvelopeFieldValue } from '@documenso/trpc/server/envelope-router/sign-envelope-field.types';
import { EnvelopeRecipientFieldTooltip } from '@documenso/ui/components/document/envelope-recipient-field-tooltip'; import { EnvelopeRecipientFieldTooltip } from '@documenso/ui/components/document/envelope-recipient-field-tooltip';
import { EnvelopeFieldToolTip } from '@documenso/ui/components/field/envelope-field-tooltip'; import { EnvelopeFieldToolTip } from '@documenso/ui/components/field/envelope-field-tooltip';
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { useEmbedSigningContext } from '~/components/embed/embed-signing-context'; import { useEmbedSigningContext } from '~/components/embed/embed-signing-context';
@@ -139,13 +138,11 @@ export const EnvelopeSignerPageRenderer = ({ pageData }: { pageData: PageRenderD
const fieldToRender = ZFullFieldSchema.parse(unparsedField); const fieldToRender = ZFullFieldSchema.parse(unparsedField);
let color: TRecipientColor = 'green'; const color = fieldToRender.fieldMeta?.readOnly
? 'readOnly'
if (fieldToRender.fieldMeta?.readOnly) { : showPendingFieldTooltip && isFieldUnsignedAndRequired(fieldToRender)
color = 'readOnly'; ? 'orange'
} else if (showPendingFieldTooltip && isFieldUnsignedAndRequired(fieldToRender)) { : 'green';
color = 'orange';
}
const { fieldGroup } = renderField({ const { fieldGroup } = renderField({
scale, scale,
+2 -3
View File
@@ -1,11 +1,10 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
const baseConfig = require('@documenso/tailwind-config'); const baseConfig = require('@documenso/ui/tailwind.config.cjs');
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
...baseConfig, presets: [baseConfig],
content: [ content: [
...baseConfig.content,
'./app/**/*.{ts,tsx}', './app/**/*.{ts,tsx}',
`${path.join(require.resolve('@documenso/ui'), '..')}/components/**/*.{ts,tsx}`, `${path.join(require.resolve('@documenso/ui'), '..')}/components/**/*.{ts,tsx}`,
`${path.join(require.resolve('@documenso/ui'), '..')}/icons/**/*.{ts,tsx}`, `${path.join(require.resolve('@documenso/ui'), '..')}/icons/**/*.{ts,tsx}`,
@@ -15,7 +15,7 @@ import type { TSetEnvelopeFieldsResponse } from '@documenso/trpc/server/envelope
import type { TSetEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/set-envelope-recipients.types'; import type { TSetEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/set-envelope-recipients.types';
import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/update-envelope.types'; import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/update-envelope.types';
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors'; import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
import { AVAILABLE_RECIPIENT_COLORS } from '@documenso/ui/lib/recipient-colors'; import { getRecipientColor } from '@documenso/ui/lib/recipient-colors';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import type { TDocumentEmailSettings } from '../../types/document-email'; import type { TDocumentEmailSettings } from '../../types/document-email';
@@ -346,15 +346,8 @@ export const EnvelopeEditorProvider = ({
}; };
const getRecipientColorKey = useCallback( const getRecipientColorKey = useCallback(
(recipientId: number) => { (recipientId: number) =>
const recipientIndex = envelope.recipients.findIndex( getRecipientColor(envelope.recipients.findIndex((r) => r.id === recipientId)),
(recipient) => recipient.id === recipientId,
);
return AVAILABLE_RECIPIENT_COLORS[
Math.max(recipientIndex, 0) % AVAILABLE_RECIPIENT_COLORS.length
];
},
[envelope.recipients], [envelope.recipients],
); );
@@ -6,7 +6,7 @@ import { type Field, type Recipient } from '@prisma/client';
import type { DocumentDataVersion } from '@documenso/lib/types/document'; import type { DocumentDataVersion } from '@documenso/lib/types/document';
import { getDocumentDataUrl } from '@documenso/lib/utils/envelope-download'; import { getDocumentDataUrl } from '@documenso/lib/utils/envelope-download';
import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors'; import type { TRecipientColor } from '@documenso/ui/lib/recipient-colors';
import { AVAILABLE_RECIPIENT_COLORS } from '@documenso/ui/lib/recipient-colors'; import { getRecipientColor } from '@documenso/ui/lib/recipient-colors';
import type { TEnvelope } from '../../types/envelope'; import type { TEnvelope } from '../../types/envelope';
import type { FieldRenderMode } from '../../universal/field-renderer/render-field'; import type { FieldRenderMode } from '../../universal/field-renderer/render-field';
@@ -198,13 +198,7 @@ export const EnvelopeRenderProvider = ({
); );
const getRecipientColorKey = useCallback( const getRecipientColorKey = useCallback(
(recipientId: number) => { (recipientId: number) => getRecipientColor(recipientIds.findIndex((id) => id === recipientId)),
const recipientIndex = recipientIds.findIndex((id) => id === recipientId);
return AVAILABLE_RECIPIENT_COLORS[
Math.max(recipientIndex, 0) % AVAILABLE_RECIPIENT_COLORS.length
];
},
[recipientIds], [recipientIds],
); );
@@ -2,7 +2,7 @@ import Konva from 'konva';
import { import {
DEFAULT_RECT_BACKGROUND, DEFAULT_RECT_BACKGROUND,
RECIPIENT_COLOR_STYLES, getRecipientColorStyles,
} from '@documenso/ui/lib/recipient-colors'; } from '@documenso/ui/lib/recipient-colors';
import type { FieldToRender, RenderFieldElementOptions } from './field-renderer'; import type { FieldToRender, RenderFieldElementOptions } from './field-renderer';
@@ -70,7 +70,7 @@ export const upsertFieldRect = (
width: fieldWidth, width: fieldWidth,
height: fieldHeight, height: fieldHeight,
fill: DEFAULT_RECT_BACKGROUND, fill: DEFAULT_RECT_BACKGROUND,
stroke: color ? RECIPIENT_COLOR_STYLES[color].baseRing : '#e5e7eb', stroke: color ? getRecipientColorStyles(color).baseRing : '#e5e7eb',
strokeWidth: 2, strokeWidth: 2,
cornerRadius: 2, cornerRadius: 2,
strokeScaleEnabled: false, strokeScaleEnabled: false,
@@ -150,7 +150,7 @@ export const createFieldHoverInteraction = ({
return; return;
} }
const hoverColor = RECIPIENT_COLOR_STYLES[options.color].baseRingHover; const hoverColor = getRecipientColorStyles(options.color).baseRingHover;
fieldGroup.on('mouseover', () => { fieldGroup.on('mouseover', () => {
const layer = fieldRect.getLayer(); const layer = fieldRect.getLayer();
@@ -110,10 +110,7 @@ export const DocumentReadOnlyFields = ({
color={ color={
showRecipientColors showRecipientColors
? getRecipientColorStyles( ? getRecipientColorStyles(
Math.max( recipientIds.findIndex((id) => id === field.recipientId),
recipientIds.findIndex((id) => id === field.recipientId),
0,
),
) )
: undefined : undefined
} }
+71 -112
View File
@@ -1,9 +1,7 @@
// !: We declare all of our classes here since TailwindCSS will remove any unused CSS classes, import { colord } from 'colord';
// !: therefore doing this at runtime is not possible without whitelisting a set of classnames. import { once } from 'remeda';
// !:
// !: This will later be improved as we move to a CSS variable approach and rotate the lightness
export type RecipientColorMap = Record<number, RecipientColorStyles>; export type TRecipientColor = 'readOnly' | (typeof AVAILABLE_RECIPIENT_COLORS)[number];
export type RecipientColorStyles = { export type RecipientColorStyles = {
base: string; base: string;
@@ -11,128 +9,89 @@ export type RecipientColorStyles = {
baseRingHover: string; baseRingHover: string;
baseTextHover: string; baseTextHover: string;
fieldButton: string; fieldButton: string;
fieldButtonText: string;
fieldItem: string; fieldItem: string;
fieldItemInitials: string; fieldItemInitials: string;
comboxBoxTrigger: string; comboBoxTrigger: string;
comboxBoxItem: string; comboBoxItem: string;
}; };
export const DEFAULT_RECT_BACKGROUND = 'rgba(255, 255, 255, 0.95)'; export const DEFAULT_RECT_BACKGROUND = 'rgba(255, 255, 255, 0.95)';
// !: values of the declared variable to do all the background, border and shadow styles. // !: values of the declared variable to do all the background, border and shadow styles.
export const RECIPIENT_COLOR_STYLES = { const RECIPIENT_COLOR_STYLES: Record<TRecipientColor, () => RecipientColorStyles> = {
readOnly: { readOnly: (): RecipientColorStyles => ({
base: 'ring-neutral-400', base: 'ring-neutral-400',
baseRing: 'rgba(176, 176, 176, 1)', baseRing: 'rgba(176, 176, 176, 1)',
baseRingHover: 'rgba(176, 176, 176, 1)', baseRingHover: 'rgba(176, 176, 176, 1)',
baseTextHover: 'rgba(176, 176, 176, 1)', baseTextHover: 'rgba(176, 176, 176, 1)',
fieldButton: 'border-neutral-400 hover:border-neutral-400', fieldButton: 'border-neutral-400 hover:border-neutral-400',
fieldButtonText: '',
fieldItem: 'group/field-item rounded-[2px]', fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: '', fieldItemInitials: '',
comboxBoxTrigger: comboBoxTrigger:
'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))]', '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))]',
comboxBoxItem: '', comboBoxItem: '',
}, }),
green: once(() => generateStyles('green')),
green: { blue: once(() => generateStyles('blue')),
base: 'ring-recipient-green hover:bg-recipient-green/30', purple: once(() => generateStyles('purple')),
baseRing: 'rgba(122, 195, 85, 1)', orange: once(() => generateStyles('orange')),
baseRingHover: 'rgba(122, 195, 85, 0.3)', yellow: once(() => generateStyles('yellow')),
baseTextHover: 'rgba(122, 195, 85, 1)', pink: once(() => generateStyles('pink')),
fieldButton: 'hover:border-recipient-green hover:bg-recipient-green/30 ',
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: 'group-hover/field-item:bg-recipient-green',
comboxBoxTrigger:
'ring-2 ring-recipient-green hover:bg-recipient-green/15 active:bg-recipient-green/15 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))]',
comboxBoxItem: 'hover:bg-recipient-green/15 active:bg-recipient-green/15',
},
blue: {
base: 'ring-recipient-blue hover:bg-recipient-blue/30',
baseRing: 'rgba(56, 123, 199, 1)',
baseRingHover: 'rgba(56, 123, 199, 0.3)',
baseTextHover: 'rgba(56, 123, 199, 1)',
fieldButton: 'hover:border-recipient-blue hover:bg-recipient-blue/30',
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: 'group-hover/field-item:bg-recipient-blue',
comboxBoxTrigger:
'ring-2 ring-recipient-blue hover:bg-recipient-blue/15 active:bg-recipient-blue/15 shadow-[0_0_0_5px_hsl(var(--recipient-blue)/10%),0_0_0_2px_hsl(var(--recipient-blue)/60%),0_0_0_0.5px_hsl(var(--recipient-blue))]',
comboxBoxItem: 'ring-recipient-blue hover:bg-recipient-blue/15 active:bg-recipient-blue/15',
},
purple: {
base: 'ring-recipient-purple hover:bg-recipient-purple/30',
baseRing: 'rgba(151, 71, 255, 1)',
baseRingHover: 'rgba(151, 71, 255, 0.3)',
baseTextHover: 'rgba(151, 71, 255, 1)',
fieldButton: 'hover:border-recipient-purple hover:bg-recipient-purple/30',
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: 'group-hover/field-item:bg-recipient-purple',
comboxBoxTrigger:
'ring-2 ring-recipient-purple hover:bg-recipient-purple/15 active:bg-recipient-purple/15 shadow-[0_0_0_5px_hsl(var(--recipient-purple)/10%),0_0_0_2px_hsl(var(--recipient-purple)/60%),0_0_0_0.5px_hsl(var(--recipient-purple))]',
comboxBoxItem: 'hover:bg-recipient-purple/15 active:bg-recipient-purple/15',
},
orange: {
base: 'ring-recipient-orange hover:bg-recipient-orange/30',
baseRing: 'rgba(246, 159, 30, 1)',
baseRingHover: 'rgba(246, 159, 30, 0.3)',
baseTextHover: 'rgba(246, 159, 30, 1)',
fieldButton: 'hover:border-recipient-orange hover:bg-recipient-orange/30',
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: 'group-hover/field-item:bg-recipient-orange',
comboxBoxTrigger:
'ring-2 ring-recipient-orange hover:bg-recipient-orange/15 active:bg-recipient-orange/15 shadow-[0_0_0_5px_hsl(var(--recipient-orange)/10%),0_0_0_2px_hsl(var(--recipient-orange)/60%),0_0_0_0.5px_hsl(var(--recipient-orange))]',
comboxBoxItem: 'hover:bg-recipient-orange/15 active:bg-recipient-orange/15',
},
yellow: {
base: 'ring-recipient-yellow hover:bg-recipient-yellow/30',
baseRing: 'rgba(219, 186, 0, 1)',
baseRingHover: 'rgba(219, 186, 0, 0.3)',
baseTextHover: 'rgba(219, 186, 0, 1)',
fieldButton: 'hover:border-recipient-yellow hover:bg-recipient-yellow/30',
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: 'group-hover/field-item:bg-recipient-yellow',
comboxBoxTrigger:
'ring-2 ring-recipient-yellow hover:bg-recipient-yellow/15 active:bg-recipient-yellow/15 shadow-[0_0_0_5px_hsl(var(--recipient-yellow)/10%),0_0_0_2px_hsl(var(--recipient-yellow)/60%),0_0_0_0.5px_hsl(var(--recipient-yellow))]',
comboxBoxItem: 'hover:bg-recipient-yellow/15 active:bg-recipient-yellow/15',
},
pink: {
base: 'ring-recipient-pink hover:bg-recipient-pink/30',
baseRing: 'rgba(217, 74, 186, 1)',
baseRingHover: 'rgba(217, 74, 186, 0.3)',
baseTextHover: 'rgba(217, 74, 186, 1)',
fieldButton: 'hover:border-recipient-pink hover:bg-recipient-pink/30',
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: 'group-hover/field-item:bg-recipient-pink',
comboxBoxTrigger:
'ring-2 ring-recipient-pink hover:bg-recipient-pink/15 active:bg-recipient-pink/15 shadow-[0_0_0_5px_hsl(var(--recipient-pink)/10%),0_0_0_2px_hsl(var(--recipient-pink)/60%),0_0_0_0.5px_hsl(var(--recipient-pink',
comboxBoxItem: 'hover:bg-recipient-pink/15 active:bg-recipient-pink/15',
},
} satisfies Record<string, RecipientColorStyles>;
export type TRecipientColor = keyof typeof RECIPIENT_COLOR_STYLES;
export const AVAILABLE_RECIPIENT_COLORS = [
'green',
'blue',
'purple',
'orange',
'yellow',
'pink',
] satisfies TRecipientColor[];
export const useRecipientColors = (index: number) => {
const key = AVAILABLE_RECIPIENT_COLORS[index % AVAILABLE_RECIPIENT_COLORS.length];
return RECIPIENT_COLOR_STYLES[key];
}; };
export const getRecipientColorStyles = (index: number) => { const generateStyles = (recipientColor: TRecipientColor): RecipientColorStyles => {
// Disabling the rule since the hook doesn't do anything special and can const { bg, border, ring, text } = CSS_PROPERTY;
// be used universally. const { active, hover, groupHover, groupHoverFieldItem } = CSS_VARIANT;
// eslint-disable-next-line react-hooks/rules-of-hooks
return useRecipientColors(index); const name = `recipient-${recipientColor}`;
const value = getComputedStyle(document.documentElement).getPropertyValue(`--${name}`);
const color = colord(`hsl(${value})`);
return {
base: `${ring}-${name} ${hover}:${bg}-${name}/30`,
baseRing: color.toRgbString(),
baseRingHover: color.alpha(0.3).toRgbString(),
baseTextHover: color.toRgbString(),
fieldButton: `${hover}:${border}-${name} ${hover}:${bg}-${name}/30`,
fieldButtonText: `${groupHover}:${text}-${name}`,
fieldItem: 'group/field-item rounded-[2px]',
fieldItemInitials: `${groupHoverFieldItem}:${bg}-${name}`,
comboBoxTrigger: `ring-2 ${ring}-${name} ${hover}:${bg}-${name}/15 ${active}:${bg}-${name}/15 shadow-[0_0_0_5px_hsl(var(--${name})/10%),0_0_0_2px_hsl(var(--${name})/60%),0_0_0_0.5px_hsl(var(--${name}))]`,
comboBoxItem: `${hover}:${bg}-${name}/15 ${active}:${bg}-${name}/15`,
};
};
const CSS_PROPERTY = {
bg: 'bg',
border: 'border',
ring: 'ring',
text: 'text',
};
const CSS_VARIANT = {
active: 'active',
groupHover: 'group-hover',
groupHoverFieldItem: 'group-hover/field-item',
hover: 'hover',
};
const AVAILABLE_RECIPIENT_COLORS = ['green', 'blue', 'purple', 'orange', 'yellow', 'pink'] as const;
export const RECIPIENT_DYNAMIC_CLASS = {
pattern: new RegExp(
`(${Object.values(CSS_PROPERTY).join('|')})-recipient-(${AVAILABLE_RECIPIENT_COLORS.join('|')})(\\/(15|30))?$`,
),
variants: Object.values(CSS_VARIANT),
};
export const getRecipientColor = (index: number) => {
return AVAILABLE_RECIPIENT_COLORS[Math.max(index, 0) % AVAILABLE_RECIPIENT_COLORS.length];
};
export const getRecipientColorStyles = (colorOrIndex: TRecipientColor | number) => {
const color = typeof colorOrIndex === 'number' ? getRecipientColor(colorOrIndex) : colorOrIndex;
return RECIPIENT_COLOR_STYLES[color]();
}; };
@@ -39,7 +39,7 @@ import {
} from '@documenso/lib/utils/recipients'; } from '@documenso/lib/utils/recipients';
import { FieldToolTip } from '../../components/field/field-tooltip'; import { FieldToolTip } from '../../components/field/field-tooltip';
import { useRecipientColors } from '../../lib/recipient-colors'; import { getRecipientColorStyles } from '../../lib/recipient-colors';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
import { Alert, AlertDescription } from '../alert'; import { Alert, AlertDescription } from '../alert';
import { Card, CardContent } from '../card'; import { Card, CardContent } from '../card';
@@ -180,9 +180,7 @@ export const AddFieldsFormPartial = ({
null, null,
); );
const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id); const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id);
const selectedSignerStyles = useRecipientColors( const selectedSignerStyles = getRecipientColorStyles(selectedSignerIndex);
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
);
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
@@ -15,7 +15,7 @@ import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta'; import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
import { ZCheckboxFieldMeta, ZRadioFieldMeta } from '@documenso/lib/types/field-meta'; import { ZCheckboxFieldMeta, ZRadioFieldMeta } from '@documenso/lib/types/field-meta';
import { useRecipientColors } from '../../lib/recipient-colors'; import { getRecipientColorStyles } from '../../lib/recipient-colors';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
import { FieldContent } from './field-content'; import { FieldContent } from './field-content';
import type { TDocumentFlowFormSchema } from './types'; import type { TDocumentFlowFormSchema } from './types';
@@ -100,7 +100,7 @@ const FieldItemInner = ({
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`, `${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
); );
const signerStyles = useRecipientColors(recipientIndex); const signerStyles = getRecipientColorStyles(recipientIndex);
const isDevMode = searchParams.get('devmode') === 'true'; const isDevMode = searchParams.get('devmode') === 'true';
+4 -12
View File
@@ -102,12 +102,8 @@ export const RecipientSelector = ({
role="combobox" role="combobox"
className={cn( className={cn(
'bg-background text-muted-foreground hover:text-foreground justify-between font-normal', 'bg-background text-muted-foreground hover:text-foreground justify-between font-normal',
getRecipientColorStyles( getRecipientColorStyles(recipients.findIndex((r) => r.id === selectedRecipient?.id))
Math.max( .comboBoxTrigger,
recipients.findIndex((r) => r.id === selectedRecipient?.id),
0,
),
).comboxBoxTrigger,
className, className,
)} )}
> >
@@ -151,12 +147,8 @@ export const RecipientSelector = ({
key={recipient.id} key={recipient.id}
className={cn( className={cn(
'px-2 last:mb-1 [&:not(:first-child)]:mt-1', 'px-2 last:mb-1 [&:not(:first-child)]:mt-1',
getRecipientColorStyles( getRecipientColorStyles(recipients.findIndex((r) => r.id === recipient.id))
Math.max( .comboBoxItem,
recipients.findIndex((r) => r.id === recipient.id),
0,
),
).comboxBoxItem,
{ {
'text-muted-foreground': recipient.sendStatus === SendStatus.SENT, 'text-muted-foreground': recipient.sendStatus === SendStatus.SENT,
}, },
@@ -57,7 +57,7 @@ import { FRIENDLY_FIELD_TYPE } from '@documenso/ui/primitives/document-flow/type
import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover';
import { useToast } from '@documenso/ui/primitives/use-toast'; import { useToast } from '@documenso/ui/primitives/use-toast';
import { getRecipientColorStyles, useRecipientColors } from '../../lib/recipient-colors'; import { getRecipientColorStyles } from '../../lib/recipient-colors';
import type { FieldFormType } from '../document-flow/add-fields'; import type { FieldFormType } from '../document-flow/add-fields';
import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings'; import { FieldAdvancedSettings } from '../document-flow/field-item-advanced-settings';
import { Form } from '../form/form'; import { Form } from '../form/form';
@@ -158,9 +158,7 @@ export const AddTemplateFieldsFormPartial = ({
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false); const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id); const selectedSignerIndex = recipients.findIndex((r) => r.id === selectedSigner?.id);
const selectedSignerStyles = useRecipientColors( const selectedSignerStyles = getRecipientColorStyles(selectedSignerIndex);
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
);
const onFieldCopy = useCallback( const onFieldCopy = useCallback(
(event?: KeyboardEvent | null, options?: { duplicate?: boolean; duplicateAll?: boolean }) => { (event?: KeyboardEvent | null, options?: { duplicate?: boolean; duplicateAll?: boolean }) => {
@@ -653,7 +651,7 @@ export const AddTemplateFieldsFormPartial = ({
role="combobox" role="combobox"
className={cn( className={cn(
'mb-12 mt-2 justify-between bg-background font-normal text-muted-foreground hover:text-foreground', 'mb-12 mt-2 justify-between bg-background font-normal text-muted-foreground hover:text-foreground',
selectedSignerStyles?.comboxBoxTrigger, selectedSignerStyles?.comboBoxTrigger,
)} )}
> >
{selectedSigner?.email && {selectedSigner?.email &&
@@ -710,11 +708,8 @@ export const AddTemplateFieldsFormPartial = ({
className={cn( className={cn(
'px-2 last:mb-1 [&:not(:first-child)]:mt-1', 'px-2 last:mb-1 [&:not(:first-child)]:mt-1',
getRecipientColorStyles( getRecipientColorStyles(
Math.max( recipients.findIndex((r) => r.id === recipient.id),
recipients.findIndex((r) => r.id === recipient.id), )?.comboBoxItem,
0,
),
)?.comboxBoxItem,
)} )}
onSelect={() => { onSelect={() => {
setSelectedSigner(recipient); setSelectedSigner(recipient);
+3 -1
View File
@@ -1,12 +1,14 @@
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
const baseConfig = require('@documenso/tailwind-config'); const baseConfig = require('@documenso/tailwind-config');
const { RECIPIENT_DYNAMIC_CLASS } = require('./lib/recipient-colors');
module.exports = { module.exports = {
...baseConfig, presets: [baseConfig],
content: [ content: [
...baseConfig.content, ...baseConfig.content,
'./primitives/**/*.{ts,tsx}', './primitives/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}', './components/**/*.{ts,tsx}',
'./lib/**/*.{ts,tsx}', './lib/**/*.{ts,tsx}',
], ],
safelist: [RECIPIENT_DYNAMIC_CLASS],
}; };