diff --git a/demo/collections/AllFields.ts b/demo/collections/AllFields.ts index 92a1f7348d..81338fbf31 100644 --- a/demo/collections/AllFields.ts +++ b/demo/collections/AllFields.ts @@ -40,6 +40,25 @@ const AllFields: PayloadCollectionConfig = { read: ({ req: { user } }) => Boolean(user), }, }, + { + name: 'descriptionText', + type: 'text', + label: 'Text with text description', + defaultValue: 'Default Value', + admin: { + description: 'This text describes the field', + }, + }, + { + name: 'descriptionFunction', + type: 'text', + label: 'Text with function description', + defaultValue: 'Default Value', + maxLength: 20, + admin: { + description: ({ value }) => (typeof value === 'string' ? `${20 - value.length} characters left` : ''), + }, + }, { name: 'image', type: 'upload', diff --git a/demo/collections/CustomComponents/index.ts b/demo/collections/CustomComponents/index.ts index d4fb667354..c784275b29 100644 --- a/demo/collections/CustomComponents/index.ts +++ b/demo/collections/CustomComponents/index.ts @@ -7,6 +7,7 @@ import GroupField from './components/fields/Group/Field'; import NestedGroupField from './components/fields/NestedGroupCustomField/Field'; import NestedText1Field from './components/fields/NestedText1/Field'; import ListView from './components/views/List'; +import CustomDescriptionComponent from '../../customComponents/Description'; const CustomComponents: PayloadCollectionConfig = { slug: 'custom-components', @@ -38,6 +39,17 @@ const CustomComponents: PayloadCollectionConfig = { }, }, }, + { + name: 'descriptionComponent', + type: 'text', + label: 'Text with description component', + defaultValue: 'Default Value', + admin: { + components: { + Description: CustomDescriptionComponent, + }, + }, + }, { name: 'array', label: 'Array', diff --git a/demo/customComponents/Description/index.tsx b/demo/customComponents/Description/index.tsx new file mode 100644 index 0000000000..581c56391a --- /dev/null +++ b/demo/customComponents/Description/index.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +const CustomDescriptionComponent: React.FC = ({ value }) => ( +
+ character count: + {' '} + { value.length } +
+); + +export default CustomDescriptionComponent; diff --git a/src/admin/components/forms/Label/index.scss b/src/admin/components/forms/Label/index.scss index 98c4ceeb69..c6b9c3b1fc 100644 --- a/src/admin/components/forms/Label/index.scss +++ b/src/admin/components/forms/Label/index.scss @@ -3,7 +3,7 @@ label.field-label { display: flex; padding-bottom: base(.25); - color: $color-gray; + color: $color-dark-gray; .required { color: $color-red; diff --git a/src/admin/components/forms/RenderFieldDescription/index.scss b/src/admin/components/forms/RenderFieldDescription/index.scss new file mode 100644 index 0000000000..2a6f97e10a --- /dev/null +++ b/src/admin/components/forms/RenderFieldDescription/index.scss @@ -0,0 +1,8 @@ +@import '../../../scss/styles.scss'; + +.field-description { + display: flex; + padding-top: base(.25); + padding-bottom: base(.25); + color: $color-gray; +} diff --git a/src/admin/components/forms/RenderFieldDescription/index.tsx b/src/admin/components/forms/RenderFieldDescription/index.tsx new file mode 100644 index 0000000000..255f125601 --- /dev/null +++ b/src/admin/components/forms/RenderFieldDescription/index.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Props } from './types'; +import './index.scss'; + +const RenderFieldDescription: React.FC = (props) => { + const { + description, + value, + CustomComponent, + } = props; + + if (CustomComponent) { + return ( +
+ +
+ ); + } + + if (description) { + return ( +
+ {typeof description === 'function' ? description({ value }) : description} +
+ ); + } + + return null; +}; + +export default RenderFieldDescription; diff --git a/src/admin/components/forms/RenderFieldDescription/types.ts b/src/admin/components/forms/RenderFieldDescription/types.ts new file mode 100644 index 0000000000..c784b8419b --- /dev/null +++ b/src/admin/components/forms/RenderFieldDescription/types.ts @@ -0,0 +1,5 @@ +export type Props = { + description?: string | ((value) => string); + CustomComponent?: React.ComponentType<{ value: unknown }>; + value: unknown; +} diff --git a/src/admin/components/forms/field-types/Text/index.tsx b/src/admin/components/forms/field-types/Text/index.tsx index 792e95803a..edbcfbb8ec 100644 --- a/src/admin/components/forms/field-types/Text/index.tsx +++ b/src/admin/components/forms/field-types/Text/index.tsx @@ -5,6 +5,7 @@ import Label from '../../Label'; import Error from '../../Error'; import { text } from '../../../../../fields/validations'; import { Props } from './types'; +import RenderFieldDescription from '../../RenderFieldDescription'; import './index.scss'; @@ -21,6 +22,10 @@ const Text: React.FC = (props) => { style, width, condition, + description, + components: { + Description, + } = {}, } = {}, } = props; @@ -73,6 +78,11 @@ const Text: React.FC = (props) => { id={path} name={path} /> + ); }; diff --git a/src/collections/config/types.ts b/src/collections/config/types.ts index 14e718d91f..1002251956 100644 --- a/src/collections/config/types.ts +++ b/src/collections/config/types.ts @@ -99,7 +99,7 @@ export type PayloadCollectionConfig = { admin?: { useAsTitle?: string; defaultColumns?: string[]; - disableDuplicate?: boolean + disableDuplicate?: boolean; components?: { views?: { Edit?: React.ComponentType diff --git a/src/fields/config/schema.ts b/src/fields/config/schema.ts index e3a958edde..b957206064 100644 --- a/src/fields/config/schema.ts +++ b/src/fields/config/schema.ts @@ -6,6 +6,10 @@ const component = joi.alternatives().try( ); export const baseAdminFields = joi.object().keys({ + description: joi.alternatives().try( + joi.string(), + joi.func(), + ), position: joi.string().valid('sidebar'), width: joi.string(), style: joi.object().unknown(), @@ -17,6 +21,7 @@ export const baseAdminFields = joi.object().keys({ Cell: component, Field: component, Filter: component, + Description: component, }).default({}), }); diff --git a/src/fields/config/types.ts b/src/fields/config/types.ts index 7d615d1ac9..2131e03d26 100644 --- a/src/fields/config/types.ts +++ b/src/fields/config/types.ts @@ -22,7 +22,7 @@ export type FieldAccess = (args: { siblingData: Record }) => Promise | boolean; -export type Condition = (data: Record, siblingData: Record) => boolean +export type Condition = (data: Record, siblingData: Record) => boolean; type Admin = { position?: string; @@ -31,7 +31,13 @@ type Admin = { readOnly?: boolean; disabled?: boolean; condition?: Condition; - components?: { [key: string]: React.ComponentType }; + description?: string | ((data: Record) => string); + components?: { + Filter?: React.ComponentType; + Cell?: React.ComponentType; + Field?: React.ComponentType; + Description?: React.ComponentType<{value: unknown}> + } hidden?: boolean } @@ -214,11 +220,11 @@ export type RadioField = FieldBase & { } export type Block = { - slug: string, - labels?: Labels - fields: Field[], - imageURL?: string - imageAltText?: string + slug: string; + labels?: Labels; + fields: Field[]; + imageURL?: string; + imageAltText?: string; } export type BlockField = FieldBase & {