Skip to content

Commit bc77836

Browse files
rhamiltoclaude
andcommitted
Migrate DeleteModal and related modals to modern PatternFly Modal
- Migrate DeleteModal from deprecated factory/modal components to modern PatternFly v6 Modal components - Create reusable ModalErrorContent component for error display - Update configure-count-modal and configure-machine-autoscaler-modal to use modern Modal components and ModalErrorContent - Fix button order in add-group-users-modal (Primary first, Cancel second) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 86275ab commit bc77836

5 files changed

Lines changed: 107 additions & 52 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { FC, ReactNode } from 'react';
2+
import { HelperText, HelperTextItem } from '@patternfly/react-core';
3+
4+
export interface ModalErrorContentProps {
5+
/** The error message to display */
6+
errorMessage?: ReactNode;
7+
/** Optional additional CSS class names */
8+
className?: string;
9+
/** Optional data-test attribute for testing */
10+
'data-test'?: string;
11+
}
12+
13+
/**
14+
* Displays an error message in a modal body using PatternFly HelperText.
15+
* This component follows the pattern used in modern PatternFly modals where
16+
* errors are shown in the modal body (not footer) using HelperText.
17+
*
18+
* @example
19+
* ```tsx
20+
* <ModalBody>
21+
* {/* modal content *\/}
22+
* <ModalErrorContent errorMessage={errorMessage} />
23+
* </ModalBody>
24+
* ```
25+
*/
26+
export const ModalErrorContent: FC<ModalErrorContentProps> = ({
27+
errorMessage,
28+
className = 'pf-v6-u-mt-md',
29+
'data-test': dataTest = 'modal-error',
30+
}) => {
31+
if (!errorMessage) {
32+
return null;
33+
}
34+
35+
return (
36+
<HelperText isLiveRegion className={className}>
37+
<HelperTextItem variant="error" data-test={dataTest}>
38+
{errorMessage}
39+
</HelperTextItem>
40+
</HelperText>
41+
);
42+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ModalErrorContent';

frontend/public/components/modals/configure-count-modal.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import {
77
ModalBody,
88
ModalFooter,
99
Button,
10-
HelperText,
11-
HelperTextItem,
1210
FormGroup,
1311
Form,
1412
} from '@patternfly/react-core';
@@ -18,6 +16,7 @@ import { k8sPatchResource } from '@console/dynamic-plugin-sdk/src/utils/k8s';
1816
import { K8sResourceKind, K8sModel } from '../../module/k8s';
1917
import { NumberSpinner, NumberSpinnerProps } from '../utils/number-spinner';
2018
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
19+
import { ModalErrorContent } from '@console/shared/src/components/modal-error-content';
2120

2221
export const ConfigureCountModal: OverlayComponent<ConfigureCountModalProps> = (props) => {
2322
const {
@@ -100,21 +99,17 @@ export const ConfigureCountModal: OverlayComponent<ConfigureCountModalProps> = (
10099
required
101100
min={0}
102101
/>
103-
{errorMessage && (
104-
<HelperText isLiveRegion className="pf-v6-u-mt-md">
105-
<HelperTextItem variant="error">{errorMessage}</HelperTextItem>
106-
</HelperText>
107-
)}
108102
</FormGroup>
109103
</Form>
104+
<ModalErrorContent errorMessage={errorMessage} />
110105
</ModalBody>
111106
<ModalFooter>
112-
<Button variant="secondary" onClick={closeOverlay} type="button">
113-
{t('public~Cancel')}
114-
</Button>
115107
<Button variant="primary" isLoading={inProgress} onClick={submit}>
116108
{buttonTextKey ? t(buttonTextKey, buttonTextVariables) : buttonText}
117109
</Button>
110+
<Button variant="link" onClick={closeOverlay} type="button">
111+
{t('public~Cancel')}
112+
</Button>
118113
</ModalFooter>
119114
</Modal>
120115
);

frontend/public/components/modals/configure-machine-autoscaler-modal.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import {
88
ModalBody,
99
ModalFooter,
1010
Button,
11-
HelperText,
12-
HelperTextItem,
1311
FormGroup,
1412
Form,
1513
} from '@patternfly/react-core';
@@ -20,6 +18,7 @@ import { resourcePathFromModel } from '../utils/resource-link';
2018
import { K8sResourceKind } from '../../module/k8s';
2119
import { k8sCreateResource } from '@console/dynamic-plugin-sdk/src/utils/k8s';
2220
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
21+
import { ModalErrorContent } from '@console/shared/src/components/modal-error-content';
2322

2423
export const ConfigureMachineAutoscalerModal: OverlayComponent<ConfigureMachineAutoscalerModalProps> = ({
2524
machineSet,
@@ -134,20 +133,16 @@ export const ConfigureMachineAutoscalerModal: OverlayComponent<ConfigureMachineA
134133
required
135134
/>
136135
</FormGroup>
137-
{errorMessage && (
138-
<HelperText isLiveRegion className="pf-v6-u-mt-md">
139-
<HelperTextItem variant="error">{errorMessage}</HelperTextItem>
140-
</HelperText>
141-
)}
142136
</Form>
137+
<ModalErrorContent errorMessage={errorMessage} />
143138
</ModalBody>
144139
<ModalFooter>
145-
<Button variant="secondary" onClick={closeOverlay || cancelProp} type="button">
146-
{t('public~Cancel')}
147-
</Button>
148140
<Button variant="primary" isLoading={inProgress} onClick={submit}>
149141
{t('public~Create')}
150142
</Button>
143+
<Button variant="link" onClick={closeOverlay || cancelProp} type="button">
144+
{t('public~Cancel')}
145+
</Button>
151146
</ModalFooter>
152147
</Modal>
153148
);

frontend/public/components/modals/delete-modal.tsx

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import * as _ from 'lodash';
22
import type { FC, ReactNode } from 'react';
33
import { useState, useCallback, useEffect } from 'react';
4-
import { Alert, Backdrop, Checkbox, Modal, ModalVariant } from '@patternfly/react-core';
4+
import {
5+
Alert,
6+
Button,
7+
Checkbox,
8+
Modal,
9+
ModalBody,
10+
ModalFooter,
11+
ModalHeader,
12+
ModalVariant,
13+
} from '@patternfly/react-core';
514
import { Trans, useTranslation } from 'react-i18next';
615
import { useNavigate } from 'react-router-dom-v5-compat';
716
import { OverlayComponent } from '@console/dynamic-plugin-sdk/src/app/modal-support/OverlayProvider';
8-
import {
9-
ModalTitle,
10-
ModalBody,
11-
ModalSubmitFooter,
12-
ModalWrapper,
13-
ModalComponentProps,
14-
} from '../factory/modal';
17+
import { ModalComponentProps } from '../factory/modal';
1518
import { resourceListPathFromModel, ResourceLink } from '../utils/resource-link';
1619
import {
1720
k8sKill,
@@ -24,6 +27,7 @@ import {
2427
import { YellowExclamationTriangleIcon } from '@console/shared/src/components/status/icons';
2528
import { ClusterServiceVersionModel } from '@console/operator-lifecycle-manager/src/models';
2629
import { findOwner } from '../../module/k8s/managed-by';
30+
import { ModalErrorContent } from '@console/shared/src/components/modal-error-content';
2731

2832
import { LocationDescriptor } from 'history';
2933
import { usePromiseHandler } from '@console/shared/src/hooks/promise-handler';
@@ -87,15 +91,20 @@ export const DeleteModal = (props: DeleteModalProps) => {
8791
});
8892

8993
const { kind, resource, message } = props;
94+
9095
return (
9196
<form onSubmit={submit} name="form" className="modal-content">
92-
<ModalTitle>
93-
<YellowExclamationTriangleIcon className="co-icon-space-r" />{' '}
94-
{t('public~Delete {{kind}}?', {
95-
kind: kind ? (kind.labelKey ? t(kind.labelKey) : kind.label) : '',
96-
})}
97-
</ModalTitle>
98-
<ModalBody className="modal-body">
97+
<ModalHeader
98+
title={
99+
<>
100+
<YellowExclamationTriangleIcon className="co-icon-space-r" />{' '}
101+
{t('public~Delete {{kind}}?', {
102+
kind: kind ? (kind.labelKey ? t(kind.labelKey) : kind.label) : '',
103+
})}
104+
</>
105+
}
106+
/>
107+
<ModalBody>
99108
{message}
100109
<div>
101110
{_.has(resource.metadata, 'namespace') ? (
@@ -156,14 +165,23 @@ export const DeleteModal = (props: DeleteModalProps) => {
156165
</Alert>
157166
)}
158167
</div>
168+
<ModalErrorContent errorMessage={errorMessage} data-test="alert-error" />
159169
</ModalBody>
160-
<ModalSubmitFooter
161-
errorMessage={errorMessage}
162-
inProgress={inProgress}
163-
submitDanger
164-
submitText={props.btnText || t('public~Delete')}
165-
cancel={props.cancel}
166-
/>
170+
<ModalFooter>
171+
<Button
172+
variant="danger"
173+
onClick={submit}
174+
isLoading={inProgress}
175+
isDisabled={inProgress}
176+
data-test="confirm-action"
177+
id="confirm-action"
178+
>
179+
{props.btnText || t('public~Delete')}
180+
</Button>
181+
<Button variant="link" onClick={props.cancel} data-test-id="modal-cancel-action">
182+
{t('public~Cancel')}
183+
</Button>
184+
</ModalFooter>
167185
</form>
168186
);
169187
};
@@ -172,20 +190,24 @@ export const DeleteOverlay: FC<DeleteModalProps> = (props) => {
172190
const [isOpen, setIsOpen] = useState(true);
173191
const closeModal = () => setIsOpen(false);
174192
return isOpen ? (
175-
<Backdrop>
176-
<Modal variant={ModalVariant.small} isOpen>
177-
<DeleteModal cancel={closeModal} close={closeModal} {...props} />
178-
</Modal>
179-
</Backdrop>
193+
<Modal variant={ModalVariant.small} isOpen onClose={closeModal}>
194+
<DeleteModal cancel={closeModal} close={closeModal} {...props} />
195+
</Modal>
180196
) : null;
181197
};
182198

183199
export const DeleteModalOverlay: OverlayComponent<DeleteModalProps> = (props) => {
184-
return (
185-
<ModalWrapper blocking onClose={props.closeOverlay}>
186-
<DeleteModal {...props} cancel={props.closeOverlay} close={props.closeOverlay} />
187-
</ModalWrapper>
188-
);
200+
const [isOpen, setIsOpen] = useState(true);
201+
const handleClose = () => {
202+
setIsOpen(false);
203+
props.closeOverlay();
204+
};
205+
206+
return isOpen ? (
207+
<Modal variant={ModalVariant.small} isOpen onClose={handleClose}>
208+
<DeleteModal {...props} cancel={handleClose} close={handleClose} />
209+
</Modal>
210+
) : null;
189211
};
190212

191213
export type DeleteModalProps = {

0 commit comments

Comments
 (0)