mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
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:
@ -17,6 +17,7 @@ import type {
|
|||||||
TSignFieldWithTokenMutationSchema,
|
TSignFieldWithTokenMutationSchema,
|
||||||
} from '@documenso/trpc/server/field-router/schema';
|
} from '@documenso/trpc/server/field-router/schema';
|
||||||
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip';
|
||||||
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
import { Checkbox } from '@documenso/ui/primitives/checkbox';
|
||||||
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||||
import { Label } from '@documenso/ui/primitives/label';
|
import { Label } from '@documenso/ui/primitives/label';
|
||||||
@ -276,7 +277,14 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
{validationSign?.label} {checkboxValidationLength}
|
{validationSign?.label} {checkboxValidationLength}
|
||||||
</FieldToolTip>
|
</FieldToolTip>
|
||||||
)}
|
)}
|
||||||
<div className="z-50 my-0.5 flex flex-col gap-y-1">
|
<div
|
||||||
|
className={cn(
|
||||||
|
'z-50 my-0.5 flex gap-1',
|
||||||
|
parsedFieldMeta.direction === 'horizontal'
|
||||||
|
? 'flex-row flex-wrap'
|
||||||
|
: 'flex-col gap-y-1',
|
||||||
|
)}
|
||||||
|
>
|
||||||
{values?.map((item: { id: number; value: string; checked: boolean }, index: number) => {
|
{values?.map((item: { id: number; value: string; checked: boolean }, index: number) => {
|
||||||
const itemValue = item.value || `empty-value-${item.id}`;
|
const itemValue = item.value || `empty-value-${item.id}`;
|
||||||
|
|
||||||
@ -305,7 +313,12 @@ export const DocumentSigningCheckboxField = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{field.inserted && (
|
{field.inserted && (
|
||||||
<div className="my-0.5 flex flex-col gap-y-1">
|
<div
|
||||||
|
className={cn(
|
||||||
|
'my-0.5 flex gap-1',
|
||||||
|
parsedFieldMeta.direction === 'horizontal' ? 'flex-row flex-wrap' : 'flex-col gap-y-1',
|
||||||
|
)}
|
||||||
|
>
|
||||||
{values?.map((item: { id: number; value: string; checked: boolean }, index: number) => {
|
{values?.map((item: { id: number; value: string; checked: boolean }, index: number) => {
|
||||||
const itemValue = item.value || `empty-value-${item.id}`;
|
const itemValue = item.value || `empty-value-${item.id}`;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
|||||||
width: 0,
|
width: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const calculateBounds = () => {
|
const calculateBounds = useCallback(() => {
|
||||||
const $el =
|
const $el =
|
||||||
typeof elementOrSelector === 'string'
|
typeof elementOrSelector === 'string'
|
||||||
? document.querySelector<HTMLElement>(elementOrSelector)
|
? document.querySelector<HTMLElement>(elementOrSelector)
|
||||||
@ -32,11 +32,11 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
};
|
};
|
||||||
};
|
}, [elementOrSelector, withScroll]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBounds(calculateBounds());
|
setBounds(calculateBounds());
|
||||||
}, [calculateBounds]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onResize = () => {
|
const onResize = () => {
|
||||||
@ -48,7 +48,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', onResize);
|
window.removeEventListener('resize', onResize);
|
||||||
};
|
};
|
||||||
}, [calculateBounds]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const $el =
|
const $el =
|
||||||
@ -69,7 +69,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
|||||||
return () => {
|
return () => {
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
};
|
};
|
||||||
}, [calculateBounds]);
|
}, []);
|
||||||
|
|
||||||
return bounds;
|
return bounds;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -240,35 +240,79 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const selected: string[] = fromCheckboxValue(field.customText);
|
const selected: string[] = fromCheckboxValue(field.customText);
|
||||||
|
const direction = meta.data.direction ?? 'vertical';
|
||||||
|
|
||||||
const topPadding = 12;
|
const topPadding = 12;
|
||||||
const leftCheckboxPadding = 8;
|
const leftCheckboxPadding = 8;
|
||||||
const leftCheckboxLabelPadding = 12;
|
const leftCheckboxLabelPadding = 12;
|
||||||
const checkboxSpaceY = 13;
|
const checkboxSpaceY = 13;
|
||||||
|
|
||||||
for (const [index, item] of (values ?? []).entries()) {
|
if (direction === 'horizontal') {
|
||||||
const offsetY = index * checkboxSpaceY + topPadding;
|
// Horizontal layout: arrange checkboxes side by side with wrapping
|
||||||
|
let currentX = leftCheckboxPadding;
|
||||||
|
let currentY = topPadding;
|
||||||
|
const maxWidth = pageWidth - fieldX - leftCheckboxPadding * 2;
|
||||||
|
|
||||||
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
for (const [index, item] of (values ?? []).entries()) {
|
||||||
|
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||||
|
|
||||||
if (selected.includes(item.value)) {
|
if (selected.includes(item.value)) {
|
||||||
checkbox.check();
|
checkbox.check();
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelText = item.value.includes('empty-value-') ? '' : item.value;
|
||||||
|
const labelWidth = font.widthOfTextAtSize(labelText, 12);
|
||||||
|
const itemWidth = leftCheckboxLabelPadding + labelWidth + 16; // checkbox + padding + label + margin
|
||||||
|
|
||||||
|
// Check if item fits on current line, if not wrap to next line
|
||||||
|
if (currentX + itemWidth > maxWidth && index > 0) {
|
||||||
|
currentX = leftCheckboxPadding;
|
||||||
|
currentY += checkboxSpaceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
page.drawText(labelText, {
|
||||||
|
x: fieldX + currentX + leftCheckboxLabelPadding,
|
||||||
|
y: pageHeight - (fieldY + currentY),
|
||||||
|
size: 12,
|
||||||
|
font,
|
||||||
|
rotate: degrees(pageRotationInDegrees),
|
||||||
|
});
|
||||||
|
|
||||||
|
checkbox.addToPage(page, {
|
||||||
|
x: fieldX + currentX,
|
||||||
|
y: pageHeight - (fieldY + currentY),
|
||||||
|
height: 8,
|
||||||
|
width: 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
currentX += itemWidth;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Vertical layout: original behavior
|
||||||
|
for (const [index, item] of (values ?? []).entries()) {
|
||||||
|
const offsetY = index * checkboxSpaceY + topPadding;
|
||||||
|
|
||||||
page.drawText(item.value.includes('empty-value-') ? '' : item.value, {
|
const checkbox = pdf.getForm().createCheckBox(`checkbox.${field.secondaryId}.${index}`);
|
||||||
x: fieldX + leftCheckboxPadding + leftCheckboxLabelPadding,
|
|
||||||
y: pageHeight - (fieldY + offsetY),
|
|
||||||
size: 12,
|
|
||||||
font,
|
|
||||||
rotate: degrees(pageRotationInDegrees),
|
|
||||||
});
|
|
||||||
|
|
||||||
checkbox.addToPage(page, {
|
if (selected.includes(item.value)) {
|
||||||
x: fieldX + leftCheckboxPadding,
|
checkbox.check();
|
||||||
y: pageHeight - (fieldY + offsetY),
|
}
|
||||||
height: 8,
|
|
||||||
width: 8,
|
page.drawText(item.value.includes('empty-value-') ? '' : item.value, {
|
||||||
});
|
x: fieldX + leftCheckboxPadding + leftCheckboxLabelPadding,
|
||||||
|
y: pageHeight - (fieldY + offsetY),
|
||||||
|
size: 12,
|
||||||
|
font,
|
||||||
|
rotate: degrees(pageRotationInDegrees),
|
||||||
|
});
|
||||||
|
|
||||||
|
checkbox.addToPage(page, {
|
||||||
|
x: fieldX + leftCheckboxPadding,
|
||||||
|
y: pageHeight - (fieldY + offsetY),
|
||||||
|
height: 8,
|
||||||
|
width: 8,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.with({ type: FieldType.RADIO }, (field) => {
|
.with({ type: FieldType.RADIO }, (field) => {
|
||||||
|
|||||||
@ -228,6 +228,7 @@ const getUpdatedFieldMeta = (field: Field, prefillField?: TFieldMetaPrefillField
|
|||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
label: field.label,
|
label: field.label,
|
||||||
values: newValues,
|
values: newValues,
|
||||||
|
direction: checkboxMeta.direction ?? 'vertical',
|
||||||
};
|
};
|
||||||
|
|
||||||
return meta;
|
return meta;
|
||||||
|
|||||||
@ -96,6 +96,7 @@ export const ZCheckboxFieldMeta = ZBaseFieldMeta.extend({
|
|||||||
.optional(),
|
.optional(),
|
||||||
validationRule: z.string().optional(),
|
validationRule: z.string().optional(),
|
||||||
validationLength: z.number().optional(),
|
validationLength: z.number().optional(),
|
||||||
|
direction: z.enum(['vertical', 'horizontal']).optional().default('vertical'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCheckboxFieldMeta = z.infer<typeof ZCheckboxFieldMeta>;
|
export type TCheckboxFieldMeta = z.infer<typeof ZCheckboxFieldMeta>;
|
||||||
|
|||||||
@ -3,7 +3,9 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import { type Field, FieldType } from '@prisma/client';
|
import { type Field, FieldType } from '@prisma/client';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
import { useElementBounds } from '@documenso/lib/client-only/hooks/use-element-bounds';
|
||||||
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords';
|
||||||
|
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||||
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
|
import { isFieldUnsignedAndRequired } from '@documenso/lib/utils/advanced-fields-helpers';
|
||||||
|
|
||||||
import type { RecipientColorStyles } from '../../lib/recipient-colors';
|
import type { RecipientColorStyles } from '../../lib/recipient-colors';
|
||||||
@ -23,6 +25,11 @@ export function FieldContainerPortal({
|
|||||||
const alternativePortalRoot = document.getElementById('document-field-portal-root');
|
const alternativePortalRoot = document.getElementById('document-field-portal-root');
|
||||||
|
|
||||||
const coords = useFieldPageCoords(field);
|
const coords = useFieldPageCoords(field);
|
||||||
|
const $pageBounds = useElementBounds(
|
||||||
|
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxWidth = $pageBounds?.width ? $pageBounds.width - coords.x : undefined;
|
||||||
|
|
||||||
const isCheckboxOrRadioField = field.type === 'CHECKBOX' || field.type === 'RADIO';
|
const isCheckboxOrRadioField = field.type === 'CHECKBOX' || field.type === 'RADIO';
|
||||||
|
|
||||||
@ -32,10 +39,14 @@ export function FieldContainerPortal({
|
|||||||
const bounds = {
|
const bounds = {
|
||||||
top: `${coords.y}px`,
|
top: `${coords.y}px`,
|
||||||
left: `${coords.x}px`,
|
left: `${coords.x}px`,
|
||||||
...(!isCheckboxOrRadioField && {
|
...(!isCheckboxOrRadioField
|
||||||
height: `${coords.height}px`,
|
? {
|
||||||
width: `${coords.width}px`,
|
height: `${coords.height}px`,
|
||||||
}),
|
width: `${coords.width}px`,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
maxWidth: `${maxWidth}px`,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (portalBounds) {
|
if (portalBounds) {
|
||||||
@ -44,7 +55,7 @@ export function FieldContainerPortal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return bounds;
|
return bounds;
|
||||||
}, [coords, isCheckboxOrRadioField]);
|
}, [coords, maxWidth, isCheckboxOrRadioField]);
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div className={cn('absolute', className)} style={style}>
|
<div className={cn('absolute', className)} style={style}>
|
||||||
|
|||||||
@ -38,13 +38,8 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
|
|||||||
|
|
||||||
const { type, fieldMeta } = field;
|
const { type, fieldMeta } = field;
|
||||||
|
|
||||||
// Only render checkbox if values exist, otherwise render the empty checkbox field content.
|
// Render checkbox layout for checkbox fields, even if no values exist yet
|
||||||
if (
|
if (field.type === FieldType.CHECKBOX && field.fieldMeta?.type === 'checkbox') {
|
||||||
field.type === FieldType.CHECKBOX &&
|
|
||||||
field.fieldMeta?.type === 'checkbox' &&
|
|
||||||
field.fieldMeta.values &&
|
|
||||||
field.fieldMeta.values.length > 0
|
|
||||||
) {
|
|
||||||
let checkedValues: string[] = [];
|
let checkedValues: string[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -55,8 +50,32 @@ export const FieldContent = ({ field, documentMeta }: FieldIconProps) => {
|
|||||||
console.error(err);
|
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 (
|
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) => (
|
{field.fieldMeta.values.map((item, index) => (
|
||||||
<div key={index} className="flex items-center">
|
<div key={index} className="flex items-center">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@ -129,6 +129,7 @@ const getDefaultState = (fieldType: FieldType): FieldMeta => {
|
|||||||
validationLength: 0,
|
validationLength: 0,
|
||||||
required: false,
|
required: false,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
|
direction: 'vertical',
|
||||||
};
|
};
|
||||||
case FieldType.DROPDOWN:
|
case FieldType.DROPDOWN:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { createPortal } from 'react-dom';
|
|||||||
import { Rnd } from 'react-rnd';
|
import { Rnd } from 'react-rnd';
|
||||||
import { useSearchParams } from 'react-router';
|
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 { 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';
|
||||||
@ -81,7 +82,11 @@ export const FieldItem = ({
|
|||||||
pageWidth: defaultWidth || 0,
|
pageWidth: defaultWidth || 0,
|
||||||
});
|
});
|
||||||
const [settingsActive, setSettingsActive] = useState(false);
|
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);
|
const signerStyles = useRecipientColors(recipientIndex);
|
||||||
|
|
||||||
@ -233,9 +238,10 @@ export const FieldItem = ({
|
|||||||
default={{
|
default={{
|
||||||
x: coords.pageX,
|
x: coords.pageX,
|
||||||
y: coords.pageY,
|
y: coords.pageY,
|
||||||
height: fixedSize ? '' : coords.pageHeight,
|
height: fixedSize ? 'auto' : coords.pageHeight,
|
||||||
width: fixedSize ? '' : coords.pageWidth,
|
width: fixedSize ? 'auto' : coords.pageWidth,
|
||||||
}}
|
}}
|
||||||
|
maxWidth={fixedSize && $pageBounds?.width ? $pageBounds.width - coords.pageX : undefined}
|
||||||
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
|
||||||
onDragStart={() => onFieldActivate?.()}
|
onDragStart={() => onFieldActivate?.()}
|
||||||
onResizeStart={() => onFieldActivate?.()}
|
onResizeStart={() => onFieldActivate?.()}
|
||||||
|
|||||||
@ -44,6 +44,9 @@ export const CheckboxFieldAdvancedSettings = ({
|
|||||||
const [required, setRequired] = useState(fieldState.required ?? false);
|
const [required, setRequired] = useState(fieldState.required ?? false);
|
||||||
const [validationLength, setValidationLength] = useState(fieldState.validationLength ?? 0);
|
const [validationLength, setValidationLength] = useState(fieldState.validationLength ?? 0);
|
||||||
const [validationRule, setValidationRule] = useState(fieldState.validationRule ?? '');
|
const [validationRule, setValidationRule] = useState(fieldState.validationRule ?? '');
|
||||||
|
const [direction, setDirection] = useState<'vertical' | 'horizontal'>(
|
||||||
|
fieldState.direction ?? 'vertical',
|
||||||
|
);
|
||||||
|
|
||||||
const handleToggleChange = (field: keyof CheckboxFieldMeta, value: string | boolean) => {
|
const handleToggleChange = (field: keyof CheckboxFieldMeta, value: string | boolean) => {
|
||||||
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
|
const readOnly = field === 'readOnly' ? Boolean(value) : Boolean(fieldState.readOnly);
|
||||||
@ -52,11 +55,14 @@ export const CheckboxFieldAdvancedSettings = ({
|
|||||||
field === 'validationRule' ? String(value) : String(fieldState.validationRule);
|
field === 'validationRule' ? String(value) : String(fieldState.validationRule);
|
||||||
const validationLength =
|
const validationLength =
|
||||||
field === 'validationLength' ? Number(value) : Number(fieldState.validationLength);
|
field === 'validationLength' ? Number(value) : Number(fieldState.validationLength);
|
||||||
|
const currentDirection =
|
||||||
|
field === 'direction' && String(value) === 'horizontal' ? 'horizontal' : 'vertical';
|
||||||
|
|
||||||
setReadOnly(readOnly);
|
setReadOnly(readOnly);
|
||||||
setRequired(required);
|
setRequired(required);
|
||||||
setValidationRule(validationRule);
|
setValidationRule(validationRule);
|
||||||
setValidationLength(validationLength);
|
setValidationLength(validationLength);
|
||||||
|
setDirection(currentDirection);
|
||||||
|
|
||||||
const errors = validateCheckboxField(
|
const errors = validateCheckboxField(
|
||||||
values.map((item) => item.value),
|
values.map((item) => item.value),
|
||||||
@ -65,6 +71,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
|||||||
required,
|
required,
|
||||||
validationRule,
|
validationRule,
|
||||||
validationLength,
|
validationLength,
|
||||||
|
direction: currentDirection,
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -86,6 +93,7 @@ export const CheckboxFieldAdvancedSettings = ({
|
|||||||
required,
|
required,
|
||||||
validationRule,
|
validationRule,
|
||||||
validationLength,
|
validationLength,
|
||||||
|
direction: direction,
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -137,6 +145,29 @@ export const CheckboxFieldAdvancedSettings = ({
|
|||||||
onChange={(e) => handleFieldChange('label', e.target.value)}
|
onChange={(e) => handleFieldChange('label', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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 flex-row items-center gap-x-4">
|
||||||
<div className="flex w-2/3 flex-col">
|
<div className="flex w-2/3 flex-col">
|
||||||
<Label>
|
<Label>
|
||||||
|
|||||||
Reference in New Issue
Block a user