Skip to content

Commit

Permalink
feat: adds disableListColumn, disableListFilter to fields admin props (
Browse files Browse the repository at this point in the history
  • Loading branch information
PatrikKozak committed May 3, 2024
1 parent b735d6a commit db4aace
Show file tree
Hide file tree
Showing 14 changed files with 136 additions and 28 deletions.
28 changes: 15 additions & 13 deletions docs/fields/overview.mdx
Expand Up @@ -163,19 +163,21 @@ Example:

In addition to each field's base configuration, you can define specific traits and properties for fields that only have effect on how they are rendered in the Admin panel. The following properties are available for all fields within the `admin` property:

| Option | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |
| Option | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `condition` | You can programmatically show / hide fields based on what other fields are doing. [Click here](#conditional-logic) for more info. |
| `components` | All field components can be completely and easily swapped out for custom components that you define. [Click here](#custom-components) for more info. |
| `description` | Helper text to display with the field to provide more information for the editor user. [Click here](#description) for more info. |
| `position` | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| `width` | Restrict the width of a field. you can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| `style` | Attach raw CSS style properties to the root DOM element of a field. |
| `className` | Attach a CSS class name to the root DOM element of a field. |
| `readOnly` | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| `disabled` | If a field is `disabled`, it is completely omitted from the Admin panel. |
| `disableBulkEdit` | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. |
| `disableListColumn` | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
| `disableListFilter` | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| `hidden` | Setting a field's `hidden` property on its `admin` config will transform it into a `hidden` input type. Its value will still submit with the Admin panel's requests, but the field itself will not be visible to editors. |

### Custom components

Expand Down
Expand Up @@ -15,7 +15,7 @@ import './index.scss'
const baseClass = 'column-selector'

const ColumnSelector: React.FC<Props> = (props) => {
const { collection } = props
const { slug } = props

const { columns, moveColumn, toggleColumn } = useTableColumns()

Expand Down Expand Up @@ -53,7 +53,7 @@ const ColumnSelector: React.FC<Props> = (props) => {
draggable
icon={active ? <X /> : <Plus />}
id={accessor}
key={`${collection.slug}-${col.name || i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
key={`${slug}-${col.name || i}${editDepth ? `-${editDepth}-` : ''}${uuid}`}
onClick={() => {
toggleColumn(accessor)
}}
Expand Down
@@ -1,5 +1,5 @@
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'

export type Props = {
collection: SanitizedCollectionConfig
slug: SanitizedCollectionConfig['slug']
}
Expand Up @@ -146,7 +146,7 @@ export const ListControls: React.FC<Props> = (props) => {
height={visibleDrawer === 'columns' ? 'auto' : 0}
id={`${baseClass}-columns`}
>
<ColumnSelector collection={collection} />
<ColumnSelector slug={collection.slug} />
</AnimateHeight>
)}
<AnimateHeight
Expand Down
Expand Up @@ -33,6 +33,13 @@ export const TableColumnContext = createContext<ITableColumns>({} as ITableColum

export const useTableColumns = (): ITableColumns => useContext(TableColumnContext)

const filterTableFields = (fields: Field[]): Field[] => {
return fields.reduce((acc, field) => {
if (!field.admin?.disableListColumn) acc.push(field)
return acc
}, [])
}

export const TableColumnsProvider: React.FC<{
cellProps?: Partial<CellProps>[]
children: React.ReactNode
Expand All @@ -50,9 +57,10 @@ export const TableColumnsProvider: React.FC<{
const hasInitialized = useRef(false)
const { getPreference, setPreference } = usePreferences()
const [formattedFields] = useState<Field[]>(() => formatFields(collection))
const filteredFields = filterTableFields(formattedFields)

const [tableColumns, dispatchTableColumns] = useReducer(columnReducer, {}, () => {
const initialColumns = getInitialColumnState(formattedFields, useAsTitle, defaultColumns)
const initialColumns = getInitialColumnState(filteredFields, useAsTitle, defaultColumns)

return buildColumns({
cellProps,
Expand All @@ -77,13 +85,14 @@ export const TableColumnsProvider: React.FC<{

const currentPreferences = await getPreference<ListPreferences>(preferenceKey)
prevCollection.current = collection.slug
const initialColumns = getInitialColumnState(formattedFields, useAsTitle, defaultColumns)
const initialColumns = getInitialColumnState(filteredFields, useAsTitle, defaultColumns)
const newCols = currentPreferences?.columns || initialColumns

dispatchTableColumns({
type: 'set',
payload: {
cellProps,
collection: { ...collection, fields: formatFields(collection) },
collection: { ...collection, fields: filteredFields },
columns: newCols.map((column) => {
// 'string' is for backwards compatibility
// the preference used to be stored as an array of strings
Expand All @@ -96,14 +105,13 @@ export const TableColumnsProvider: React.FC<{
return column
}),
},
type: 'set',
})

hasInitialized.current = true
}
}

sync()
void sync()
}, [
preferenceKey,
setPreference,
Expand All @@ -113,7 +121,7 @@ export const TableColumnsProvider: React.FC<{
defaultColumns,
collection,
cellProps,
formattedFields,
filteredFields,
])

// /////////////////////////////////////
Expand All @@ -133,6 +141,7 @@ export const TableColumnsProvider: React.FC<{
const setActiveColumns = useCallback(
(columns: string[]) => {
dispatchTableColumns({
type: 'set',
payload: {
// onSelect,
cellProps,
Expand All @@ -142,7 +151,6 @@ export const TableColumnsProvider: React.FC<{
active: true,
})),
},
type: 'set',
})
},
[collection, cellProps],
Expand All @@ -153,13 +161,13 @@ export const TableColumnsProvider: React.FC<{
const { fromIndex, toIndex } = args

dispatchTableColumns({
type: 'move',
payload: {
cellProps,
collection: { ...collection, fields: formatFields(collection) },
fromIndex,
toIndex,
},
type: 'move',
})
},
[collection, cellProps],
Expand All @@ -168,12 +176,12 @@ export const TableColumnsProvider: React.FC<{
const toggleColumn = useCallback(
(column: string) => {
dispatchTableColumns({
type: 'toggle',
payload: {
cellProps,
collection: { ...collection, fields: formatFields(collection) },
column,
},
type: 'toggle',
})
},
[collection, cellProps],
Expand Down
Expand Up @@ -57,8 +57,12 @@ const reduceFields = (fields, i18n) =>
...field,
},
}

if (field.admin?.disableListFilter) return reduced

return [...reduced, formattedField]
}

return reduced
}, [])

Expand Down
Expand Up @@ -9,11 +9,11 @@ const formatFields = (config: SanitizedCollectionConfig): Field[] => {

const defaultIDField: Field = {
name: 'id',
type: 'text',
admin: {
disableBulkEdit: true,
},
label: 'ID',
type: 'text',
}

const shouldSkipField = (field: Field): boolean =>
Expand Down
Expand Up @@ -47,13 +47,13 @@ const ListView: React.FC<ListIndexProps> = (props) => {
const {
collection,
collection: {
slug,
admin: {
components: { views: { List: CustomList } = {} } = {},
listSearchableFields,
pagination: { defaultLimit },
},
labels: { plural },
slug,
},
} = props

Expand Down
2 changes: 2 additions & 0 deletions packages/payload/src/fields/config/schema.ts
Expand Up @@ -19,6 +19,8 @@ export const baseAdminFields = joi.object().keys({
.alternatives()
.try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), componentSchema),
disableBulkEdit: joi.boolean().default(false),
disableListColumn: joi.boolean().default(false),
disableListFilter: joi.boolean().default(false),
disabled: joi.boolean().default(false),
hidden: joi.boolean().default(false),
initCollapsed: joi.boolean().default(false),
Expand Down
4 changes: 4 additions & 0 deletions packages/payload/src/fields/config/types.ts
Expand Up @@ -113,6 +113,8 @@ type Admin = {
condition?: Condition
description?: Description
disableBulkEdit?: boolean
disableListColumn?: boolean
disableListFilter?: boolean
disabled?: boolean
hidden?: boolean
position?: 'sidebar'
Expand Down Expand Up @@ -387,6 +389,8 @@ export type UIField = {
}
condition?: Condition
disableBulkEdit?: boolean
disableListColumn?: boolean
disableListFilter?: boolean
position?: string
width?: string
}
Expand Down
16 changes: 16 additions & 0 deletions test/fields/collections/Text/index.ts
Expand Up @@ -140,6 +140,22 @@ const TextFields: CollectionConfig = {
hasMany: true,
maxRows: 4,
},
{
name: 'disableListColumnText',
type: 'text',
admin: {
disableListColumn: true,
disableListFilter: false,
},
},
{
name: 'disableListFilterText',
type: 'text',
admin: {
disableListColumn: false,
disableListFilter: true,
},
},
],
slug: textFieldsSlug,
}
Expand Down
2 changes: 2 additions & 0 deletions test/fields/collections/Text/shared.ts
Expand Up @@ -6,6 +6,8 @@ export const textFieldsSlug = 'text-fields'
export const textDoc: Partial<TextField> = {
text: 'Seeded text document',
localizedText: 'Localized text',
disableListColumnText: 'Disable List Column Text',
disableListFilterText: 'Disable List Filter Text',
}

export const anotherTextDoc: Partial<TextField> = {
Expand Down
68 changes: 68 additions & 0 deletions test/fields/e2e.spec.ts
Expand Up @@ -64,6 +64,74 @@ describe('fields', () => {
await expect(textCell).toHaveText(textDoc.text)
})

test('should not display field in list view column selector if admin.disableListColumn is true', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()

await expect(page.locator('.column-selector')).toBeVisible()

// Check if "Disable List Column Text" is not present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Column Text'),
}),
).toBeHidden()
})

test('should display field in list view filter selector if admin.disableListColumn is true and admin.disableListFilter is false', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-where').click()
await page.waitForSelector('.list-controls__where.rah-static--height-auto')
await page.locator('.where-builder__add-first-filter').click()

const initialField = page.locator('.condition__field')
await initialField.click()
const initialFieldOptions = initialField.locator('.rs__option')

// Get all text contents of options
const optionsTexts = await initialFieldOptions.allTextContents()

// Check if any option text contains "Disable List Column Text"
const containsText = optionsTexts.some((text) => text.includes('Disable List Column Text'))

// Assert that at least one option contains the desired text
expect(containsText).toBeTruthy()
})

test('should display field in list view column selector if admin.disableListColumn is false and admin.disableListFilter is true', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-columns').click()

await expect(page.locator('.column-selector')).toBeVisible()

// Check if "Disable List Filter Text" is present in the column options
await expect(
page.locator(`.column-selector .column-selector__column`, {
hasText: exactText('Disable List Filter Text'),
}),
).toBeVisible()
})

test('should not display field in list view filter condition selector if admin.disableListFilter is true', async () => {
await page.goto(url.list)
await page.locator('.list-controls__toggle-where').click()
await page.waitForSelector('.list-controls__where.rah-static--height-auto')
await page.locator('.where-builder__add-first-filter').click()

const initialField = page.locator('.condition__field')
await initialField.click()
const initialFieldOptions = initialField.locator('.rs__option')

// Get all text contents of options
const optionsTexts = await initialFieldOptions.allTextContents()

// Check if any option text contains "Disable List Filter Text"
const containsText = optionsTexts.some((text) => text.includes('Disable List Filter Text'))

// Assert that none of the options contain the desired text
expect(containsText).toBeFalsy()
})

test('should display i18n label in cells when missing field data', async () => {
await page.goto(url.list)
const textCell = page.locator('.row-1 .cell-i18nText')
Expand Down
2 changes: 2 additions & 0 deletions test/fields/payload-types.ts
Expand Up @@ -588,6 +588,8 @@ export interface TextField {
localizedHasMany?: string[] | null
withMinRows?: string[] | null
withMaxRows?: string[] | null
disableListColumnText?: string | null
disableListFilterText?: string | null
updatedAt: string
createdAt: string
}
Expand Down

0 comments on commit db4aace

Please sign in to comment.