feat: allow passing false as PayloadComponent which signals that the component should not be rendered (#7682)
If it's undefined/null => Fallback Component may be rendered If it's false => No component should be rendered - as if an empty component was passed in This ensures that the user does not have to install `@payloadcms/ui` anymore, which previously exported an empty component to be used in component paths
This commit is contained in:
@@ -227,8 +227,13 @@ export type MappedClientComponent<TComponentClientProps extends JsonObject = Jso
|
|||||||
type: 'client'
|
type: 'client'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MappedEmptyComponent = {
|
||||||
|
type: 'empty'
|
||||||
|
}
|
||||||
|
|
||||||
export type MappedComponent<TComponentClientProps extends JsonObject = JsonObject> =
|
export type MappedComponent<TComponentClientProps extends JsonObject = JsonObject> =
|
||||||
| MappedClientComponent<TComponentClientProps>
|
| MappedClientComponent<TComponentClientProps>
|
||||||
|
| MappedEmptyComponent
|
||||||
| MappedServerComponent<TComponentClientProps>
|
| MappedServerComponent<TComponentClientProps>
|
||||||
| undefined
|
| undefined
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const apiKeyFields = [
|
|||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
Field: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
label: ({ t }) => t('authentication:enableAPIKey'),
|
label: ({ t }) => t('authentication:enableAPIKey'),
|
||||||
@@ -23,7 +23,7 @@ export const apiKeyFields = [
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
Field: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const emailFieldConfig: EmailField = {
|
|||||||
type: 'email',
|
type: 'email',
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
Field: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const usernameFieldConfig: TextField = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
Field: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const verificationFields: Field[] = [
|
|||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
Field: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
label: ({ t }) => t('authentication:verified'),
|
label: ({ t }) => t('authentication:verified'),
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ export function parsePayloadComponent(payloadComponent: PayloadComponent): {
|
|||||||
exportName: string
|
exportName: string
|
||||||
path: string
|
path: string
|
||||||
} {
|
} {
|
||||||
|
if (!payloadComponent) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
const pathAndMaybeExport =
|
const pathAndMaybeExport =
|
||||||
typeof payloadComponent === 'string' ? payloadComponent : payloadComponent.path
|
typeof payloadComponent === 'string' ? payloadComponent : payloadComponent.path
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type { default as sharp } from 'sharp'
|
|||||||
import type { DeepRequired } from 'ts-essentials'
|
import type { DeepRequired } from 'ts-essentials'
|
||||||
|
|
||||||
import type { RichTextAdapterProvider } from '../admin/RichText.js'
|
import type { RichTextAdapterProvider } from '../admin/RichText.js'
|
||||||
import type { DocumentTabConfig, MappedComponent, RichTextAdapter } from '../admin/types.js'
|
import type { DocumentTabConfig, RichTextAdapter } from '../admin/types.js'
|
||||||
import type { AdminViewConfig, ServerSideEditViewProps } from '../admin/views/types.js'
|
import type { AdminViewConfig, ServerSideEditViewProps } from '../admin/views/types.js'
|
||||||
import type { Permissions } from '../auth/index.js'
|
import type { Permissions } from '../auth/index.js'
|
||||||
import type {
|
import type {
|
||||||
@@ -38,12 +38,12 @@ import type { PayloadLogger } from '../utilities/logger.js'
|
|||||||
/**
|
/**
|
||||||
* The string path pointing to the React component. If one of the generics is `never`, you effectively mark it as a server-only or client-only component.
|
* The string path pointing to the React component. If one of the generics is `never`, you effectively mark it as a server-only or client-only component.
|
||||||
*
|
*
|
||||||
* If the path is an empty string, it will be treated as () => null
|
* If it is `false` an empty component will be rendered.
|
||||||
*/
|
*/
|
||||||
export type PayloadComponent<
|
export type PayloadComponent<
|
||||||
TComponentServerProps extends never | object = Record<string, any>,
|
TComponentServerProps extends never | object = Record<string, any>,
|
||||||
TComponentClientProps extends never | object = Record<string, any>,
|
TComponentClientProps extends never | object = Record<string, any>,
|
||||||
> = RawPayloadComponent<TComponentServerProps, TComponentClientProps> | string
|
> = RawPayloadComponent<TComponentServerProps, TComponentClientProps> | false | string
|
||||||
|
|
||||||
// We need the actual object as its own type, otherwise the infers for the PayloadClientReactComponent / PayloadServerReactComponent will not work due to the string union.
|
// We need the actual object as its own type, otherwise the infers for the PayloadClientReactComponent / PayloadServerReactComponent will not work due to the string union.
|
||||||
// We also NEED to actually use those generics for this to work, thus they are part of the props.
|
// We also NEED to actually use those generics for this to work, thus they are part of the props.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const baseVersionFields: Field[] = [
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
Field: false,
|
||||||
},
|
},
|
||||||
disableBulkEdit: true,
|
disableBulkEdit: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ import type { RichTextCustomElement } from '../../../types.js'
|
|||||||
export const textAlign: RichTextCustomElement = {
|
export const textAlign: RichTextCustomElement = {
|
||||||
name: 'alignment',
|
name: 'alignment',
|
||||||
Button: '@payloadcms/richtext-slate/client#TextAlignElementButton',
|
Button: '@payloadcms/richtext-slate/client#TextAlignElementButton',
|
||||||
Element: '@payloadcms/ui/shared#emptyComponent',
|
Element: false,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,3 @@ export {
|
|||||||
} from '../../utilities/groupNavItems.js'
|
} from '../../utilities/groupNavItems.js'
|
||||||
export { hasSavePermission } from '../../utilities/hasSavePermission.js'
|
export { hasSavePermission } from '../../utilities/hasSavePermission.js'
|
||||||
export { isEditing } from '../../utilities/isEditing.js'
|
export { isEditing } from '../../utilities/isEditing.js'
|
||||||
|
|
||||||
export const emptyComponent = () => null
|
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ export const RenderComponent: React.FC<{
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mappedComponent.type === 'empty') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
if (mappedComponent.RenderedComponent) {
|
if (mappedComponent.RenderedComponent) {
|
||||||
return mappedComponent.RenderedComponent
|
return mappedComponent.RenderedComponent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,7 +257,9 @@ export const createClientCollectionConfig = ({
|
|||||||
Component: createMappedComponent(
|
Component: createMappedComponent(
|
||||||
hasEditView &&
|
hasEditView &&
|
||||||
'Component' in collection.admin.components.views.edit.default &&
|
'Component' in collection.admin.components.views.edit.default &&
|
||||||
collection.admin.components.views.edit.default.Component,
|
collection.admin.components.views.edit.default.Component
|
||||||
|
? collection.admin.components.views.edit.default.Component
|
||||||
|
: null,
|
||||||
{
|
{
|
||||||
collectionSlug: collection.slug,
|
collectionSlug: collection.slug,
|
||||||
},
|
},
|
||||||
@@ -312,7 +314,9 @@ export const createClientCollectionConfig = ({
|
|||||||
clientCollection.admin.components.views.list.Component = createMappedComponent(
|
clientCollection.admin.components.views.list.Component = createMappedComponent(
|
||||||
hasListView &&
|
hasListView &&
|
||||||
'Component' in collection.admin.components.views.list &&
|
'Component' in collection.admin.components.views.list &&
|
||||||
collection.admin.components.views.list.Component,
|
collection.admin.components.views.list.Component
|
||||||
|
? collection.admin.components.views.list.Component
|
||||||
|
: null,
|
||||||
{
|
{
|
||||||
collectionSlug: collection.slug,
|
collectionSlug: collection.slug,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { ImportMap, PayloadComponent, ResolvedComponent } from 'payload'
|
|||||||
import { parsePayloadComponent } from 'payload/shared'
|
import { parsePayloadComponent } from 'payload/shared'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets th resolved React component from `PayloadComponent` from the importMap
|
* Gets the resolved React component from `PayloadComponent` from the importMap
|
||||||
*/
|
*/
|
||||||
export const getComponent = <
|
export const getComponent = <
|
||||||
TComponentServerProps extends object,
|
TComponentServerProps extends object,
|
||||||
@@ -23,6 +23,7 @@ export const getComponent = <
|
|||||||
silent?: boolean
|
silent?: boolean
|
||||||
}): ResolvedComponent<TComponentServerProps, TComponentClientProps> => {
|
}): ResolvedComponent<TComponentServerProps, TComponentClientProps> => {
|
||||||
if (!payloadComponent) {
|
if (!payloadComponent) {
|
||||||
|
// undefined, null or false
|
||||||
return {
|
return {
|
||||||
Component: undefined,
|
Component: undefined,
|
||||||
clientProps: undefined,
|
clientProps: undefined,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function getCreateMappedComponent({
|
|||||||
Fallback: React.FC<any>,
|
Fallback: React.FC<any>,
|
||||||
identifier: string,
|
identifier: string,
|
||||||
): MappedComponent => {
|
): MappedComponent => {
|
||||||
if (!payloadComponent) {
|
if (payloadComponent === undefined || payloadComponent === null) {
|
||||||
if (!Fallback) {
|
if (!Fallback) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
@@ -55,6 +55,12 @@ export function getCreateMappedComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payloadComponent === false) {
|
||||||
|
return {
|
||||||
|
type: 'empty',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const resolvedComponent =
|
const resolvedComponent =
|
||||||
payloadComponent &&
|
payloadComponent &&
|
||||||
typeof payloadComponent === 'object' &&
|
typeof payloadComponent === 'object' &&
|
||||||
@@ -107,7 +113,7 @@ export function getCreateMappedComponent({
|
|||||||
fallback,
|
fallback,
|
||||||
identifier,
|
identifier,
|
||||||
) => {
|
) => {
|
||||||
if (!payloadComponent && !fallback) {
|
if ((payloadComponent === undefined || payloadComponent === null) && !fallback) {
|
||||||
return undefined as any
|
return undefined as any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,9 @@ export const createClientGlobalConfig = ({
|
|||||||
Component: createMappedComponent(
|
Component: createMappedComponent(
|
||||||
hasEditView &&
|
hasEditView &&
|
||||||
'Component' in global.admin.components.views.edit.default &&
|
'Component' in global.admin.components.views.edit.default &&
|
||||||
global.admin.components.views.edit.default.Component,
|
global.admin.components.views.edit.default.Component
|
||||||
|
? global.admin.components.views.edit.default.Component
|
||||||
|
: null,
|
||||||
{
|
{
|
||||||
globalSlug: global.slug,
|
globalSlug: global.slug,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user