feat(ui): toggle sortable arrays and blocks (#6008)

This commit is contained in:
Kendell Joseph
2024-05-08 13:28:26 -04:00
committed by GitHub
parent dc8c099d9e
commit 4c6aaafe88
14 changed files with 129 additions and 18 deletions

View File

@@ -57,6 +57,7 @@ In addition to the default [field admin config](/docs/fields/overview#admin-conf
| ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| **`components.RowLabel`** | Function or React component to be rendered as the label on the array row. Receives `({ data, index, path })` as args |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
### Example

View File

@@ -53,9 +53,10 @@ _\* An asterisk denotes that a property is required._
In addition to the default [field admin config](/docs/fields/overview#admin-config), you can adjust the following properties:
| Option | Description |
| ------------------- | ------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| Option | Description |
| ------------------- | ---------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
### Block configs

View File

@@ -318,6 +318,7 @@ export const array = baseField.keys({
RowLabel: componentSchema,
})
.default({}),
isSortable: joi.boolean(),
})
.default({}),
dbName: joi.alternatives().try(joi.string(), joi.func()),
@@ -415,6 +416,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(

View File

@@ -613,6 +613,10 @@ export type ArrayField = FieldBase & {
RowLabel?: RowLabel
} & Admin['components']
initCollapsed?: boolean
/**
* Disable drag and drop sorting
*/
isSortable?: boolean
}
/**
* Customize the SQL table name
@@ -684,6 +688,10 @@ export type Block = {
export type BlockField = FieldBase & {
admin?: Admin & {
initCollapsed?: boolean
/**
* Disable drag and drop sorting
*/
isSortable?: boolean
}
blocks: Block[]
defaultValue?: unknown

View File

