Files
payload/test/fields/collections/Tabs/index.ts
Paul 878dc54579 feat: added support for conditional tabs (#8720)
Adds support for conditional tabs.

You can now add a `condition` function like other fields to each
individual tab's admin config like so:

```ts
{
  name: 'contentTab',
  admin: {
    condition: (data) => Boolean(data?.enableTab)
  }
}
```

This will toggle the individual tab's visibility in the document listing

### Example


https://github.com/user-attachments/assets/45cf9cfd-eaed-4dfe-8a32-1992385fd05c

This is an updated PR from
https://github.com/payloadcms/payload/pull/8406 thanks to @willviles

---------

Co-authored-by: Will Viles <will@willviles.com>
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-03-13 13:32:53 +00:00

426 lines
12 KiB
TypeScript

import type { CollectionConfig } from 'payload'
import { tabsFieldsSlug } from '../../slugs.js'
import { getBlocksField } from '../Blocks/index.js'
import { namedTabDefaultValue } from './constants.js'
const TabsFields: CollectionConfig = {
slug: tabsFieldsSlug,
access: {
read: () => true,
},
versions: true,
fields: [
{
name: 'sidebarField',
type: 'text',
label: 'Sidebar Field',
admin: {
position: 'sidebar',
description:
'This should not collapse despite there being many tabs pushing the main fields open.',
},
},
{
name: 'conditionalTabVisible',
type: 'checkbox',
label: 'Toggle Conditional Tab',
admin: {
position: 'sidebar',
description:
'When active, the conditional tab should be visible. When inactive, it should be hidden.',
},
},
{
type: 'tabs',
tabs: [
{
name: 'conditionalTab',
label: 'Conditional Tab',
description: 'This tab should only be visible when the conditional field is checked.',
fields: [
{
name: 'conditionalTabField',
type: 'text',
label: 'Conditional Tab Field',
defaultValue:
'This field should only be visible when the conditional tab is visible.',
},
{
name: 'nestedConditionalTabVisible',
type: 'checkbox',
label: 'Toggle Nested Conditional Tab',
defaultValue: true,
admin: {
description:
'When active, the nested conditional tab should be visible. When inactive, it should be hidden.',
},
},
{
type: 'group',
name: 'conditionalTabGroup',
fields: [
{
type: 'text',
name: 'conditionalTabGroupTitle',
},
{
type: 'tabs',
tabs: [
{
// duplicate name as above, should not conflict with tab IDs in form-state, if it does then tests will fail
name: 'conditionalTab',
label: 'Duplicate conditional tab',
fields: [],
admin: {
condition: ({ conditionalTab }) =>
!!conditionalTab?.nestedConditionalTabVisible,
},
},
],
},
],
},
{
type: 'tabs',
tabs: [
{
label: 'Nested Unconditional Tab',
description: 'Description for a nested unconditional tab',
fields: [
{
name: 'nestedUnconditionalTabInput',
type: 'text',
},
],
},
{
label: 'Nested Conditional Tab',
description: 'Here is a description for a nested conditional tab',
fields: [
{
name: 'nestedConditionalTabInput',
type: 'textarea',
defaultValue:
'This field should only be visible when the nested conditional tab is visible.',
},
],
admin: {
condition: ({ conditionalTab }) =>
!!conditionalTab?.nestedConditionalTabVisible,
},
},
],
},
],
admin: {
condition: ({ conditionalTabVisible }) => !!conditionalTabVisible,
},
},
{
label: 'Tab with Array',
description: 'This tab has an array.',
fields: [
{
type: 'ui',
name: 'demoUIField',
label: 'Demo UI Field',
admin: {
components: {
Field: '/collections/Tabs/UIField.js#UIField',
},
},
},
{
name: 'array',
labels: {
singular: 'Item',
plural: 'Items',
},
type: 'array',
required: true,
fields: [
// path: 'array.n.text'
// schemaPath: '_index-1-0.array.text'
{
name: 'text',
type: 'text',
required: true,
},
],
},
],
},
{
label: 'Tab with Blocks',
description: 'Blocks are rendered here to ensure they populate and render correctly.',
fields: [getBlocksField()],
},
{
label: 'Tab with Group',
description: 'This tab has a group, which should not render its top border or margin.',
fields: [
{
name: 'group',
type: 'group',
label: 'Group',
fields: [
{
name: 'number',
type: 'number',
required: true,
},
],
},
],
},
{
label: 'Tab with Row',
description: 'This tab has a row field.',
fields: [
{
type: 'row',
fields: [
{
name: 'textInRow',
type: 'text',
required: true,
admin: {
width: '50%',
},
},
{
name: 'numberInRow',
type: 'number',
required: true,
admin: {
width: '50%',
},
},
],
},
{
name: 'json',
type: 'json',
},
],
},
{
name: 'tab',
label: 'Tab with Name',
interfaceName: 'TabWithName',
description: 'This tab has a name, which should namespace the contained fields.',
fields: [
{
name: 'array',
labels: {
singular: 'Item',
plural: 'Items',
},
type: 'array',
required: true,
fields: [
{
name: 'text',
type: 'text',
required: true,
},
],
},
{
name: 'text',
type: 'text',
},
{
name: 'defaultValue',
type: 'text',
defaultValue: namedTabDefaultValue,
},
{
type: 'row',
fields: [
{
name: 'arrayInRow',
type: 'array',
fields: [
{
name: 'textInArrayInRow',
type: 'text',
},
],
},
],
},
],
},
{
name: 'namedTabWithDefaultValue',
description: 'This tab has a name, which should namespace the contained fields.',
fields: [
{
name: 'defaultValue',
type: 'text',
defaultValue: namedTabDefaultValue,
},
],
},
{
name: 'localizedTab',
label: { en: 'Localized Tab en', es: 'Localized Tab es' },
localized: true,
description: 'This tab is localized and requires a name',
fields: [
{
name: 'text',
type: 'text',
},
],
},
{
name: 'accessControlTab',
access: {
read: () => false,
},
description: 'This tab is cannot be read',
fields: [
{
name: 'text',
type: 'text',
},
],
},
{
name: 'hooksTab',
label: 'Hooks Tab',
hooks: {
beforeValidate: [
({ data = {} }) => {
if (!data.hooksTab) {
data.hooksTab = {}
}
data.hooksTab.beforeValidate = true
return data.hooksTab
},
],
beforeChange: [
({ data = {} }) => {
if (!data.hooksTab) {
data.hooksTab = {}
}
data.hooksTab.beforeChange = true
return data.hooksTab
},
],
afterChange: [
({ originalDoc }) => {
originalDoc.hooksTab.afterChange = true
return originalDoc.hooksTab
},
],
afterRead: [
({ data = {} }) => {
if (!data.hooksTab) {
data.hooksTab = {}
}
data.hooksTab.afterRead = true
return data.hooksTab
},
],
},
description: 'This tab has hooks',
fields: [
{
name: 'beforeValidate',
type: 'checkbox',
},
{
name: 'beforeChange',
type: 'checkbox',
},
{
name: 'afterChange',
type: 'checkbox',
},
{
name: 'afterRead',
type: 'checkbox',
},
],
},
{
name: 'camelCaseTab',
fields: [
{
name: 'array',
type: 'array',
fields: [
{
type: 'text',
name: 'text',
localized: true,
},
{
type: 'array',
name: 'array',
fields: [
{
type: 'text',
name: 'text',
},
],
},
],
},
],
},
],
},
{
type: 'collapsible',
label: 'Tabs within Collapsible',
fields: [
{
type: 'tabs',
tabs: [
{
label: 'Nested Tab One',
description: 'Here is a description for a nested tab',
fields: [
{
name: 'textarea',
type: 'textarea',
},
],
},
{
label: 'Nested Tab Two',
description: 'Description for tab two',
fields: [
{
name: 'anotherText',
type: 'text',
required: true,
},
],
},
{
name: 'nestedTab',
label: 'Nested Tab with Name',
description: 'This tab has a name, which should namespace the contained fields.',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
},
],
},
],
}
export default TabsFields