Skip to content

Commit 2b6dba6

Browse files
committed
feat(dialog): open dialogs with hooks (useAlertDialog, useConfirmationDialog and useDialog)
1 parent 82e63c8 commit 2b6dba6

File tree

5 files changed

+165
-44
lines changed

5 files changed

+165
-44
lines changed

demo/DialogExamples.jsx

+53-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,42 @@
22
import React from 'react';
33

44
// eslint-disable-next-line import/no-unresolved
5-
import { Dialog, Form, FormGroupInput } from '../dist/main';
6-
import { ConfirmationDialog, AlertDialog } from '../src/dialog';
5+
import {
6+
AlertDialog,
7+
ConfirmationDialog,
8+
Dialog,
9+
Form,
10+
FormGroupInput,
11+
useAlertDialog,
12+
useConfirmationDialog,
13+
useDialog,
14+
} from '../dist/main';
715

816
export function DialogExamples() {
17+
const { showDialog, DialogPortal } = useDialog({
18+
title: 'useDialog',
19+
body: ({ foo, bar }) => (
20+
<div>
21+
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quibusdam sequi vero sapiente delectus error sunt, a
22+
eveniet nobis est ex magni nesciunt magnam. Eaque eius hic eligendi dolorum ut quas?
23+
<p>
24+
Extra props: {foo} {bar}
25+
</p>
26+
</div>
27+
),
28+
});
29+
const { showDialog: showConfirmationDialog, DialogPortal: ConfirmationDialogPortal } = useConfirmationDialog({
30+
title: 'useConfirmationDialog',
31+
message: ({ foo }) => <em>Opened by useConfirmationDialog. Extra props: {foo}</em>,
32+
onProceed: () => console.info('onProceed'),
33+
onCancel: () => console.warn('onCancel'),
34+
});
35+
const { showDialog: showAlertDialog, DialogPortal: AlertDialogPortal } = useAlertDialog({
36+
title: 'useAlertDialog',
37+
message: ({ bar }) => <em>Opened by useAlertDialog. Extra props: {bar}</em>,
38+
onClose: () => console.warn('onClose'),
39+
});
40+
941
return (
1042
<div className="row">
1143
<div className="col-6">
@@ -95,7 +127,7 @@ export function DialogExamples() {
95127
onProceed={() => console.info('onProceed')}
96128
onCancel={() => console.warn('onCancel')}
97129
>
98-
<button type="button" className="btn btn-info">
130+
<button type="button" className="btn btn-warning">
99131
Do something
100132
</button>
101133
</ConfirmationDialog>
@@ -177,18 +209,34 @@ export function DialogExamples() {
177209
</>
178210
}
179211
>
180-
<button type="button" className="btn btn-warning">
212+
<button type="button" className="btn btn-info">
181213
Open second dialog
182214
</button>
183215
</Dialog>
184216
</>
185217
}
186218
>
187-
<button type="button" className="btn btn-warning">
219+
<button type="button" className="btn btn-secondary">
188220
Open first dialog
189221
</button>
190222
</Dialog>
191223
</div>
224+
<div className="col-6">
225+
<h1 className="h4 mt-3">Open a dialog programmatically</h1>
226+
227+
<DialogPortal />
228+
<button type="button" className="btn btn-primary" onClick={() => showDialog({ foo: 'Foo', bar: 'Bar' })}>
229+
useDialog
230+
</button>
231+
<ConfirmationDialogPortal />
232+
<button type="button" className="btn btn-warning ml-2" onClick={() => showConfirmationDialog({ foo: 'FOO' })}>
233+
useConfirmationDialog
234+
</button>
235+
<AlertDialogPortal />
236+
<button type="button" className="btn btn-info ml-2" onClick={() => showAlertDialog({ bar: 'BAR' })}>
237+
useAlertDialog
238+
</button>
239+
</div>
192240
</div>
193241
);
194242
}

src/dialog/AlertDialog.jsx

+36-6
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,30 @@ import PropTypes from 'prop-types';
33

44
import { awaitForAsyncTask } from '../utils/event-handlers';
55

