diff --git a/packages/ui/src/elements/WhereBuilder/Condition/index.tsx b/packages/ui/src/elements/WhereBuilder/Condition/index.tsx index c57eab59fe..23c849bf21 100644 --- a/packages/ui/src/elements/WhereBuilder/Condition/index.tsx +++ b/packages/ui/src/elements/WhereBuilder/Condition/index.tsx @@ -141,6 +141,11 @@ export const Condition: React.FC = (props) => {
+ ((option?.data?.plainTextLabel as string) || option.label) + .toLowerCase() + .includes(inputValue.toLowerCase()) + } isClearable={false} onChange={handleFieldChange} options={reducedFields.filter((field) => !field.field.admin.disableListFilter)} diff --git a/packages/ui/src/elements/WhereBuilder/reduceFields.tsx b/packages/ui/src/elements/WhereBuilder/reduceFields.tsx index de2b079569..76728b1c72 100644 --- a/packages/ui/src/elements/WhereBuilder/reduceFields.tsx +++ b/packages/ui/src/elements/WhereBuilder/reduceFields.tsx @@ -4,6 +4,7 @@ import type { ClientField } from 'payload' import { getTranslation } from '@payloadcms/translations' import { fieldIsHiddenOrDisabled, fieldIsID, tabHasName } from 'payload/shared' +import { renderToStaticMarkup } from 'react-dom/server' import type { ReducedField } from './types.js' @@ -152,10 +153,15 @@ export const reduceFields = ({ }) : localizedLabel + // React elements in filter options are not searchable in React Select + // Extract plain text to make them filterable in dropdowns + const textFromLabel = extractTextFromReactNode(formattedLabel) + const fieldPath = pathPrefix ? createNestedClientFieldPath(pathPrefix, field) : field.name const formattedField: ReducedField = { label: formattedLabel, + plainTextLabel: textFromLabel, value: fieldPath, ...fieldTypes[field.type], field, @@ -168,3 +174,29 @@ export const reduceFields = ({ return reduced }, []) } + +/** + * Extracts plain text content from a React node by removing HTML tags. + * Used to make React elements searchable in filter dropdowns. + */ +const extractTextFromReactNode = (reactNode: React.ReactNode): string => { + if (!reactNode) { + return '' + } + if (typeof reactNode === 'string') { + return reactNode + } + + const html = renderToStaticMarkup(reactNode) + + // Handle different environments (server vs browser) + if (typeof document !== 'undefined') { + // Browser environment - use actual DOM + const div = document.createElement('div') + div.innerHTML = html + return div.textContent || '' + } else { + // Server environment - use regex to strip HTML tags + return html.replace(/<[^>]*>/g, '') + } +} diff --git a/packages/ui/src/elements/WhereBuilder/types.ts b/packages/ui/src/elements/WhereBuilder/types.ts index 0914c5acee..1e5eee1847 100644 --- a/packages/ui/src/elements/WhereBuilder/types.ts +++ b/packages/ui/src/elements/WhereBuilder/types.ts @@ -23,6 +23,7 @@ export type ReducedField = { label: string value: Operator }[] + plainTextLabel?: string value: Value } diff --git a/test/admin/e2e/list-view/e2e.spec.ts b/test/admin/e2e/list-view/e2e.spec.ts index d9bf0c149e..881aa670b0 100644 --- a/test/admin/e2e/list-view/e2e.spec.ts +++ b/test/admin/e2e/list-view/e2e.spec.ts @@ -393,6 +393,24 @@ describe('List View', () => { await expect(page.locator(tableRowLocator)).toHaveCount(2) }) + test('should search for nested fields in field dropdown', async () => { + await page.goto(postsUrl.list) + + await openListFilters(page, {}) + + const whereBuilder = page.locator('.where-builder') + await whereBuilder.locator('.where-builder__add-first-filter').click() + const conditionField = whereBuilder.locator('.condition__field') + await conditionField.click() + await conditionField.locator('input.rs__input').fill('Tab 1 > Title') + + await expect( + conditionField.locator('.rs__menu-list').locator('div', { + hasText: exactText('Tab 1 > Title'), + }), + ).toBeVisible() + }) + test('should allow to filter in array field', async () => { await createArray() diff --git a/tsconfig.base.json b/tsconfig.base.json index c9793d25c6..02de03b79e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -31,7 +31,7 @@ } ], "paths": { - "@payload-config": ["./test/_community/config.ts"], + "@payload-config": ["./test/admin/config.ts"], "@payloadcms/admin-bar": ["./packages/admin-bar/src"], "@payloadcms/live-preview": ["./packages/live-preview/src"], "@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],