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>
This commit is contained in:
@@ -126,12 +126,69 @@ describe('Tabs', () => {
|
||||
|
||||
test('should render array data within named tabs', async () => {
|
||||
await navigateToDoc(page, url)
|
||||
await switchTab(page, '.tabs-field__tab-button:nth-child(5)')
|
||||
await switchTab(page, '.tabs-field__tab-button:has-text("Tab with Name")')
|
||||
await expect(page.locator('#field-tab__array__0__text')).toHaveValue(
|
||||
"Hello, I'm the first row, in a named tab",
|
||||
)
|
||||
})
|
||||
|
||||
test('should render conditional tab when checkbox is toggled', async () => {
|
||||
await navigateToDoc(page, url)
|
||||
|
||||
const conditionalTabSelector = '.tabs-field__tab-button:text-is("Conditional Tab")'
|
||||
const button = page.locator(conditionalTabSelector)
|
||||
await expect(
|
||||
async () => await expect(page.locator(conditionalTabSelector)).toHaveClass(/--hidden/),
|
||||
).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
|
||||
const checkboxSelector = `input#field-conditionalTabVisible`
|
||||
await page.locator(checkboxSelector).check()
|
||||
await expect(page.locator(checkboxSelector)).toBeChecked()
|
||||
|
||||
await expect(
|
||||
async () => await expect(page.locator(conditionalTabSelector)).not.toHaveClass(/--hidden/),
|
||||
).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
|
||||
await switchTab(page, conditionalTabSelector)
|
||||
|
||||
await expect(
|
||||
page.locator('label[for="field-conditionalTab__conditionalTabField"]'),
|
||||
).toHaveCount(1)
|
||||
})
|
||||
|
||||
test('should hide nested conditional tab when checkbox is toggled', async () => {
|
||||
await navigateToDoc(page, url)
|
||||
|
||||
// Show the conditional tab
|
||||
const conditionalTabSelector = '.tabs-field__tab-button:text-is("Conditional Tab")'
|
||||
const checkboxSelector = `input#field-conditionalTabVisible`
|
||||
await page.locator(checkboxSelector).check()
|
||||
await switchTab(page, conditionalTabSelector)
|
||||
|
||||
// Now assert on the nested conditional tab
|
||||
const nestedConditionalTabSelector = '.tabs-field__tab-button:text-is("Nested Conditional Tab")'
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await expect(page.locator(nestedConditionalTabSelector)).not.toHaveClass(/--hidden/),
|
||||
).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
|
||||
const nestedCheckboxSelector = `input#field-conditionalTab__nestedConditionalTabVisible`
|
||||
await page.locator(nestedCheckboxSelector).uncheck()
|
||||
|
||||
await expect(
|
||||
async () => await expect(page.locator(nestedConditionalTabSelector)).toHaveClass(/--hidden/),
|
||||
).toPass({
|
||||
timeout: POLL_TOPASS_TIMEOUT,
|
||||
})
|
||||
})
|
||||
|
||||
test('should save preferences for tab order', async () => {
|
||||
await page.goto(url.list)
|
||||
|
||||
@@ -139,7 +196,7 @@ describe('Tabs', () => {
|
||||
const href = await firstItem.getAttribute('href')
|
||||
await firstItem.click()
|
||||
|
||||
const regex = new RegExp(href.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
||||
const regex = new RegExp(href!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
||||
|
||||
await page.waitForURL(regex)
|
||||
|
||||
|
||||
@@ -21,9 +21,103 @@ const TabsFields: CollectionConfig = {
|
||||
'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.',
|
||||
|
||||
@@ -1831,6 +1831,19 @@ export interface TabsField {
|
||||
* This should not collapse despite there being many tabs pushing the main fields open.
|
||||
*/
|
||||
sidebarField?: string | null;
|
||||
/**
|
||||
* When active, the conditional tab should be visible. When inactive, it should be hidden.
|
||||
*/
|
||||
conditionalTabVisible?: boolean | null;
|
||||
conditionalTab?: {
|
||||
conditionalTabField?: string | null;
|
||||
/**
|
||||
* When active, the nested conditional tab should be visible. When inactive, it should be hidden.
|
||||
*/
|
||||
nestedConditionalTabVisible?: boolean | null;
|
||||
nestedUnconditionalTabInput?: string | null;
|
||||
nestedConditionalTabInput?: string | null;
|
||||
};
|
||||
array: {
|
||||
text: string;
|
||||
id?: string | null;
|
||||
@@ -3452,6 +3465,15 @@ export interface TabsFields2Select<T extends boolean = true> {
|
||||
*/
|
||||
export interface TabsFieldsSelect<T extends boolean = true> {
|
||||
sidebarField?: T;
|
||||
conditionalTabVisible?: T;
|
||||
conditionalTab?:
|
||||
| T
|
||||
| {
|
||||
conditionalTabField?: T;
|
||||
nestedConditionalTabVisible?: T;
|
||||
nestedUnconditionalTabInput?: T;
|
||||
nestedConditionalTabInput?: T;
|
||||
};
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
|
||||
Reference in New Issue
Block a user