feat(ui): make select and relationship field placeholder configurable (#12253)
### What? Allows to overwrite the default placeholder text of select and relationship fields. ### Why? The default placeholder text is generic. In some scenarios a custom placeholder can guide the user better. ### How? Adds a new property `admin.placeholder` to relationship and select field which allows to define an alternative text or translation function for the placeholder. The placeholder is used in the form fields as well as in the filter options.   --------- Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
41
test/admin/collections/Placeholder.ts
Normal file
41
test/admin/collections/Placeholder.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { placeholderCollectionSlug } from '../slugs.js'
|
||||
|
||||
export const Placeholder: CollectionConfig = {
|
||||
slug: placeholderCollectionSlug,
|
||||
fields: [
|
||||
{
|
||||
name: 'defaultSelect',
|
||||
type: 'select',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'placeholderSelect',
|
||||
type: 'select',
|
||||
options: [{ label: 'Option 1', value: 'option1' }],
|
||||
admin: {
|
||||
placeholder: 'Custom placeholder',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'defaultRelationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'posts',
|
||||
},
|
||||
{
|
||||
name: 'placeholderRelationship',
|
||||
type: 'relationship',
|
||||
relationTo: 'posts',
|
||||
admin: {
|
||||
placeholder: 'Custom placeholder',
|
||||
},
|
||||
},
|
||||
],
|
||||
versions: true,
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { Array } from './collections/Array.js'
|
||||
import { BaseListFilter } from './collections/BaseListFilter.js'
|
||||
@@ -19,6 +18,7 @@ import { CollectionHidden } from './collections/Hidden.js'
|
||||
import { ListDrawer } from './collections/ListDrawer.js'
|
||||
import { CollectionNoApiView } from './collections/NoApiView.js'
|
||||
import { CollectionNotInView } from './collections/NotInView.js'
|
||||
import { Placeholder } from './collections/Placeholder.js'
|
||||
import { Posts } from './collections/Posts.js'
|
||||
import { UploadCollection } from './collections/Upload.js'
|
||||
import { UploadTwoCollection } from './collections/UploadTwo.js'
|
||||
@@ -43,7 +43,8 @@ import {
|
||||
protectedCustomNestedViewPath,
|
||||
publicCustomViewPath,
|
||||
} from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
export default buildConfigWithDefaults({
|
||||
admin: {
|
||||
importMap: {
|
||||
@@ -165,6 +166,7 @@ export default buildConfigWithDefaults({
|
||||
BaseListFilter,
|
||||
with300Documents,
|
||||
ListDrawer,
|
||||
Placeholder,
|
||||
],
|
||||
globals: [
|
||||
GlobalHidden,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import type { User as PayloadUser } from 'payload'
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { mapAsync } from 'payload'
|
||||
import * as qs from 'qs-esm'
|
||||
|
||||
import type { Config, Geo, Post, User } from '../../payload-types.js'
|
||||
import type { Config, Geo, Post } from '../../payload-types.js'
|
||||
|
||||
import {
|
||||
ensureCompilationIsDone,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
customViews1CollectionSlug,
|
||||
geoCollectionSlug,
|
||||
listDrawerSlug,
|
||||
placeholderCollectionSlug,
|
||||
postsCollectionSlug,
|
||||
with300DocumentsSlug,
|
||||
} from '../../slugs.js'
|
||||
@@ -64,6 +64,7 @@ describe('List View', () => {
|
||||
let customViewsUrl: AdminUrlUtil
|
||||
let with300DocumentsUrl: AdminUrlUtil
|
||||
let withListViewUrl: AdminUrlUtil
|
||||
let placeholderUrl: AdminUrlUtil
|
||||
let user: any
|
||||
|
||||
let serverURL: string
|
||||
@@ -87,7 +88,7 @@ describe('List View', () => {
|
||||
baseListFiltersUrl = new AdminUrlUtil(serverURL, 'base-list-filters')
|
||||
customViewsUrl = new AdminUrlUtil(serverURL, customViews1CollectionSlug)
|
||||
withListViewUrl = new AdminUrlUtil(serverURL, listDrawerSlug)
|
||||
|
||||
placeholderUrl = new AdminUrlUtil(serverURL, placeholderCollectionSlug)
|
||||
const context = await browser.newContext()
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
@@ -1408,6 +1409,66 @@ describe('List View', () => {
|
||||
).toHaveText('Title')
|
||||
})
|
||||
})
|
||||
|
||||
describe('placeholder', () => {
|
||||
test('should display placeholder in filter options', async () => {
|
||||
await page.goto(
|
||||
`${placeholderUrl.list}${qs.stringify(
|
||||
{
|
||||
where: {
|
||||
or: [
|
||||
{
|
||||
and: [
|
||||
{
|
||||
defaultSelect: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
placeholderSelect: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
defaultRelationship: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
placeholderRelationship: {
|
||||
equals: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)}`,
|
||||
)
|
||||
|
||||
const conditionValueSelects = page.locator('#list-controls-where .condition__value')
|
||||
await expect(conditionValueSelects.nth(0)).toHaveText('Select a value')
|
||||
await expect(conditionValueSelects.nth(1)).toHaveText('Custom placeholder')
|
||||
await expect(conditionValueSelects.nth(2)).toHaveText('Select a value')
|
||||
await expect(conditionValueSelects.nth(3)).toHaveText('Custom placeholder')
|
||||
})
|
||||
})
|
||||
test('should display placeholder in edit view', async () => {
|
||||
await page.goto(placeholderUrl.create)
|
||||
|
||||
await expect(page.locator('#field-defaultSelect .rs__placeholder')).toHaveText('Select a value')
|
||||
await expect(page.locator('#field-placeholderSelect .rs__placeholder')).toHaveText(
|
||||
'Custom placeholder',
|
||||
)
|
||||
await expect(page.locator('#field-defaultRelationship .rs__placeholder')).toHaveText(
|
||||
'Select a value',
|
||||
)
|
||||
await expect(page.locator('#field-placeholderRelationship .rs__placeholder')).toHaveText(
|
||||
'Custom placeholder',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
async function createPost(overrides?: Partial<Post>): Promise<Post> {
|
||||
|
||||
@@ -14,6 +14,7 @@ export const noApiViewCollectionSlug = 'collection-no-api-view'
|
||||
export const disableDuplicateSlug = 'disable-duplicate'
|
||||
export const disableCopyToLocale = 'disable-copy-to-locale'
|
||||
export const uploadCollectionSlug = 'uploads'
|
||||
export const placeholderCollectionSlug = 'placeholder'
|
||||
|
||||
export const uploadTwoCollectionSlug = 'uploads-two'
|
||||
export const customFieldsSlug = 'custom-fields'
|
||||
|
||||
Reference in New Issue
Block a user