diff --git a/docs/fields/array.mdx b/docs/fields/array.mdx index 8ea755bd28..333eaa4906 100644 --- a/docs/fields/array.mdx +++ b/docs/fields/array.mdx @@ -59,6 +59,7 @@ properties: | Option | Description | |---------------------------|----------------------------------------------------------------------------------------------------------------------| | **`initCollapsed`** | Set the initial collapsed state | +| **`isSortable`** | Disable order sorting by setting this value to `false` | | **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args | ### Example diff --git a/docs/fields/blocks.mdx b/docs/fields/blocks.mdx index 1991900ea6..49c4203edc 100644 --- a/docs/fields/blocks.mdx +++ b/docs/fields/blocks.mdx @@ -58,6 +58,7 @@ properties: | Option | Description | |---------------------|---------------------------------| | **`initCollapsed`** | Set the initial collapsed state | +| **`isSortable`** | Disable order sorting by setting this value to `false` | ### Block configs diff --git a/packages/payload/src/admin/components/elements/ArrayAction/index.tsx b/packages/payload/src/admin/components/elements/ArrayAction/index.tsx index 020b2d27dc..226f6a3850 100644 --- a/packages/payload/src/admin/components/elements/ArrayAction/index.tsx +++ b/packages/payload/src/admin/components/elements/ArrayAction/index.tsx @@ -19,6 +19,7 @@ export const ArrayAction: React.FC = ({ duplicateRow, hasMaxRows, index, + isSortable, moveRow, removeRow, rowCount, @@ -33,7 +34,7 @@ export const ArrayAction: React.FC = ({ render={({ close }) => { return ( - {index !== 0 && ( + {isSortable && index !== 0 && ( { @@ -47,7 +48,7 @@ export const ArrayAction: React.FC = ({ {t('moveUp')} )} - {index < rowCount - 1 && ( + {isSortable && index < rowCount - 1 && ( { diff --git a/packages/payload/src/admin/components/elements/ArrayAction/types.ts b/packages/payload/src/admin/components/elements/ArrayAction/types.ts index 5e2addaca3..db9a7cd86e 100644 --- a/packages/payload/src/admin/components/elements/ArrayAction/types.ts +++ b/packages/payload/src/admin/components/elements/ArrayAction/types.ts @@ -3,6 +3,7 @@ export type Props = { duplicateRow: (current: number) => void hasMaxRows: boolean index: number + isSortable: boolean moveRow: (from: number, to: number) => void removeRow: (index: number) => void rowCount: number diff --git a/packages/payload/src/admin/components/forms/field-types/Array/ArrayRow.tsx b/packages/payload/src/admin/components/forms/field-types/Array/ArrayRow.tsx index 5771b147de..76bd7578a9 100644 --- a/packages/payload/src/admin/components/forms/field-types/Array/ArrayRow.tsx +++ b/packages/payload/src/admin/components/forms/field-types/Array/ArrayRow.tsx @@ -26,6 +26,7 @@ type ArrayRowProps = UseDraggableSortableReturn & duplicateRow: (rowIndex: number) => void forceRender?: boolean hasMaxRows?: boolean + isSortable: boolean moveRow: (fromIndex: number, toIndex: number) => void readOnly?: boolean removeRow: (rowIndex: number) => void @@ -44,6 +45,7 @@ export const ArrayRow: React.FC = ({ forceRender = false, hasMaxRows, indexPath, + isSortable, labels, listeners, moveRow, @@ -94,6 +96,7 @@ export const ArrayRow: React.FC = ({ duplicateRow={duplicateRow} hasMaxRows={hasMaxRows} index={rowIndex} + isSortable={isSortable} moveRow={moveRow} removeRow={removeRow} rowCount={rowCount} diff --git a/packages/payload/src/admin/components/forms/field-types/Array/index.tsx b/packages/payload/src/admin/components/forms/field-types/Array/index.tsx index a81dabce75..90a781f27c 100644 --- a/packages/payload/src/admin/components/forms/field-types/Array/index.tsx +++ b/packages/payload/src/admin/components/forms/field-types/Array/index.tsx @@ -29,7 +29,7 @@ const baseClass = 'array-field' const ArrayFieldType: React.FC = (props) => { const { name, - admin: { className, components, condition, description, readOnly }, + admin: { className, components, condition, description, isSortable = true, readOnly }, fieldTypes, fields, forceRender = false, @@ -113,7 +113,7 @@ const ArrayFieldType: React.FC = (props) => { const duplicateRow = useCallback( (rowIndex: number) => { - dispatchFields({ path, rowIndex, type: 'DUPLICATE_ROW' }) + dispatchFields({ type: 'DUPLICATE_ROW', path, rowIndex }) setModified(true) setTimeout(() => { @@ -133,7 +133,7 @@ const ArrayFieldType: React.FC = (props) => { const moveRow = useCallback( (moveFromIndex: number, moveToIndex: number) => { - dispatchFields({ moveFromIndex, moveToIndex, path, type: 'MOVE_ROW' }) + dispatchFields({ type: 'MOVE_ROW', moveFromIndex, moveToIndex, path }) setModified(true) }, [dispatchFields, path, setModified], @@ -141,14 +141,14 @@ const ArrayFieldType: React.FC = (props) => { const toggleCollapseAll = useCallback( (collapsed: boolean) => { - dispatchFields({ collapsed, path, setDocFieldPreferences, type: 'SET_ALL_ROWS_COLLAPSED' }) + dispatchFields({ type: 'SET_ALL_ROWS_COLLAPSED', collapsed, path, setDocFieldPreferences }) }, [dispatchFields, path, setDocFieldPreferences], ) const setCollapse = useCallback( (rowID: string, collapsed: boolean) => { - dispatchFields({ collapsed, path, rowID, setDocFieldPreferences, type: 'SET_ROW_COLLAPSED' }) + dispatchFields({ type: 'SET_ROW_COLLAPSED', collapsed, path, rowID, setDocFieldPreferences }) }, [dispatchFields, path, setDocFieldPreferences], ) @@ -227,7 +227,7 @@ const ArrayFieldType: React.FC = (props) => { onDragEnd={({ moveFromIndex, moveToIndex }) => moveRow(moveFromIndex, moveToIndex)} > {rows.map((row, i) => ( - + {(draggableSortableItemProps) => ( = (props) => { forceRender={forceRender} hasMaxRows={hasMaxRows} indexPath={indexPath} + isSortable={isSortable} labels={labels} moveRow={moveRow} path={path} diff --git a/packages/payload/src/admin/components/forms/field-types/Blocks/BlockRow.tsx b/packages/payload/src/admin/components/forms/field-types/Blocks/BlockRow.tsx index 93b0d0080f..e623d71fc7 100644 --- a/packages/payload/src/admin/components/forms/field-types/Blocks/BlockRow.tsx +++ b/packages/payload/src/admin/components/forms/field-types/Blocks/BlockRow.tsx @@ -26,6 +26,7 @@ type BlockFieldProps = UseDraggableSortableReturn & duplicateRow: (rowIndex: number) => void forceRender?: boolean hasMaxRows?: boolean + isSortable?: boolean moveRow: (fromIndex: number, toIndex: number) => void readOnly: boolean removeRow: (rowIndex: number) => void @@ -44,6 +45,7 @@ export const BlockRow: React.FC = ({ forceRender, hasMaxRows, indexPath, + isSortable, labels, listeners, moveRow, @@ -90,6 +92,7 @@ export const BlockRow: React.FC = ({ blocks={blocks} duplicateRow={duplicateRow} hasMaxRows={hasMaxRows} + isSortable={isSortable} labels={labels} moveRow={moveRow} removeRow={removeRow} diff --git a/packages/payload/src/admin/components/forms/field-types/Blocks/RowActions.tsx b/packages/payload/src/admin/components/forms/field-types/Blocks/RowActions.tsx index 3e521b02f3..41ba130a14 100644 --- a/packages/payload/src/admin/components/forms/field-types/Blocks/RowActions.tsx +++ b/packages/payload/src/admin/components/forms/field-types/Blocks/RowActions.tsx @@ -13,6 +13,7 @@ export const RowActions: React.FC<{ blocks: Block[] duplicateRow: (rowIndex: number, blockType: string) => void hasMaxRows?: boolean + isSortable?: boolean labels: Labels moveRow: (fromIndex: number, toIndex: number) => void removeRow: (rowIndex: number) => void @@ -25,6 +26,7 @@ export const RowActions: React.FC<{ blocks, duplicateRow, hasMaxRows, + isSortable, labels, moveRow, removeRow, @@ -59,6 +61,7 @@ export const RowActions: React.FC<{ duplicateRow={() => duplicateRow(rowIndex, blockType)} hasMaxRows={hasMaxRows} index={rowIndex} + isSortable={isSortable} moveRow={moveRow} removeRow={removeRow} rowCount={rowCount} diff --git a/packages/payload/src/admin/components/forms/field-types/Blocks/index.tsx b/packages/payload/src/admin/components/forms/field-types/Blocks/index.tsx index 29e3feaf69..1a7a1a1252 100644 --- a/packages/payload/src/admin/components/forms/field-types/Blocks/index.tsx +++ b/packages/payload/src/admin/components/forms/field-types/Blocks/index.tsx @@ -34,7 +34,7 @@ const BlocksField: React.FC = (props) => { const { name, - admin: { className, condition, description, readOnly }, + admin: { className, condition, description, isSortable = true, readOnly }, blocks, fieldTypes, forceRender = false, @@ -230,7 +230,7 @@ const BlocksField: React.FC = (props) => { if (blockToRender) { return ( - + {(draggableSortableItemProps) => ( = (props) => { forceRender={forceRender} hasMaxRows={hasMaxRows} indexPath={indexPath} + isSortable={isSortable} labels={labels} moveRow={moveRow} path={path} diff --git a/packages/payload/src/fields/config/schema.ts b/packages/payload/src/fields/config/schema.ts index d8d3e39f2e..876f019128 100644 --- a/packages/payload/src/fields/config/schema.ts +++ b/packages/payload/src/fields/config/schema.ts @@ -311,6 +311,7 @@ export const array = baseField.keys({ RowLabel: componentSchema, }) .default({}), + isSortable: joi.boolean(), }) .default({}), dbName: joi.alternatives().try(joi.string(), joi.func()), @@ -408,6 +409,11 @@ export const relationship = baseField.keys({ export const blocks = baseField.keys({ name: joi.string().required(), type: joi.string().valid('blocks').required(), + admin: baseAdminFields + .keys({ + isSortable: joi.boolean(), + }) + .default({}), blocks: joi .array() .items( diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index a67abaacdf..8e3dbbf44f 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -564,6 +564,10 @@ export type ArrayField = FieldBase & { RowLabel?: RowLabel } & Admin['components'] initCollapsed?: boolean | false + /** + * Disable drag and drop sorting + */ + isSortable?: boolean } /** * Customize the SQL table name @@ -631,6 +635,10 @@ export type Block = { export type BlockField = FieldBase & { admin?: Admin & { initCollapsed?: boolean | false + /** + * Disable drag and drop sorting + */ + isSortable?: boolean } blocks: Block[] defaultValue?: unknown diff --git a/test/fields/collections/Array/index.ts b/test/fields/collections/Array/index.ts index f0782b0426..166f420003 100644 --- a/test/fields/collections/Array/index.ts +++ b/test/fields/collections/Array/index.ts @@ -155,6 +155,21 @@ const ArrayFields: CollectionConfig = { minRows: 2, type: 'array', }, + { + name: 'disableSortItems', + defaultValue: arrayDefaultValue, + admin: { + isSortable: false, + }, + fields: [ + { + name: 'text', + required: true, + type: 'text', + }, + ], + type: 'array', + }, ], slug: arrayFieldsSlug, versions: true, diff --git a/test/fields/collections/Array/shared.ts b/test/fields/collections/Array/shared.ts index d78c73ac9e..6ae5fc85ef 100644 --- a/test/fields/collections/Array/shared.ts +++ b/test/fields/collections/Array/shared.ts @@ -63,4 +63,15 @@ export const anotherArrayDoc: Partial = { text: 'second row', }, ], + disableSortItems: [ + { + text: 'un-sortable item 1', + }, + { + text: 'un-sortable item 2', + }, + { + text: 'un-sortable item 3', + }, + ], } diff --git a/test/fields/collections/Blocks/index.ts b/test/fields/collections/Blocks/index.ts index 14c08e1cc7..042cc07e59 100644 --- a/test/fields/collections/Blocks/index.ts +++ b/test/fields/collections/Blocks/index.ts @@ -124,6 +124,7 @@ const BlockFields: CollectionConfig = { name: 'collapsedByDefaultBlocks', admin: { initCollapsed: true, + isSortable: false, }, localized: true, }, diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index 9f2ebb45f2..3990b32ea9 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -696,6 +696,12 @@ describe('fields', () => { }) }) }) + + test('should have disabled admin sorting', async () => { + await page.goto(url.create) + const field = page.locator('#field-collapsedByDefaultBlocks .array-actions__action-chevron') + expect(await field.count()).toEqual(0) + }) }) describe('array', () => { @@ -716,6 +722,12 @@ describe('fields', () => { await expect(field).toHaveValue('defaultValue') }) + test('should have disabled admin sorting', async () => { + await page.goto(url.create) + const field = page.locator('#field-disableSortItems .array-actions__action-chevron') + expect(await field.count()).toEqual(0) + }) + test('should render RowLabel using a function', async () => { const label = 'custom row label as function' await page.goto(url.create) diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index a86cd06104..c5b0765c13 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -265,6 +265,10 @@ export interface ArrayField { id?: string | null }[] | null + disableSortItems?: { + text: string + id?: string | null + }[] updatedAt: string createdAt: string }