feat: add custom font sizes to fields (#1376)

Adds custom font sizes to fields

https://github.com/user-attachments/assets/1473a4d7-8dc6-4ead-acf5-dd78be7782a0
This commit is contained in:
Ephraim Duncan
2024-10-16 05:05:41 +00:00
committed by GitHub
parent 0bd2760792
commit e0c948c2ac
30 changed files with 663 additions and 186 deletions

View File

@ -13,8 +13,12 @@ import { match } from 'ts-pattern';
import {
type TBaseFieldMeta as BaseFieldMeta,
type TCheckboxFieldMeta as CheckboxFieldMeta,
type TDateFieldMeta as DateFieldMeta,
type TDropdownFieldMeta as DropdownFieldMeta,
type TEmailFieldMeta as EmailFieldMeta,
type TFieldMetaSchema as FieldMeta,
type TInitialsFieldMeta as InitialsFieldMeta,
type TNameFieldMeta as NameFieldMeta,
type TNumberFieldMeta as NumberFieldMeta,
type TRadioFieldMeta as RadioFieldMeta,
type TTextFieldMeta as TextFieldMeta,
@ -33,7 +37,11 @@ import {
} from './document-flow-root';
import { FieldItem } from './field-item';
import { CheckboxFieldAdvancedSettings } from './field-items-advanced-settings/checkbox-field';
import { DateFieldAdvancedSettings } from './field-items-advanced-settings/date-field';
import { DropdownFieldAdvancedSettings } from './field-items-advanced-settings/dropdown-field';
import { EmailFieldAdvancedSettings } from './field-items-advanced-settings/email-field';
import { InitialsFieldAdvancedSettings } from './field-items-advanced-settings/initials-field';
import { NameFieldAdvancedSettings } from './field-items-advanced-settings/name-field';
import { NumberFieldAdvancedSettings } from './field-items-advanced-settings/number-field';
import { RadioFieldAdvancedSettings } from './field-items-advanced-settings/radio-field';
import { TextFieldAdvancedSettings } from './field-items-advanced-settings/text-field';
@ -55,10 +63,34 @@ export type FieldMetaKeys =
| keyof NumberFieldMeta
| keyof RadioFieldMeta
| keyof CheckboxFieldMeta
| keyof DropdownFieldMeta;
| keyof DropdownFieldMeta
| keyof InitialsFieldMeta
| keyof NameFieldMeta
| keyof EmailFieldMeta
| keyof DateFieldMeta;
const getDefaultState = (fieldType: FieldType): FieldMeta => {
switch (fieldType) {
case FieldType.INITIALS:
return {
type: 'initials',
fontSize: 14,
};
case FieldType.NAME:
return {
type: 'name',
fontSize: 14,
};
case FieldType.EMAIL:
return {
type: 'email',
fontSize: 14,
};
case FieldType.DATE:
return {
type: 'date',
fontSize: 14,
};
case FieldType.TEXT:
return {
type: 'text',
@ -66,6 +98,7 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
placeholder: '',
text: '',
characterLimit: 0,
fontSize: 14,
required: false,
readOnly: false,
};
@ -80,6 +113,7 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
maxValue: 0,
required: false,
readOnly: false,
fontSize: 14,
};
case FieldType.RADIO:
return {
@ -180,10 +214,17 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
const handleFieldChange = (
key: FieldMetaKeys,
value: string | { checked: boolean; value: string }[] | { value: string }[] | boolean,
value:
| string
| { checked: boolean; value: string }[]
| { value: string }[]
| boolean
| number,
) => {
setFieldState((prevState: FieldMeta) => {
if (['characterLimit', 'minValue', 'maxValue', 'validationLength'].includes(key)) {
if (
['characterLimit', 'minValue', 'maxValue', 'validationLength', 'fontSize'].includes(key)
) {
const parsedValue = Number(value);
return {
@ -232,6 +273,35 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
))}
{match(field.type)
.with(FieldType.INITIALS, () => (
<InitialsFieldAdvancedSettings
fieldState={fieldState}
handleFieldChange={handleFieldChange}
handleErrors={setErrors}
/>
))
.with(FieldType.NAME, () => (
<NameFieldAdvancedSettings
fieldState={fieldState}
handleFieldChange={handleFieldChange}
handleErrors={setErrors}
/>
))
.with(FieldType.EMAIL, () => (
<EmailFieldAdvancedSettings
fieldState={fieldState}
handleFieldChange={handleFieldChange}
handleErrors={setErrors}
/>
))
.with(FieldType.DATE, () => (
<DateFieldAdvancedSettings
fieldState={fieldState}
handleFieldChange={handleFieldChange}
handleErrors={setErrors}
/>
))
.with(FieldType.TEXT, () => (
<TextFieldAdvancedSettings
fieldState={fieldState}
@ -268,7 +338,6 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
/>
))
.otherwise(() => null)}
{errors.length > 0 && (
<div className="mt-4">
<ul>

View File

@ -76,7 +76,17 @@ export const FieldItem = ({
const signerStyles = useSignerColors(recipientIndex);
const advancedField = ['NUMBER', 'RADIO', 'CHECKBOX', 'DROPDOWN', 'TEXT'].includes(field.type);
const advancedField = [
'NUMBER',
'RADIO',
'CHECKBOX',
'DROPDOWN',
'TEXT',
'INITIALS',
'EMAIL',
'DATE',
'NAME',
].includes(field.type);
const calculateCoords = useCallback(() => {
const $page = document.querySelector<HTMLElement>(

View File

@ -66,6 +66,7 @@ export const CheckboxFieldAdvancedSettings = ({
required,
validationRule,
validationLength,
type: 'checkbox',
},
);
handleErrors(errors);
@ -86,6 +87,7 @@ export const CheckboxFieldAdvancedSettings = ({
required,
validationRule,
validationLength,
type: 'checkbox',
},
);
handleErrors(errors);

View File

@ -0,0 +1,71 @@
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { validateFields as validateDateFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TDateFieldMeta as DateFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
type DateFieldAdvancedSettingsProps = {
fieldState: DateFieldMeta;
handleFieldChange: (key: keyof DateFieldMeta, value: string | boolean) => void;
handleErrors: (errors: string[]) => void;
};
export const DateFieldAdvancedSettings = ({
fieldState,
handleFieldChange,
handleErrors,
}: DateFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
// const handleInput = (field: keyof DateFieldMeta, value: string | boolean) => {
// if (field === 'fontSize') {
// const fontSize = value === '' ? '' : Number(value);
// if (typeof fontSize === 'number' && !Number.isNaN(fontSize)) {
// const errors = validateDateFields({
// fontSize,
// type: 'date',
// });
// handleErrors(errors);
// handleFieldChange(field, fontSize.toString());
// } else {
// handleErrors(['Invalid font size']);
// }
// } else {
// handleFieldChange(field, value);
// }
// };
const handleInput = (field: keyof DateFieldMeta, value: string | boolean) => {
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
const errors = validateDateFields({
fontSize,
type: 'date',
});
handleErrors(errors);
handleFieldChange(field, value);
};
return (
<div className="flex flex-col gap-4">
<div>
<Label>
<Trans>Font Size</Trans>
</Label>
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
max={96}
/>
</div>
</div>
);
};

View File

@ -62,7 +62,12 @@ export const DropdownFieldAdvancedSettings = ({
setReadOnly(readOnly);
setRequired(required);
const errors = validateDropdownField(undefined, { readOnly, required, values });
const errors = validateDropdownField(undefined, {
readOnly,
required,
values,
type: 'dropdown',
});
handleErrors(errors);
handleFieldChange(field, value);
@ -76,7 +81,12 @@ export const DropdownFieldAdvancedSettings = ({
};
useEffect(() => {
const errors = validateDropdownField(undefined, { readOnly, required, values });
const errors = validateDropdownField(undefined, {
readOnly,
required,
values,
type: 'dropdown',
});
handleErrors(errors);
}, [values]);

View File

@ -0,0 +1,53 @@
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { validateFields as validateEmailFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
import { type TEmailFieldMeta as EmailFieldMeta } from '@documenso/lib/types/field-meta';
import { Input } from '@documenso/ui/primitives/input';
import { Label } from '@documenso/ui/primitives/label';
type EmailFieldAdvancedSettingsProps = {
fieldState: EmailFieldMeta;
handleFieldChange: (key: keyof EmailFieldMeta, value: string | boolean) => void;
handleErrors: (errors: string[]) => void;
};
export const EmailFieldAdvancedSettings = ({
fieldState,
handleFieldChange,
handleErrors,
}: EmailFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const handleInput = (field: keyof EmailFieldMeta, value: string | boolean) => {
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
const errors = validateEmailFields({
fontSize,
type: 'email',
});
handleErrors(errors);
handleFieldChange(field, value);
};
return (
<div className="flex flex-col gap-4">
<div>
<Label>
<Trans>Font Size</Trans>
</Label>
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
max={96}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,53 @@
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { validateFields as validateInitialsFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
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';
type InitialsFieldAdvancedSettingsProps = {
fieldState: InitialsFieldMeta;
handleFieldChange: (key: keyof InitialsFieldMeta, value: string | boolean) => void;
handleErrors: (errors: string[]) => void;
};
export const InitialsFieldAdvancedSettings = ({
fieldState,
handleFieldChange,
handleErrors,
}: InitialsFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const handleInput = (field: keyof InitialsFieldMeta, value: string | boolean) => {
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
const errors = validateInitialsFields({
fontSize,
type: 'initials',
});
handleErrors(errors);
handleFieldChange(field, value);
};
return (
<div className="flex flex-col gap-4">
<div>
<Label>
<Trans>Font Size</Trans>
</Label>
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
max={96}
/>
</div>
</div>
);
};

View File

@ -0,0 +1,53 @@
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { validateFields as validateNameFields } from '@documenso/lib/advanced-fields-validation/validate-fields';
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';
type NameFieldAdvancedSettingsProps = {
fieldState: NameFieldMeta;
handleFieldChange: (key: keyof NameFieldMeta, value: string | boolean) => void;
handleErrors: (errors: string[]) => void;
};
export const NameFieldAdvancedSettings = ({
fieldState,
handleFieldChange,
handleErrors,
}: NameFieldAdvancedSettingsProps) => {
const { _ } = useLingui();
const handleInput = (field: keyof NameFieldMeta, value: string | boolean) => {
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
const errors = validateNameFields({
fontSize,
type: 'name',
});
handleErrors(errors);
handleFieldChange(field, value);
};
return (
<div className="flex flex-col gap-4">
<div>
<Label>
<Trans>Font Size</Trans>
</Label>
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
max={96}
/>
</div>
</div>
);
};

View File

@ -38,12 +38,13 @@ export const NumberFieldAdvancedSettings = ({
const [showValidation, setShowValidation] = useState(false);
const handleInput = (field: keyof NumberFieldMeta, value: string | boolean) => {
const userValue = field === 'value' ? value : fieldState.value || 0;
const userMinValue = field === 'minValue' ? Number(value) : Number(fieldState.minValue || 0);
const userMaxValue = field === 'maxValue' ? Number(value) : Number(fieldState.maxValue || 0);
const userValue = field === 'value' ? value : fieldState.value ?? 0;
const userMinValue = field === 'minValue' ? Number(value) : Number(fieldState.minValue ?? 0);
const userMaxValue = field === 'maxValue' ? Number(value) : Number(fieldState.maxValue ?? 0);
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
const required = field === 'required' ? Boolean(value) : Boolean(fieldState.required);
const numberFormat = field === 'numberFormat' ? String(value) : fieldState.numberFormat || '';
const numberFormat = field === 'numberFormat' ? String(value) : fieldState.numberFormat ?? '';
const fontSize = field === 'fontSize' ? Number(value) : Number(fieldState.fontSize ?? 14);
const valueErrors = validateNumberField(String(userValue), {
minValue: userMinValue,
@ -51,6 +52,8 @@ export const NumberFieldAdvancedSettings = ({
readOnly,
required,
numberFormat,
fontSize,
type: 'number',
});
handleErrors(valueErrors);
@ -115,6 +118,23 @@ export const NumberFieldAdvancedSettings = ({
</SelectContent>
</Select>
</div>
<div>
<Label>
<Trans>Font Size</Trans>
</Label>
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
max={96}
/>
</div>
<div className="mt-2 flex flex-col gap-4">
<div className="flex flex-row items-center gap-2">
<Switch

View File

@ -70,7 +70,7 @@ export const RadioFieldAdvancedSettings = ({
setReadOnly(readOnly);
setRequired(required);
const errors = validateRadioField(String(value), { readOnly, required, values });
const errors = validateRadioField(String(value), { readOnly, required, values, type: 'radio' });
handleErrors(errors);
handleFieldChange(field, value);
@ -95,7 +95,7 @@ export const RadioFieldAdvancedSettings = ({
}, [fieldState.values]);
useEffect(() => {
const errors = validateRadioField(undefined, { readOnly, required, values });
const errors = validateRadioField(undefined, { readOnly, required, values, type: 'radio' });
handleErrors(errors);
}, [values]);

View File

@ -22,9 +22,10 @@ export const TextFieldAdvancedSettings = ({
const { _ } = useLingui();
const handleInput = (field: keyof TextFieldMeta, value: string | boolean) => {
const text = field === 'text' ? String(value) : fieldState.text || '';
const text = field === 'text' ? String(value) : fieldState.text ?? '';
const limit =
field === 'characterLimit' ? Number(value) : Number(fieldState.characterLimit || 0);
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);
@ -32,6 +33,8 @@ export const TextFieldAdvancedSettings = ({
characterLimit: Number(limit),
readOnly,
required,
fontSize,
type: 'text',
});
handleErrors(textErrors);
@ -93,6 +96,22 @@ export const TextFieldAdvancedSettings = ({
/>
</div>
<div>
<Label>
<Trans>Font Size</Trans>
</Label>
<Input
id="fontSize"
type="number"
className="bg-background mt-2"
placeholder={_(msg`Field font size`)}
value={fieldState.fontSize}
onChange={(e) => handleInput('fontSize', e.target.value)}
min={8}
max={96}
/>
</div>
<div className="mt-4 flex flex-col gap-4">
<div className="flex flex-row items-center gap-2">
<Switch