@@ -17,6 +17,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
@@ -27,6 +28,7 @@ export const ArrayAction: React.FC<Props> = ({
duplicateRow,
hasMaxRows,
index,
isSortable,
moveRow,
removeRow,
rowCount,
@@ -42,7 +44,7 @@ export const ArrayAction: React.FC<Props> = ({
render={({ close }) => {
return (
<PopupList.ButtonGroup buttonSize="small">
{index !== 0 && (
{isSortable && index !== 0 && (
<PopupList.Button
className={`${baseClass}__action ${baseClass}__move-up`}
onClick={() => {
@@ -56,7 +58,7 @@ export const ArrayAction: React.FC<Props> = ({
{t('general:moveUp')}
</PopupList.Button>
)}
{index < rowCount - 1 && (
{isSortable && index < rowCount - 1 && (
<PopupList.Button
className={`${baseClass}__action`}
onClick={() => {

View File

@@ -27,6 +27,7 @@ type ArrayRowProps = UseDraggableSortableReturn & {
forceRender?: boolean
hasMaxRows?: boolean
indexPath: string
isSortable?: boolean
labels: ArrayField['labels']
moveRow: (fromIndex: number, toIndex: number) => void
path: string
@@ -50,6 +51,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
forceRender = false,
hasMaxRows,
indexPath,
isSortable,
labels,
listeners,
moveRow,
@@ -100,6 +102,7 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
duplicateRow={duplicateRow}
hasMaxRows={hasMaxRows}
index={rowIndex}
isSortable={isSortable}
moveRow={moveRow}
removeRow={removeRow}
rowCount={rowCount}
@@ -108,11 +111,15 @@ export const ArrayRow: React.FC<ArrayRowProps> = ({
}
className={classNames}
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
dragHandleProps={{
id: row.id,
attributes,
listeners,
}}
dragHandleProps={
isSortable
? {
id: row.id,
attributes,
listeners,
}
: undefined
}
header={
<div className={`${baseClass}__row-header`}>
<RowLabel

View File

@@ -37,6 +37,7 @@ export type ArrayFieldProps = FormFieldBase & {
CustomRowLabel?: React.ReactNode
fieldMap: FieldMap
forceRender?: boolean
isSortable?: boolean
label?: FieldBase['label']
labels?: ArrayFieldType['labels']
maxRows?: ArrayFieldType['maxRows']
@@ -58,6 +59,7 @@ export const _ArrayField: React.FC<ArrayFieldProps> = (props) => {
errorProps,
fieldMap,
forceRender = false,
isSortable = true,
label,
labelProps,
localized,
@@ -261,7 +263,7 @@ export const _ArrayField: React.FC<ArrayFieldProps> = (props) => {
errorPath.startsWith(`${path}.${i}.`),
).length
return (
<DraggableSortableItem disabled={readOnly} id={row.id} key={row.id}>
<DraggableSortableItem disabled={readOnly || !isSortable} id={row.id} key={row.id}>
{(draggableSortableItemProps) => (
<ArrayRow
{...draggableSortableItemProps}
@@ -273,6 +275,7 @@ export const _ArrayField: React.FC<ArrayFieldProps> = (props) => {
forceRender={forceRender}
hasMaxRows={hasMaxRows}
indexPath={indexPath}
isSortable={isSortable}
labels={labels}
moveRow={moveRow}
path={path}

View File

@@ -28,6 +28,7 @@ type BlockFieldProps = UseDraggableSortableReturn & {
forceRender?: boolean
hasMaxRows?: boolean
indexPath: string
isSortable?: boolean
labels: Labels
moveRow: (fromIndex: number, toIndex: number) => void
path: string
@@ -50,6 +51,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
errorCount,
forceRender,
hasMaxRows,
isSortable,
labels,
listeners,
moveRow,
@@ -97,6 +99,7 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
duplicateRow={duplicateRow}
fieldMap={block.fieldMap}
hasMaxRows={hasMaxRows}
isSortable={isSortable}
labels={labels}
moveRow={moveRow}
removeRow={removeRow}
@@ -107,11 +110,15 @@ export const BlockRow: React.FC<BlockFieldProps> = ({
}
className={classNames}
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
dragHandleProps={{
id: row.id,
attributes,
listeners,
}}
dragHandleProps={
isSortable
? {
id: row.id,
attributes,
listeners,
}
: undefined
}
header={
<div className={`${baseClass}__block-header`}>
<span className={`${baseClass}__block-number`}>

View File

@@ -20,6 +20,7 @@ export const RowActions: React.FC<{
duplicateRow: (rowIndex: number, blockType: string) => void
fieldMap: FieldMap
hasMaxRows?: boolean
isSortable?: boolean
labels: Labels
moveRow: (fromIndex: number, toIndex: number) => void
removeRow: (rowIndex: number) => void
@@ -32,6 +33,7 @@ export const RowActions: React.FC<{
blocks,
duplicateRow,
hasMaxRows,
isSortable,
labels,
moveRow,
removeRow,
@@ -67,6 +69,7 @@ export const RowActions: React.FC<{
duplicateRow={() => duplicateRow(rowIndex, blockType)}
hasMaxRows={hasMaxRows}
index={rowIndex}
isSortable={isSortable}
moveRow={moveRow}
removeRow={removeRow}
rowCount={rowCount}

View File

@@ -39,6 +39,7 @@ import type { FormFieldBase } from '../shared/index.js'
export type BlocksFieldProps = FormFieldBase & {
blocks?: ReducedBlock[]
forceRender?: boolean
isSortable?: boolean
label?: FieldBase['label']
labels?: BlockField['labels']
maxRows?: number
@@ -62,6 +63,7 @@ const _BlocksField: React.FC<BlocksFieldProps> = (props) => {
descriptionProps,
errorProps,
forceRender = false,
isSortable = true,
label,
labelProps,
labels: labelsFromProps,
@@ -277,7 +279,7 @@ const _BlocksField: React.FC<BlocksFieldProps> = (props) => {
errorPath.startsWith(`${path}.${i}`),
).length
return (
<DraggableSortableItem disabled={readOnly} id={row.id} key={row.id}>
<DraggableSortableItem disabled={readOnly || !isSortable} id={row.id} key={row.id}>
{(draggableSortableItemProps) => (
<BlockRow
{...draggableSortableItemProps}
@@ -289,6 +291,7 @@ const _BlocksField: React.FC<BlocksFieldProps> = (props) => {
forceRender={forceRender}
hasMaxRows={hasMaxRows}
indexPath={indexPath}
isSortable={isSortable}
labels={labels}
moveRow={moveRow}
path={path}

View File

@@ -269,6 +269,7 @@ export const mapFields = (args: {
parentPath: path,
readOnly: readOnlyOverride,
}),
isSortable: field.admin?.isSortable,
label: field?.label,
labels: field.labels,
maxRows: field.maxRows,
@@ -312,6 +313,7 @@ export const mapFields = (args: {
blocks,
className: field.admin?.className,
disabled: field.admin?.disabled,
isSortable: field.admin?.isSortable,
label: field?.label,
labels: field.labels,
maxRows: field.maxRows,

View File

@@ -139,6 +139,21 @@ const ArrayFields: CollectionConfig = {
minRows: 2,
type: 'array',
},
{
name: 'disableSort',
defaultValue: arrayDefaultValue,
admin: {
isSortable: false,
},
fields: [
{
name: 'text',
required: true,
type: 'text',
},
],
type: 'array',
},
],
slug: arrayFieldsSlug,
versions: true,

View File

@@ -130,6 +130,14 @@ const BlockFields: CollectionConfig = {
},
localized: true,
},
{
...getBlocksField('localized'),
name: 'disableSort',
admin: {
isSortable: false,
},
localized: true,
},
{
...getBlocksField('localized'),
name: 'localizedBlocks',

View File

@@ -24,7 +24,14 @@ import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
import { jsonDoc } from './collections/JSON/shared.js'
import { numberDoc } from './collections/Number/shared.js'
import { textDoc } from './collections/Text/shared.js'
import { collapsibleFieldsSlug, pointFieldsSlug, tabsFieldsSlug, textFieldsSlug } from './slugs.js'
import {
arrayFieldsSlug,
blockFieldsSlug,
collapsibleFieldsSlug,
pointFieldsSlug,
tabsFieldsSlug,
textFieldsSlug,
} from './slugs.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@@ -536,6 +543,44 @@ describe('fields', () => {
})
})
describe('sortable arrays', () => {
let url: AdminUrlUtil
beforeAll(() => {
url = new AdminUrlUtil(serverURL, arrayFieldsSlug)
})
test('should have disabled admin sorting', async () => {
await page.goto(url.create)
const field = page.locator('#field-disableSort .array-actions__action-chevron')
expect(await field.count()).toEqual(0)
})
test('the drag handle should be hidden', async () => {
await page.goto(url.create)
const field = page.locator('#field-disableSort .collapsible__drag')
expect(await field.count()).toEqual(0)
})
})
describe('sortable blocks', () => {
let url: AdminUrlUtil
beforeAll(() => {
url = new AdminUrlUtil(serverURL, blockFieldsSlug)
})
test('should have disabled admin sorting', async () => {
await page.goto(url.create)
const field = page.locator('#field-disableSort .array-actions__action-chevron')
expect(await field.count()).toEqual(0)
})
test('the drag handle should be hidden', async () => {
await page.goto(url.create)
const field = page.locator('#field-disableSort .collapsible__drag')
expect(await field.count()).toEqual(0)
})
})
describe('tabs', () => {
let url: AdminUrlUtil
beforeAll(() => {