feat(ui): adds beforeInput & afterInput props for arrays, blocks, collapsible, group, radio, & relationship fields. (#9674)
The following fields did not have the ability to add beforeInput & afterInput: - Arrays - Blocks - Collapsibles - Groups - Radios - Relationships ### How? Adds the ability to define and add before and after inputs to these fields in your config. Here are examples of the above fields with beforeInputs & afterInputs defined (I.e. `#before-input` & `#after-input`): `Arrays`:  `Blocks`:  `Collapsible`:  `Groups`:  `Radios`:  `Relationships`: 
This commit is contained in:
@@ -619,6 +619,8 @@ export type DateFieldClient = {
|
||||
export type GroupField = {
|
||||
admin?: {
|
||||
components?: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
Label?: CustomComponent<GroupFieldLabelClientComponent | GroupFieldLabelServerComponent>
|
||||
} & Admin['components']
|
||||
hideGutter?: boolean
|
||||
@@ -660,6 +662,8 @@ export type CollapsibleField = {
|
||||
| {
|
||||
admin: {
|
||||
components: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
Label: CustomComponent<
|
||||
CollapsibleFieldLabelClientComponent | CollapsibleFieldLabelServerComponent
|
||||
>
|
||||
@@ -671,6 +675,8 @@ export type CollapsibleField = {
|
||||
| {
|
||||
admin?: {
|
||||
components?: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
Label?: CustomComponent<
|
||||
CollapsibleFieldLabelClientComponent | CollapsibleFieldLabelServerComponent
|
||||
>
|
||||
@@ -1029,6 +1035,8 @@ type RelationshipAdmin = {
|
||||
allowCreate?: boolean
|
||||
allowEdit?: boolean
|
||||
components?: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
Error?: CustomComponent<
|
||||
RelationshipFieldErrorClientComponent | RelationshipFieldErrorServerComponent
|
||||
>
|
||||
@@ -1124,6 +1132,8 @@ export type RichTextFieldClient<
|
||||
export type ArrayField = {
|
||||
admin?: {
|
||||
components?: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
Error?: CustomComponent<ArrayFieldErrorClientComponent | ArrayFieldErrorServerComponent>
|
||||
Label?: CustomComponent<ArrayFieldLabelClientComponent | ArrayFieldLabelServerComponent>
|
||||
RowLabel?: RowLabelComponent
|
||||
@@ -1163,6 +1173,8 @@ export type ArrayFieldClient = {
|
||||
export type RadioField = {
|
||||
admin?: {
|
||||
components?: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
Error?: CustomComponent<RadioFieldErrorClientComponent | RadioFieldErrorServerComponent>
|
||||
Label?: CustomComponent<RadioFieldLabelClientComponent | RadioFieldLabelServerComponent>
|
||||
} & Admin['components']
|
||||
@@ -1302,6 +1314,8 @@ export type ClientBlock = {
|
||||
export type BlocksField = {
|
||||
admin?: {
|
||||
components?: {
|
||||
afterInput?: CustomComponent[]
|
||||
beforeInput?: CustomComponent[]
|
||||
Error?: CustomComponent<BlocksFieldErrorClientComponent | BlocksFieldErrorServerComponent>
|
||||
} & Admin['components']
|
||||
initCollapsed?: boolean
|
||||
|
||||
@@ -110,7 +110,7 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
|
||||
)
|
||||
|
||||
const {
|
||||
customComponents: { Description, Error, Label, RowLabels } = {},
|
||||
customComponents: { AfterInput, BeforeInput, Description, Error, Label, RowLabels } = {},
|
||||
errorPaths,
|
||||
rows: rowsData = [],
|
||||
showError,
|
||||
@@ -268,6 +268,7 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
|
||||
/>
|
||||
</header>
|
||||
<NullifyLocaleField fieldValue={value} localized={localized} path={path} />
|
||||
{BeforeInput}
|
||||
{(rowsData?.length > 0 || (!valid && (showRequired || showMinRows))) && (
|
||||
<DraggableSortable
|
||||
className={`${baseClass}__draggable-rows`}
|
||||
@@ -349,6 +350,7 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
|
||||
{t('fields:addLabel', { label: getTranslation(labels.singular, i18n) })}
|
||||
</Button>
|
||||
)}
|
||||
{AfterInput}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
|
||||
)
|
||||
|
||||
const {
|
||||
customComponents: { Description, Error, Label } = {},
|
||||
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
|
||||
errorPaths,
|
||||
rows = [],
|
||||
showError,
|
||||
@@ -250,6 +250,7 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
|
||||
Fallback={<FieldDescription description={description} path={path} />}
|
||||
/>
|
||||
</header>
|
||||
{BeforeInput}
|
||||
<NullifyLocaleField fieldValue={value} localized={localized} path={path} />
|
||||
{(rows.length > 0 || (!valid && (showRequired || showMinRows))) && (
|
||||
<DraggableSortable
|
||||
@@ -350,6 +351,7 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
{AfterInput}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
|
||||
const [errorCount, setErrorCount] = useState(0)
|
||||
const fieldHasErrors = errorCount > 0
|
||||
|
||||
const { customComponents: { Description, Label } = {} } = useField({
|
||||
const { customComponents: { AfterInput, BeforeInput, Description, Label } = {} } = useField({
|
||||
path,
|
||||
})
|
||||
|
||||
@@ -120,6 +120,7 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
|
||||
id={`field-${fieldPreferencesKey}`}
|
||||
style={styles}
|
||||
>
|
||||
{BeforeInput}
|
||||
<CollapsibleElement
|
||||
className={`${baseClass}__collapsible`}
|
||||
collapsibleStyle={fieldHasErrors ? 'error' : 'default'}
|
||||
@@ -142,6 +143,7 @@ const CollapsibleFieldComponent: CollapsibleFieldClientComponent = (props) => {
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</CollapsibleElement>
|
||||
{AfterInput}
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Description}
|
||||
Fallback={<FieldDescription description={description} path={path} />}
|
||||
|
||||
@@ -40,7 +40,8 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
|
||||
const isWithinGroup = useGroup()
|
||||
const isWithinRow = useRow()
|
||||
const isWithinTab = useTabs()
|
||||
const { customComponents: { Description, Label } = {}, errorPaths } = useField({ path })
|
||||
const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, errorPaths } =
|
||||
useField({ path })
|
||||
const submitted = useFormSubmitted()
|
||||
const errorCount = errorPaths.length
|
||||
const fieldHasErrors = submitted && errorCount > 0
|
||||
@@ -94,6 +95,7 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
|
||||
)}
|
||||
{fieldHasErrors && <ErrorPill count={errorCount} i18n={i18n} withMessage />}
|
||||
</div>
|
||||
{BeforeInput}
|
||||
<RenderFields
|
||||
fields={fields}
|
||||
margins="small"
|
||||
@@ -105,6 +107,7 @@ export const GroupFieldComponent: GroupFieldClientComponent = (props) => {
|
||||
/>
|
||||
</div>
|
||||
</GroupProvider>
|
||||
{AfterInput}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -149,6 +149,7 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
||||
CustomComponent={Error}
|
||||
Fallback={<FieldError path={path} showError={showError} />}
|
||||
/>
|
||||
{BeforeInput}
|
||||
{hasMany ? (
|
||||
<ReactSelect
|
||||
className={`field-${path.replace(/\./g, '__')}`}
|
||||
@@ -177,7 +178,6 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
||||
/>
|
||||
) : (
|
||||
<div>
|
||||
{BeforeInput}
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
@@ -194,9 +194,9 @@ const NumberFieldComponent: NumberFieldClientComponent = (props) => {
|
||||
type="number"
|
||||
value={typeof value === 'number' ? value : ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
)}
|
||||
{AfterInput}
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Description}
|
||||
Fallback={<FieldDescription description={description} path={path} />}
|
||||
|
||||
@@ -52,7 +52,7 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
|
||||
)
|
||||
|
||||
const {
|
||||
customComponents: { Description, Error, Label } = {},
|
||||
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
|
||||
setValue,
|
||||
showError,
|
||||
value: valueFromContext,
|
||||
@@ -90,6 +90,7 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
|
||||
}
|
||||
/>
|
||||
<div className={`${fieldBaseClass}__wrap`}>
|
||||
{BeforeInput}
|
||||
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>
|
||||
{options.map((option) => {
|
||||
let optionValue = ''
|
||||
@@ -127,6 +128,7 @@ const RadioGroupFieldComponent: RadioFieldClientComponent = (props) => {
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
{AfterInput}
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Description}
|
||||
Fallback={<FieldDescription description={description} path={path} />}
|
||||
|
||||
@@ -100,7 +100,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
)
|
||||
|
||||
const {
|
||||
customComponents: { Description, Error, Label } = {},
|
||||
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
|
||||
filterOptions,
|
||||
initialValue,
|
||||
setValue,
|
||||
@@ -603,6 +603,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
CustomComponent={Error}
|
||||
Fallback={<FieldError path={path} showError={showError} />}
|
||||
/>
|
||||
{BeforeInput}
|
||||
{!errorLoading && (
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<ReactSelect
|
||||
@@ -707,6 +708,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
|
||||
</div>
|
||||
)}
|
||||
{errorLoading && <div className={`${baseClass}__error-loading`}>{errorLoading}</div>}
|
||||
{AfterInput}
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Description}
|
||||
Fallback={<FieldDescription description={description} path={path} />}
|
||||
|
||||
@@ -73,5 +73,119 @@ export const CustomFields: CollectionConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'relationshipFieldWithBeforeAfterInputs',
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
components: {
|
||||
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
|
||||
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
|
||||
},
|
||||
},
|
||||
relationTo: 'posts',
|
||||
},
|
||||
{
|
||||
name: 'arrayFieldWithBeforeAfterInputs',
|
||||
type: 'array',
|
||||
admin: {
|
||||
components: {
|
||||
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
|
||||
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'someTextField',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'blocksFieldWithBeforeAfterInputs',
|
||||
type: 'blocks',
|
||||
admin: {
|
||||
components: {
|
||||
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
|
||||
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
|
||||
},
|
||||
},
|
||||
blocks: [
|
||||
{
|
||||
slug: 'blockFields',
|
||||
fields: [
|
||||
{
|
||||
name: 'textField',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Collapsible Field With Before & After Inputs',
|
||||
type: 'collapsible',
|
||||
admin: {
|
||||
components: {
|
||||
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
|
||||
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
|
||||
},
|
||||
description: 'This is a collapsible field.',
|
||||
initCollapsed: false,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'groupFieldWithBeforeAfterInputs',
|
||||
type: 'group',
|
||||
admin: {
|
||||
components: {
|
||||
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
|
||||
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'textOne',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'textTwo',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'radioFieldWithBeforeAfterInputs',
|
||||
label: {
|
||||
en: 'Radio en',
|
||||
es: 'Radio es',
|
||||
},
|
||||
type: 'radio',
|
||||
admin: {
|
||||
components: {
|
||||
afterInput: ['/collections/CustomFields/AfterInput.js#AfterInput'],
|
||||
beforeInput: ['/collections/CustomFields/BeforeInput.js#BeforeInput'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: { en: 'Value One', es: 'Value Uno' },
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'Value Two',
|
||||
value: 'two',
|
||||
},
|
||||
{
|
||||
label: 'Value Three',
|
||||
value: 'three',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user