6-
import { Dialog } from './Dialog';
6+
import { Dialog, useDialog } from './Dialog';
7+
8+
export function useAlertDialog({ title, message, ...footerProps }) {
9+
const { showDialog, DialogPortal } = useDialog({
10+
title,
11+
body: message,
12+
// eslint-disable-next-line react/prop-types
13+
footer({ close }) {
14+
return <AlertDialogFooter close={close} {...footerProps} />;
15+
},
16+
});
17+
18+
return {
19+
showDialog,
20+
DialogPortal,
21+
};
22+
}
723

824
export function AlertDialog({ title, message, children, onClose, closeLabel }) {
925
return (
1026
<Dialog
1127
title={title}
1228
body={message}
13-
footer={({ close }) => (
14-
<button type="button" className="btn btn-primary" onClick={awaitForAsyncTask(onClose, close)}>
15-
{closeLabel}
16-
</button>
17-
)}
29+
footer={({ close }) => <AlertDialogFooter close={close} onClose={onClose} closeLabel={closeLabel} />}
1830
>
1931
{children}
2032
</Dialog>
@@ -34,3 +46,21 @@ AlertDialog.propTypes = {
3446
title: PropTypes.node,
3547
closeLabel: PropTypes.node,
3648
};
49+
50+
function AlertDialogFooter({ close, onClose, closeLabel }) {
51+
return (
52+
<button type="button" className="btn btn-primary" onClick={awaitForAsyncTask(onClose, close)}>
53+
{closeLabel}
54+
</button>
55+
);
56+
}
57+
AlertDialogFooter.defaultProps = {
58+
onClose: () => {},
59+
closeLabel: 'Close',
60+
};
61+
62+
AlertDialogFooter.propTypes = {
63+
close: PropTypes.func,
64+
onClose: PropTypes.func,
65+
closeLabel: PropTypes.node,
66+
};

src/dialog/ConfirmationDialog.jsx

+52-31
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,38 @@ import PropTypes from 'prop-types';
44
import { awaitForAsyncTask, safeClick } from '../utils/event-handlers';
55
import { formatClasses } from '../utils/attributes';
66

7-
import { Dialog } from './Dialog';
8-
9-
export function ConfirmationDialog({
10-
title,
11-
message,
12-
children,
13-
onProceed,
14-
onCancel,
15-
cancelLabel,
16-
proceedLabel,
17-
proceedType,
18-
}) {
7+
import { Dialog, useDialog } from './Dialog';
8+
9+
export function useConfirmationDialog({ title, message, ...footerProps }) {
10+
const { showDialog, DialogPortal } = useDialog({
11+
title,
12+
body: message,
13+
// eslint-disable-next-line react/prop-types
14+
footer({ close }) {
15+
return <ConfirmationDialogFooter close={close} {...footerProps} />;
16+
},
17+
});
18+
19+
return {
20+
showDialog,
21+
DialogPortal,
22+
};
23+
}
24+
25+
export function ConfirmationDialog({ title, message, children, ...footerProps }) {
1926
return (
2027
<Dialog
2128
title={title}
2229
body={message}
23-
footer={({ close }) => (
24-
<>
25-
<button type="button" className="btn btn-secondary" onClick={safeClick(awaitForAsyncTask(onCancel, close))}>
26-
{cancelLabel}
27-
</button>
28-
<button
29-
type="button"
30-
className={formatClasses(['btn', `btn-${proceedType}`])}
31-
onClick={safeClick(awaitForAsyncTask(onProceed, close))}
32-
>
33-
{proceedLabel}
34-
</button>
35-
</>
36-
)}
30+
footer={({ close }) => <ConfirmationDialogFooter close={close} {...footerProps} />}
3731
>
3832
{children}
3933
</Dialog>
4034
);
4135
}
4236

4337
ConfirmationDialog.defaultProps = {
44-
onProceed: () => {},
45-
onCancel: () => {},
4638
title: 'Atention required',
47-
cancelLabel: 'Cancel',
48-
proceedLabel: 'Proceed',
49-
proceedType: 'primary',
5039
};
5140

5241
ConfirmationDialog.propTypes = {
@@ -59,3 +48,35 @@ ConfirmationDialog.propTypes = {
5948
proceedLabel: PropTypes.node,
6049
proceedType: PropTypes.oneOf(['primary', 'danger', 'success']),
6150
};
51+
52+
function ConfirmationDialogFooter({ close, onProceed, onCancel, cancelLabel, proceedLabel, proceedType }) {
53+
return (
54+
<>
55+
<button type="button" className="btn btn-secondary" onClick={safeClick(awaitForAsyncTask(onCancel, close))}>
56+
{cancelLabel}
57+
</button>
58+
<button
59+
type="button"
60+
className={formatClasses(['btn', `btn-${proceedType}`])}
61+
onClick={safeClick(awaitForAsyncTask(onProceed, close))}
62+
>
63+
{proceedLabel}
64+
</button>
65+
</>
66+
);
67+
}
68+
ConfirmationDialogFooter.defaultProps = {
69+
onProceed: () => {},
70+
onCancel: () => {},
71+
cancelLabel: 'Cancel',
72+
proceedLabel: 'Proceed',
73+
proceedType: 'primary',
74+
};
75+
ConfirmationDialogFooter.propTypes = {
76+
close: PropTypes.func,
77+
onCancel: PropTypes.func,
78+
onProceed: PropTypes.func,
79+
cancelLabel: PropTypes.node,
80+
proceedLabel: PropTypes.node,
81+
proceedType: PropTypes.oneOf(['primary', 'danger', 'success']),
82+
};

src/dialog/Dialog.jsx

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import PropTypes from 'prop-types';
33

44
import { useOpenState } from '../utils/useOpenState';
@@ -8,6 +8,25 @@ import { safeClick } from '../utils/event-handlers';
88
import { ModalPortal } from './ModalPortal';
99
import { Modal } from './Modal';
1010

11+
export function useDialog(props) {
12+
const { isOpen, open, close } = useOpenState();
13+
const [dialogBodyProps, setDialogBodyProps] = useState({});
14+
15+
return {
16+
showDialog(_props) {
17+
setDialogBodyProps(_props);
18+
open();
19+
},
20+
DialogPortal() {
21+
return (
22+
<ModalPortal isOpen={isOpen()} title={props.title}>
23+
<Modal {...props} dialogBodyProps={dialogBodyProps} onClose={close} isOpen={isOpen()} />
24+
</ModalPortal>
25+
);
26+
},
27+
};
28+
}
29+
1130
export function Dialog({ children, ...props }) {
1231
const { isOpen, open, close } = useOpenState();
1332

src/dialog/Modal.jsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export function Modal({
2020
staticBackdrop,
2121
title,
2222
useTimesClose,
23+
dialogBodyProps,
2324
}) {
2425
const modalRef = useRef(null);
2526
const closeAndHide = useCallback(() => {
@@ -88,7 +89,7 @@ export function Modal({
8889
)}
8990
</div>
9091
)}
91-
<div className="modal-body">{renderObjectOrFunction(body, { close: closeAndHide })}</div>
92+
<div className="modal-body">{renderObjectOrFunction(body, { ...dialogBodyProps, close: closeAndHide })}</div>
9293
{footer && <div className="modal-footer">{renderObjectOrFunction(footer, { close: closeAndHide })}</div>}
9394
</div>
9495
</div>
@@ -99,6 +100,7 @@ export function Modal({
99100
Modal.defaultProps = {
100101
afterOpen: () => {},
101102
centered: true,
103+
dialogBodyProps: {},
102104
keyboard: true,
103105
scrollable: false,
104106
size: '',
@@ -110,6 +112,7 @@ Modal.propTypes = {
110112
afterOpen: PropTypes.func,
111113
body: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
112114
centered: PropTypes.bool,
115+
dialogBodyProps: PropTypes.object,
113116
footer: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
114117
isOpen: PropTypes.bool,
115118
keyboard: PropTypes.bool,

0 commit comments

Comments
 (0)