fix(ui): unable to search for nested fields in WhereBuilder field selection (#11986)
### What?
Extract text from the React node label in WhereBuilder
### Why?
If you have a nested field in filter options, the label would show
correctly, but the search will not work
### How
By adding an `extractTextFromReactNode` function that gets text out of
React.node label
### Code setup:
```
{
type: "collapsible",
label: "Meta",
fields: [
{
name: 'media',
type: 'relationship',
relationTo: 'media',
label: 'Ferrari',
filterOptions: () => {
return {
id: { in: ['67efdbc872ca925bc2868933'] },
}
}
},
{
name: 'media2',
type: 'relationship',
relationTo: 'media',
label: 'Williams',
filterOptions: () => {
return {
id: { in: ['67efdbc272ca925bc286891c'] },
}
}
},
],
},
```
### Before:
https://github.com/user-attachments/assets/25d4b3a2-6ac0-476b-973e-575238e916c4
### After:
https://github.com/user-attachments/assets/92346a6c-b2d1-4e08-b1e4-9ac1484f9ef3
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
This commit is contained in:
@@ -141,6 +141,11 @@ export const Condition: React.FC<Props> = (props) => {
|
||||
<div className={`${baseClass}__field`}>
|
||||
<ReactSelect
|
||||
disabled={disabled}
|
||||
filterOption={(option, inputValue) =>
|
||||
((option?.data?.plainTextLabel as string) || option.label)
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())
|
||||
}
|
||||
isClearable={false}
|
||||
onChange={handleFieldChange}
|
||||
options={reducedFields.filter((field) => !field.field.admin.disableListFilter)}
|
||||
|
||||
@@ -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, '')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export type ReducedField = {
|
||||
label: string
|
||||
value: Operator
|
||||
}[]
|
||||
plainTextLabel?: string
|
||||
value: Value
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
Reference in New Issue
Block a user