From ea84e82ad502f26475561ecb08af5ca02b4d036f Mon Sep 17 00:00:00 2001 From: Patrik Date: Fri, 10 May 2024 15:59:29 -0400 Subject: [PATCH] feat(payload, ui): adds `disableListColumn` & `disableListFilter` to fields `admin` props (#6238) --- docs/fields/overview.mdx | 28 +++++----- packages/payload/src/fields/config/schema.ts | 2 + packages/payload/src/fields/config/types.ts | 20 +++++++ .../ui/src/elements/ColumnSelector/index.tsx | 14 ++++- packages/ui/src/elements/Table/index.tsx | 1 + .../TableColumns/buildColumnState.tsx | 4 ++ .../elements/WhereBuilder/reduceFieldMap.tsx | 2 + .../ComponentMap/buildComponentMap/fields.tsx | 4 ++ .../ComponentMap/buildComponentMap/types.ts | 2 + test/fields/collections/Text/index.ts | 16 ++++++ test/fields/collections/Text/shared.ts | 2 + test/fields/e2e.spec.ts | 55 +++++++++++++++++++ test/fields/payload-types.ts | 6 +- 13 files changed, 139 insertions(+), 17 deletions(-) diff --git a/docs/fields/overview.mdx b/docs/fields/overview.mdx index a006c64d0d..3662f3a49e 100644 --- a/docs/fields/overview.mdx +++ b/docs/fields/overview.mdx @@ -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 diff --git a/packages/payload/src/fields/config/schema.ts b/packages/payload/src/fields/config/schema.ts index e7f08a0e3c..8c2799fd2a 100644 --- a/packages/payload/src/fields/config/schema.ts +++ b/packages/payload/src/fields/config/schema.ts @@ -21,6 +21,8 @@ export const baseAdminFields = joi.object().keys({ .alternatives() .try(joi.string(), joi.object().pattern(joi.string(), [joi.string()]), joi.function()), 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), diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index be0f6f9132..0b986e4bab 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -132,6 +132,16 @@ type Admin = { custom?: Record description?: Description disableBulkEdit?: boolean + /** + * Shows / hides fields from appearing in the list view column selector. + * @type boolean + */ + disableListColumn?: boolean + /** + * Shows / hides fields from appearing in the list view filter options. + * @type boolean + */ + disableListFilter?: boolean disabled?: boolean hidden?: boolean position?: 'sidebar' @@ -443,6 +453,16 @@ export type UIField = { /** Extension point to add your custom data. Available in server and client. */ custom?: Record disableBulkEdit?: boolean + /** + * Shows / hides fields from appearing in the list view column selector. + * @type boolean + */ + disableListColumn?: boolean + /** + * Shows / hides fields from appearing in the list view filter options. + * @type boolean + */ + disableListFilter?: boolean position?: string width?: string } diff --git a/packages/ui/src/elements/ColumnSelector/index.tsx b/packages/ui/src/elements/ColumnSelector/index.tsx index b7016aa5a3..2341eeb793 100644 --- a/packages/ui/src/elements/ColumnSelector/index.tsx +++ b/packages/ui/src/elements/ColumnSelector/index.tsx @@ -3,6 +3,8 @@ import type { SanitizedCollectionConfig } from 'payload/types' import React, { useId } from 'react' +import type { Column } from '../Table/index.js' + import { Plus } from '../../icons/Plus/index.js' import { X } from '../../icons/X/index.js' import { useEditDepth } from '../../providers/EditDepth/index.js' @@ -17,6 +19,12 @@ export type Props = { collectionSlug: SanitizedCollectionConfig['slug'] } +const filterColumnFields = (fields: Column[]): Column[] => { + return fields.filter((field) => { + return !field.admin?.disableListColumn + }) +} + export const ColumnSelector: React.FC = ({ collectionSlug }) => { const { columns, moveColumn, toggleColumn } = useTableColumns() @@ -27,10 +35,12 @@ export const ColumnSelector: React.FC = ({ collectionSlug }) => { return null } + const filteredColumns = filterColumnFields(columns) + return ( col?.accessor)} + ids={filteredColumns.map((col) => col?.accessor)} onDragEnd={({ moveFromIndex, moveToIndex }) => { moveColumn({ fromIndex: moveFromIndex, @@ -38,7 +48,7 @@ export const ColumnSelector: React.FC = ({ collectionSlug }) => { }) }} > - {columns.map((col, i) => { + {filteredColumns.map((col, i) => { if (!col) return null const { Label, accessor, active } = col diff --git a/packages/ui/src/elements/Table/index.tsx b/packages/ui/src/elements/Table/index.tsx index 9ee1b16752..a4b871bdd0 100644 --- a/packages/ui/src/elements/Table/index.tsx +++ b/packages/ui/src/elements/Table/index.tsx @@ -21,6 +21,7 @@ export type Column = { Label: React.ReactNode accessor: string active: boolean + admin?: FieldBase['admin'] cellProps?: Partial components: { Cell: React.ReactNode diff --git a/packages/ui/src/elements/TableColumns/buildColumnState.tsx b/packages/ui/src/elements/TableColumns/buildColumnState.tsx index 7b070ba07b..af900e7fdd 100644 --- a/packages/ui/src/elements/TableColumns/buildColumnState.tsx +++ b/packages/ui/src/elements/TableColumns/buildColumnState.tsx @@ -134,6 +134,10 @@ export const buildColumnState = (args: Args): Column[] => { Label, accessor: name, active, + admin: { + disableListColumn: field.disableListColumn, + disableListFilter: field.disableListFilter, + }, cellProps: { ...field.cellComponentProps, ...cellProps?.[index], diff --git a/packages/ui/src/elements/WhereBuilder/reduceFieldMap.tsx b/packages/ui/src/elements/WhereBuilder/reduceFieldMap.tsx index c32196cab9..c4c840753c 100644 --- a/packages/ui/src/elements/WhereBuilder/reduceFieldMap.tsx +++ b/packages/ui/src/elements/WhereBuilder/reduceFieldMap.tsx @@ -32,6 +32,8 @@ export const reduceFieldMap = (fieldMap: Column[], i18n) => }, } + if (field.admin?.disableListFilter) return reduced + return [...reduced, formattedField] } diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx b/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx index 4c12861f93..cbf0fa9ce5 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/fields.tsx @@ -755,6 +755,10 @@ export const mapFields = (args: { custom: field?.admin?.custom, disableBulkEdit: 'admin' in field && 'disableBulkEdit' in field.admin && field.admin.disableBulkEdit, + disableListColumn: + 'admin' in field && 'disableListColumn' in field.admin && field.admin.disableListColumn, + disableListFilter: + 'admin' in field && 'disableListFilter' in field.admin && field.admin.disableListFilter, fieldComponentProps, fieldIsPresentational, isFieldAffectingData, diff --git a/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts b/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts index 69c8841417..b2b125d0b2 100644 --- a/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts +++ b/packages/ui/src/providers/ComponentMap/buildComponentMap/types.ts @@ -71,6 +71,8 @@ export type MappedField = { cellComponentProps: CellComponentProps custom?: Record disableBulkEdit?: boolean + disableListColumn?: boolean + disableListFilter?: boolean disabled?: boolean fieldComponentProps: FieldComponentProps fieldIsPresentational: boolean diff --git a/test/fields/collections/Text/index.ts b/test/fields/collections/Text/index.ts index fa4afccfd9..e4c4af8062 100644 --- a/test/fields/collections/Text/index.ts +++ b/test/fields/collections/Text/index.ts @@ -141,6 +141,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, + }, + }, ], } diff --git a/test/fields/collections/Text/shared.ts b/test/fields/collections/Text/shared.ts index d4e7c25683..e811690ef1 100644 --- a/test/fields/collections/Text/shared.ts +++ b/test/fields/collections/Text/shared.ts @@ -6,6 +6,8 @@ export const textFieldsSlug = 'text-fields' export const textDoc: Partial = { text: 'Seeded text document', localizedText: 'Localized text', + disableListColumnText: 'Disable List Column Text', + disableListFilterText: 'Disable List Filter Text', } export const anotherTextDoc: Partial = { diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index 073e79ff29..46e08c8d8b 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -10,6 +10,7 @@ import type { Config } from './payload-types.js' import { ensureAutoLoginAndCompilationIsDone, + exactText, initPageConsoleErrorCatch, navigateToListCellLink, openDocDrawer, @@ -89,6 +90,60 @@ describe('fields', () => { await expect(textCell).toHaveText(textDoc.text) }) + test('should hide field in column selector when admin.disableListColumn', 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 show field in filter when admin.disableListColumn is true', async () => { + await page.goto(url.list) + await page.locator('.list-controls__toggle-where').click() + await page.locator('.where-builder__add-first-filter').click() + + const initialField = page.locator('.condition__field') + await initialField.click() + + await expect( + initialField.locator(`.rs__menu-list:has-text("Disable List Column Text")`), + ).toBeVisible() + }) + + 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 hide field in filter when admin.disableListFilter is true', async () => { + await page.goto(url.list) + await page.locator('.list-controls__toggle-where').click() + await page.locator('.where-builder__add-first-filter').click() + + const initialField = page.locator('.condition__field') + await initialField.click() + + await expect( + initialField.locator(`.rs__option :has-text("Disable List Filter Text")`), + ).toBeHidden() + }) + 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') diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index c24953c905..bf3bd601e5 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -698,6 +698,8 @@ export interface TextField { localizedHasMany?: string[] | null; withMinRows?: string[] | null; withMaxRows?: string[] | null; + disableListColumnText?: string | null; + disableListFilterText?: string | null; updatedAt: string; createdAt: string; } @@ -1411,6 +1413,6 @@ export interface LexicalBlocksRadioButtonsBlock { declare module 'payload' { - // @ts-ignore + // @ts-ignore export interface GeneratedTypes extends Config {} -} \ No newline at end of file +}