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`:
![Screenshot 2024-12-02 at 1 22
15 PM](https://github.com/user-attachments/assets/8a2d5351-acab-4b28-b9bd-b19e1942da7e)


`Blocks`:
![Screenshot 2024-12-02 at 1 35
53 PM](https://github.com/user-attachments/assets/e9b5f22b-2671-4efb-8ee4-b4de3a1addc5)


`Collapsible`:
![Screenshot 2024-12-02 at 1 46
34 PM](https://github.com/user-attachments/assets/38f7a016-8a79-4ece-90d2-0b1761db19cd)


`Groups`:
![Screenshot 2024-12-02 at 1 52
34 PM](https://github.com/user-attachments/assets/dc6a123b-ee01-4021-bbbf-ac1b8c086072)


`Radios`:
![Screenshot 2024-12-02 at 1 59
35 PM](https://github.com/user-attachments/assets/4ddd16a1-2ad2-44f5-a7e9-86aa6e4e947b)


`Relationships`:
![Screenshot 2024-12-02 at 1 21
55 PM](https://github.com/user-attachments/assets/b4679baf-6157-41c9-8485-ca570be979d2)
This commit is contained in:
Patrik
2024-12-02 14:37:16 -05:00
committed by GitHub
parent 631edd4c17
commit 58b7415385
9 changed files with 149 additions and 8 deletions

View File

@@ -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

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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} />}

View File

@@ -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>
)
}

View File

@@ -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} />}

View File

@@ -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} />}

View File

@@ -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} />}

View File

@@ -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',
},
],
},
],
}