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:
Alessio Gravili
2024-08-14 18:31:58 -04:00
committed by GitHub
parent cb7fa00a6f
commit 49a2d70fbb
15 changed files with 41 additions and 18 deletions

View File

@@ -227,8 +227,13 @@ export type MappedClientComponent<TComponentClientProps extends JsonObject = Jso
type: 'client'
}
export type MappedEmptyComponent = {
type: 'empty'
}
export type MappedComponent<TComponentClientProps extends JsonObject = JsonObject> =
| MappedClientComponent<TComponentClientProps>
| MappedEmptyComponent
| MappedServerComponent<TComponentClientProps>
| undefined

View File

@@ -13,7 +13,7 @@ export const apiKeyFields = [
type: 'checkbox',
admin: {
components: {
Field: '@payloadcms/ui/shared#emptyComponent',
Field: false,
},
},
label: ({ t }) => t('authentication:enableAPIKey'),
@@ -23,7 +23,7 @@ export const apiKeyFields = [
type: 'text',
admin: {
components: {
Field: '@payloadcms/ui/shared#emptyComponent',
Field: false,
},
},
hooks: {

View File

@@ -7,7 +7,7 @@ export const emailFieldConfig: EmailField = {
type: 'email',
admin: {
components: {
Field: '@payloadcms/ui/shared#emptyComponent',
Field: false,
},
},
hooks: {

View File

@@ -7,7 +7,7 @@ export const usernameFieldConfig: TextField = {
type: 'text',
admin: {
components: {
Field: '@payloadcms/ui/shared#emptyComponent',
Field: false,
},
},
hooks: {

View File

@@ -26,7 +26,7 @@ export const verificationFields: Field[] = [
},
admin: {
components: {
Field: '@payloadcms/ui/shared#emptyComponent',
Field: false,
},
},
label: ({ t }) => t('authentication:verified'),

View File

@@ -4,6 +4,9 @@ export function parsePayloadComponent(payloadComponent: PayloadComponent): {
exportName: string
path: string
} {
if (!payloadComponent) {
return null
}
const pathAndMaybeExport =
typeof payloadComponent === 'string' ? payloadComponent : payloadComponent.path

View File

@@ -13,7 +13,7 @@ import type { default as sharp } from 'sharp'
import type { DeepRequired } from 'ts-essentials'
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 { Permissions } from '../auth/index.js'
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.
*
* 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<
TComponentServerProps 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 also NEED to actually use those generics for this to work, thus they are part of the props.

View File

@@ -17,7 +17,7 @@ const baseVersionFields: Field[] = [
type: 'select',
admin: {
components: {
Field: '@payloadcms/ui/shared#emptyComponent',
Field: false,
},
disableBulkEdit: true,
},

View File

@@ -3,5 +3,5 @@ import type { RichTextCustomElement } from '../../../types.js'
export const textAlign: RichTextCustomElement = {
name: 'alignment',
Button: '@payloadcms/richtext-slate/client#TextAlignElementButton',
Element: '@payloadcms/ui/shared#emptyComponent',
Element: false,
}

View File

@@ -21,5 +21,3 @@ export {
} from '../../utilities/groupNavItems.js'
export { hasSavePermission } from '../../utilities/hasSavePermission.js'
export { isEditing } from '../../utilities/isEditing.js'
export const emptyComponent = () => null

View File

@@ -58,6 +58,10 @@ export const RenderComponent: React.FC<{
))
}
if (mappedComponent.type === 'empty') {
return null
}
if (mappedComponent.RenderedComponent) {
return mappedComponent.RenderedComponent
}

View File

@@ -257,7 +257,9 @@ export const createClientCollectionConfig = ({
Component: createMappedComponent(
hasEditView &&
'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,
},
@@ -312,7 +314,9 @@ export const createClientCollectionConfig = ({
clientCollection.admin.components.views.list.Component = createMappedComponent(
hasListView &&
'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,
},

View File

@@ -3,7 +3,7 @@ import type { ImportMap, PayloadComponent, ResolvedComponent } from 'payload'
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 = <
TComponentServerProps extends object,
@@ -23,6 +23,7 @@ export const getComponent = <
silent?: boolean
}): ResolvedComponent<TComponentServerProps, TComponentClientProps> => {
if (!payloadComponent) {
// undefined, null or false
return {
Component: undefined,
clientProps: undefined,

View File

@@ -31,7 +31,7 @@ export function getCreateMappedComponent({
Fallback: React.FC<any>,
identifier: string,
): MappedComponent => {
if (!payloadComponent) {
if (payloadComponent === undefined || payloadComponent === null) {
if (!Fallback) {
return undefined
}
@@ -55,6 +55,12 @@ export function getCreateMappedComponent({
}
}
if (payloadComponent === false) {
return {
type: 'empty',
}
}
const resolvedComponent =
payloadComponent &&
typeof payloadComponent === 'object' &&
@@ -107,7 +113,7 @@ export function getCreateMappedComponent({
fallback,
identifier,
) => {
if (!payloadComponent && !fallback) {
if ((payloadComponent === undefined || payloadComponent === null) && !fallback) {
return undefined as any
}

View File

@@ -138,7 +138,9 @@ export const createClientGlobalConfig = ({
Component: createMappedComponent(
hasEditView &&
'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,
},