mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 00:03:33 +10:00
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:
@ -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>
|
||||
|
||||
@ -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>(
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user