feat: horizontal checkboxes (#1911)

Adds the ability to have checkboxes align horizontally, wrapping when
they would go off the PDF
This commit is contained in:
Lucas Smith
2025-07-19 22:06:50 +10:00
committed by GitHub
parent c47dc8749a
commit 512e3555b4
10 changed files with 169 additions and 42 deletions

View File

@ -38,13 +38,8 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
const { type, fieldMeta } = field;
// Only render checkbox if values exist, otherwise render the empty checkbox field content.
if (
field.type === FieldType.CHECKBOX &&
field.fieldMeta?.type === 'checkbox' &&
field.fieldMeta.values &&
field.fieldMeta.values.length > 0
) {
// Render checkbox layout for checkbox fields, even if no values exist yet
if (field.type === FieldType.CHECKBOX && field.fieldMeta?.type === 'checkbox') {
let checkedValues: string[] = [];
try {
@ -55,8 +50,32 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
console.error(err);
}
// If no values exist yet, show a placeholder checkbox
if (!field.fieldMeta.values || field.fieldMeta.values.length === 0) {
return (
<div
className={cn(
'flex gap-1 py-0.5',
field.fieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
)}
>
<div className="flex items-center">
<Checkbox className="h-3 w-3" disabled />
<Label className="text-foreground ml-1.5 text-xs font-normal opacity-50">
Checkbox option
</Label>
</div>
</div>
);
}
return (
<div className="flex flex-col gap-y-1 py-0.5">
<div
className={cn(
'flex gap-1 py-0.5',
field.fieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
)}
>
{field.fieldMeta.values.map((item, index) => (
<div key={index} className="flex items-center">
<Checkbox

View File

@ -129,6 +129,7 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
validationLength: 0,
required: false,
readOnly: false,
direction: 'vertical',
};
case FieldType.DROPDOWN:
return {

View File

@ -8,6 +8,7 @@ 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 { 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';
@ -81,7 +82,11 @@ export const FieldItem = ({
pageWidth: defaultWidth || 0,
});
const [settingsActive, setSettingsActive] = useState(false);
const $el = useRef(null);
const $el = useRef<HTMLDivElement>(null);
const $pageBounds = useElementBounds(
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`,
);
const signerStyles = useRecipientColors(recipientIndex);
@ -233,9 +238,10 @@ export const FieldItem = ({
default={{
x: coords.pageX,
y: coords.pageY,
height: fixedSize ? '' : coords.pageHeight,
width: fixedSize ? '' : coords.pageWidth,
height: fixedSize ? 'auto' : coords.pageHeight,
width: fixedSize ? 'auto' : coords.pageWidth,
}}
maxWidth={fixedSize && $pageBounds?.width ? $pageBounds.width - coords.pageX : undefined}
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
onDragStart={() => onFieldActivate?.()}
onResizeStart={() => onFieldActivate?.()}

View File

@ -44,6 +44,9 @@ 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 handleToggleChange = (field: keyof CheckboxFieldMeta, value: string | boolean) => {
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
@ -52,11 +55,14 @@ export const CheckboxFieldAdvancedSettings = ({
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);
setValidationRule(validationRule);
setValidationLength(validationLength);
setDirection(currentDirection);
const errors = validateCheckboxField(
values.map((item) => item.value),
@ -65,6 +71,7 @@ export const CheckboxFieldAdvancedSettings = ({
required,
validationRule,
validationLength,
direction: currentDirection,
type: 'checkbox',
},
);
@ -86,6 +93,7 @@ export const CheckboxFieldAdvancedSettings = ({
required,
validationRule,
validationLength,
direction: direction,
type: 'checkbox',
},
);
@ -137,6 +145,29 @@ export const CheckboxFieldAdvancedSettings = ({
onChange={(e) => handleFieldChange('label', e.target.value)}
/>
</div>
<div className="mb-2">
<Label>
<Trans>Direction</Trans>
</Label>
<Select
value={fieldState.direction ?? 'vertical'}
onValueChange={(val) => handleToggleChange('direction', val)}
>
<SelectTrigger className="text-muted-foreground bg-background mt-2 w-full">
<SelectValue placeholder={_(msg`Select direction`)} />
</SelectTrigger>
<SelectContent position="popper">
<SelectItem value="vertical">
<Trans>Vertical</Trans>
</SelectItem>
<SelectItem value="horizontal">
<Trans>Horizontal</Trans>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-row items-center gap-x-4">
<div className="flex w-2/3 flex-col">
<Label>