Skip to content

Commit 673332a

Browse files
committed
feat(pf4): use FormFieldGroup in FieldArray
1 parent 834aaac commit 673332a

File tree

4 files changed

+191
-119
lines changed

4 files changed

+191
-119
lines changed

packages/pf4-component-mapper/src/field-array/field-array.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { ReactNode } from "react";
22
import { FieldArrayField } from "@data-driven-forms/react-form-renderer";
33

4+
interface FieldArrayButtonLabels {
5+
add?: ReactNode;
6+
remove?: ReactNode;
7+
removeAll?: ReactNode;
8+
}
9+
410
export interface FieldArrayProps {
511
label?: ReactNode;
612
description?: ReactNode;
@@ -9,6 +15,8 @@ export interface FieldArrayProps {
915
minItems?: number;
1016
maxItems?: number;
1117
noItemsMessage?: ReactNode;
18+
name: string;
19+
buttonLabels?: FieldArrayButtonLabels;
1220
}
1321

1422
declare const FieldArray: React.ComponentType<FieldArrayProps>;

packages/pf4-component-mapper/src/field-array/field-array.js

Lines changed: 75 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,47 @@
1-
import React, { Fragment, memo } from 'react';
1+
import React, { memo } from 'react';
22
import isEqual from 'lodash/isEqual';
33
import PropTypes from 'prop-types';
44
import { useFormApi, FieldArray } from '@data-driven-forms/react-form-renderer';
55

6-
import { Bullseye, FormHelperText, Grid, GridItem } from '@patternfly/react-core';
6+
import { Bullseye, Button, Flex, FlexItem, FormFieldGroup, FormFieldGroupHeader, FormHelperText, Grid, GridItem } from '@patternfly/react-core';
77

8-
import { AddCircleOIcon, CloseIcon } from '@patternfly/react-icons';
8+
import { TrashIcon } from '@patternfly/react-icons';
99

1010
import './final-form-array.css';
1111
import { useFieldApi } from '@data-driven-forms/react-form-renderer';
1212

13+
const Spacer = () => <span className="ddf-final-form-spacer" />;
14+
1315
const ArrayItem = memo(
14-
({ fields, fieldIndex, name, remove, length, minItems }) => {
16+
({ fields, fieldIndex, name, remove, length, minItems, buttonLabels, isLast }) => {
1517
const { renderForm } = useFormApi();
1618

17-
const widths = {
18-
label: fields[0].label ? 5 : 0,
19-
field: fields[0].label ? 7 : 12,
20-
};
21-
2219
const editedFields = fields.map((field, index) => {
2320
const computedName = field.name ? `${name}.${field.name}` : name;
24-
return { ...field, name: computedName, key: `${name}-${index}`, hideLabel: true };
21+
return { ...field, name: computedName, key: `${name}-${index}` };
2522
});
2623

24+
const isRemoveDisabled = length <= minItems;
25+
2726
return (
2827
<React.Fragment>
29-
<Grid>
30-
<GridItem sm={11}>
31-
<hr className="ddf-final-form-hr" />
32-
</GridItem>
33-
</Grid>
34-
<Grid>
35-
<GridItem sm={11}>
36-
{editedFields.map((field, index) => (
37-
<Grid key={`${field.label}-${index}`} className="ddf-final-form-array-grid">
38-
{widths.label > 0 && (
39-
<GridItem sm={widths.label} key={`${field.label}-${index}`}>
40-
<label htmlFor={field.name}>
41-
{field.label}
42-
{field.isRequired && <span className="pf-c-form__label-required">*</span>}
43-
</label>
44-
</GridItem>
45-
)}
46-
<GridItem sm={widths.field}>{renderForm([field])}</GridItem>
47-
</Grid>
48-
))}
49-
</GridItem>
50-
<GridItem sm={1}>
51-
{length > minItems && (
52-
<Bullseye>
53-
<CloseIcon onClick={() => remove(fieldIndex)} className="ddf-final-form-group-remove-icon" />
54-
</Bullseye>
55-
)}
56-
{length <= minItems && (
57-
<Bullseye>
58-
<CloseIcon className="ddf-final-form-group-remove-icon disabled" />
59-
</Bullseye>
60-
)}
61-
</GridItem>
62-
</Grid>
28+
<Flex>
29+
<FlexItem className="pf-c-form" grow={{ default: 'flex_1' }}>
30+
{editedFields.map((field) => renderForm([field]))}
31+
</FlexItem>
32+
<FlexItem>
33+
{editedFields[0].label && <Spacer />}
34+
<Button
35+
variant="plain"
36+
aria-label={buttonLabels.remove}
37+
disabled={isRemoveDisabled}
38+
{...(!isRemoveDisabled && { onClick: () => remove(fieldIndex) })}
39+
>
40+
<TrashIcon />
41+
</Button>
42+
</FlexItem>
43+
</Flex>
44+
{!isLast && editedFields.length > 1 && <hr className="ddf-final-form-hr" />}
6345
</React.Fragment>
6446
);
6547
},
@@ -73,6 +55,10 @@ ArrayItem.propTypes = {
7355
remove: PropTypes.func.isRequired,
7456
length: PropTypes.number,
7557
minItems: PropTypes.number,
58+
buttonLabels: PropTypes.shape({
59+
remove: PropTypes.node,
60+
}),
61+
isLast: PropTypes.bool,
7662
};
7763

7864
const DynamicArray = ({ ...props }) => {
@@ -86,22 +72,49 @@ const DynamicArray = ({ ...props }) => {
8672
minItems,
8773
maxItems,
8874
noItemsMessage,
75+
buttonLabels,
8976
...rest
9077
} = useFieldApi(props);
9178
const { dirty, submitFailed, error, submitError } = meta;
9279
const isError = (dirty || submitFailed) && (error || submitError) && (typeof error === 'string' || typeof submitError === 'string');
9380

81+
const combinedButtonLabels = {
82+
add: 'Add item',
83+
removeAll: 'Delete all',
84+
remove: 'Remove',
85+
...buttonLabels,
86+
};
87+
9488
return (
9589
<FieldArray key={rest.input.name} name={rest.input.name} validate={arrayValidator}>
96-
{({ fields: { map, value = [], push, remove } }) => (
97-
<Fragment>
98-
{label && <GridItem sm={12}>{label}</GridItem>}
99-
{description && <GridItem sm={12}>{description}</GridItem>}
100-
{value.length <= 0 && (
101-
<Bullseye>
102-
<GridItem sm={12}>{noItemsMessage}</GridItem>
103-
</Bullseye>
104-
)}
90+
{({ fields: { map, value = [], push, remove, removeBatch } }) => (
91+
<FormFieldGroup
92+
header={
93+
<FormFieldGroupHeader
94+
titleText={{ text: label, id: props.name }}
95+
titleDescription={description}
96+
actions={
97+
<React.Fragment>
98+
<Button
99+
variant="link"
100+
isDisabled={value.length === 0}
101+
{...(value.length !== 0 && { onClick: () => removeBatch(value.map((_, index) => index)) })}
102+
>
103+
{combinedButtonLabels.removeAll}
104+
</Button>
105+
<Button
106+
variant="secondary"
107+
isDisabled={value.length >= maxItems}
108+
{...(!(value.length >= maxItems) && { onClick: () => push(defaultItem) })}
109+
>
110+
{combinedButtonLabels.add}
111+
</Button>
112+
</React.Fragment>
113+
}
114+
/>
115+
}
116+
>
117+
{value.length <= 0 && <Bullseye>{noItemsMessage}</Bullseye>}
105118
{map((name, index) => (
106119
<ArrayItem
107120
key={`${name}-${index}`}
@@ -111,6 +124,8 @@ const DynamicArray = ({ ...props }) => {
111124
remove={remove}
112125
length={value.length}
113126
minItems={minItems}
127+
buttonLabels={combinedButtonLabels}
128+
isLast={value.length === index + 1}
114129
/>
115130
))}
116131
<Grid>
@@ -121,33 +136,27 @@ const DynamicArray = ({ ...props }) => {
121136
</FormHelperText>
122137
)}
123138
</GridItem>
124-
<GridItem sm={1} className="final-form-array-add-container">
125-
{value.length < maxItems && (
126-
<Bullseye>
127-
<AddCircleOIcon onClick={() => push(defaultItem)} className="ddf-final-form-group-add-icon" />
128-
</Bullseye>
129-
)}
130-
{value.length >= maxItems && (
131-
<Bullseye>
132-
<AddCircleOIcon className="ddf-final-form-group-add-icon disabled" />
133-
</Bullseye>
134-
)}
135-
</GridItem>
136139
</Grid>
137-
</Fragment>
140+
</FormFieldGroup>
138141
)}
139142
</FieldArray>
140143
);
141144
};
142145

143146
DynamicArray.propTypes = {
147+
name: PropTypes.string,
144148
label: PropTypes.node,
145149
description: PropTypes.node,
146150
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
147151
defaultItem: PropTypes.any,
148152
minItems: PropTypes.number,
149153
maxItems: PropTypes.number,
150154
noItemsMessage: PropTypes.node,
155+
buttonLabels: PropTypes.shape({
156+
add: PropTypes.node,
157+
remove: PropTypes.node,
158+
removeAll: PropTypes.node,
159+
}),
151160
};
152161

153162
DynamicArray.defaultProps = {
Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,9 @@
1-
.ddf-final-form-group-remove-icon {
2-
color: var(--pf-global--icon--Color--light);
3-
height: 70%;
4-
}
5-
.ddf-final-form-group-remove-icon:hover:not(.disabled) {
6-
color: var(--pf-global--icon--Color--dark);
7-
cursor: pointer;
8-
}
9-
.ddf-final-form-group-remove-icon.disabled {
10-
opacity: 0.5;
11-
}
12-
.final-form-array-add-container {
13-
min-height: 36px !important;
14-
}
15-
.ddf-final-form-group-add-icon {
16-
color: var(--pf-global--icon--Color--light);
17-
min-height: 24px;
18-
min-width: 24px;
19-
}
20-
.ddf-final-form-group-add-icon:hover:not(.disabled) {
21-
color: var(--pf-global--icon--Color--dark);
22-
cursor: pointer;
23-
}
24-
.ddf-final-form-group-add-icon.disabled {
25-
opacity: 0.5;
26-
}
27-
.ddf-final-form-group-label {
28-
margin: auto 0 auto var(--pf-global--spacer--md);
29-
}
301
.ddf-final-form-hr {
31-
color: var(global_BorderColor_100);
32-
}
33-
.ddf-final-form-array-grid:not(:last-child) {
34-
margin-bottom: var(--pf-global--spacer--md);
35-
}
2+
color: var(--pf-global_BorderColor_100);
3+
border-bottom-width: 0;
4+
}
5+
.ddf-final-form-spacer{
6+
display: inline-block;
7+
min-width: 100%;
8+
min-height: 32px;
9+
}

0 commit comments

Comments
 (0)