@chris-c-brine/form-dialog 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -162
- package/dist/components/buttons/FormResetButton.d.ts +0 -4
- package/dist/components/buttons/FormSubmitButton.d.ts +1 -4
- package/dist/components/dialogs/BaseDialog.d.ts +5 -26
- package/dist/components/dialogs/BlackoutDialog.d.ts +2 -2
- package/dist/components/dialogs/FormDialog.d.ts +7 -9
- package/dist/components/dialogs/FormDialogActions.d.ts +2 -2
- package/dist/components/forms/PaperForm.d.ts +14 -6
- package/dist/components/forms/PersistForm.d.ts +2 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/useDialog.d.ts +18 -12
- package/dist/hooks/useFormDialog.d.ts +1 -1
- package/dist/hooks/usePersistForm.d.ts +29 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.esm.js +207 -234
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +207 -234
- package/dist/index.js.map +1 -1
- package/dist/utils/ThemeBridge.d.ts +0 -1
- package/dist/utils/applyDefaultFormDialogProps.d.ts +29 -29
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/utils.types.d.ts +15 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import { createContext, useContext, useState, useEffect,
|
|
2
|
+
import { createContext, useContext, Fragment, useState, useEffect, useRef, useCallback, memo, useMemo, Children } from 'react';
|
|
3
3
|
import { Dialog, DialogTitle, DialogContent, DialogActions, Paper, IconButton, Button, useTheme as useTheme$1, Box, CircularProgress, Badge, Grid } from '@mui/material';
|
|
4
|
-
import {
|
|
4
|
+
import { useFormContext, FormContainer, useFormState } from 'react-hook-form-mui';
|
|
5
|
+
import { isEqualWith, isObject, isNumber, isEqual, isEmpty, omit, merge, debounce } from 'lodash';
|
|
6
|
+
import { Refresh, Close, Save } from '@mui/icons-material';
|
|
5
7
|
import { useTheme } from '@mui/material/styles';
|
|
6
|
-
import { merge, isEqualWith, isObject, isNumber, isEqual, isEmpty, omit, debounce } from 'lodash';
|
|
7
|
-
import { Close, Refresh, Save } from '@mui/icons-material';
|
|
8
8
|
import { create } from 'zustand';
|
|
9
9
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
|
10
|
-
import { useFormContext as useFormContext$1 } from 'react-hook-form';
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* A component for rendering a modal dialog with an optional blackout effect.
|
|
14
13
|
*/
|
|
15
|
-
const BlackoutDialog = ({ open = false, blackout = false, id = "blackout-dialog", children, sx,
|
|
14
|
+
const BlackoutDialog = ({ open = false, blackout = false, id = "blackout-dialog", children, sx, ...props }) => {
|
|
16
15
|
const sxProps = blackout ? { ...sx, backgroundColor: "black" } : sx;
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* 1) When you open a dialog via a button, the button's ancestor gets aria-hidden, presumably so that
|
|
18
|
+
* screen readers (SR) can't interact with it while the dialog is open.
|
|
19
|
+
* 2) When you close the dialog, the aria-hidden is removed so that SRs can interact with the button again.
|
|
20
|
+
* 3) Also, when closing, the focus is moved back on the opener button.
|
|
21
|
+
*
|
|
22
|
+
* Now comes the important part. You get Blocked aria-hidden on a <div> only when 3 happens before 2. In this case,
|
|
23
|
+
* as the error message explains, we are trying to place focus somewhere where SRs don't have access, which is
|
|
24
|
+
* a No No, and browsers correctly warn about it.
|
|
25
|
+
*
|
|
26
|
+
* This ordering is dependent on the closeAfterTransition prop on the MUI Dialog. When false and user triggers
|
|
27
|
+
* dialog close, aria-hidden is removed (2) and then focus is placed on the opener button (3). Meaning there is no
|
|
28
|
+
* issue. You can see it in action here. However, when it's true (the default), the order is the other way around,
|
|
29
|
+
* and you get the warning.
|
|
30
|
+
*/
|
|
31
|
+
return (jsx(Dialog, { closeAfterTransition: false, open: open, id: id, ...props, sx: sxProps, children: children }));
|
|
18
32
|
};
|
|
19
33
|
|
|
20
34
|
/**
|
|
@@ -36,27 +50,11 @@ const BlackoutDialog = ({ open = false, blackout = false, id = "blackout-dialog"
|
|
|
36
50
|
* consistent styling and behavior.
|
|
37
51
|
*
|
|
38
52
|
* @example
|
|
39
|
-
* // Basic usage with all sections
|
|
40
|
-
* <BaseDialog
|
|
41
|
-
* open={open}
|
|
42
|
-
* onClose={handleClose}
|
|
43
|
-
* title="Confirmation"
|
|
44
|
-
* actions={
|
|
45
|
-
* <>
|
|
46
|
-
* <Button onClick={handleCancel}>Cancel</Button>
|
|
47
|
-
* <Button onClick={handleConfirm}>Confirm</Button>
|
|
48
|
-
* </>
|
|
49
|
-
* }
|
|
50
|
-
* >
|
|
51
|
-
* Are you sure you want to proceed?
|
|
52
|
-
* </BaseDialog>
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
53
|
* // Customized with section props
|
|
56
54
|
* <BaseDialog
|
|
57
55
|
* open={isOpen}
|
|
58
56
|
* title="Advanced Settings"
|
|
59
|
-
* titleProps={{ sx: {
|
|
57
|
+
* titleProps={{ sx: { backgroundColor: 'primary.main', color: 'white' } }}
|
|
60
58
|
* contentProps={{ sx: { p: 3 } }}
|
|
61
59
|
* actionsProps={{ sx: { justifyContent: 'space-between' } }}
|
|
62
60
|
* actions={<SettingsDialogActions />}
|
|
@@ -74,158 +72,15 @@ const BaseDialog = ({ title = null, closeButton, titleProps, children = null, co
|
|
|
74
72
|
...sx,
|
|
75
73
|
}, ...props, children: [title && jsx(DialogTitle, { ...titleProps, children: title }), " ", closeButton, children && jsx(DialogContent, { ...contentProps, children: children }), actions && jsx(DialogActions, { ...actionsProps, children: actions })] }));
|
|
76
74
|
|
|
77
|
-
/**
|
|
78
|
-
* A component that combines a Material UI Paper with a form container
|
|
79
|
-
*
|
|
80
|
-
* PaperForm wraps form content in an elevated paper surface, providing
|
|
81
|
-
* visual distinction and a clean container for form elements. It integrates
|
|
82
|
-
* seamlessly with react-hook-form by using FormContainer internally.
|
|
83
|
-
*
|
|
84
|
-
* This component is useful for creating standalone form surfaces outside of dialogs,
|
|
85
|
-
* such as on pages or within cards. It supports all Paper props for styling
|
|
86
|
-
* while handling form state management through FormContainer.
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* // Basic usage
|
|
90
|
-
* <PaperForm
|
|
91
|
-
* formProps={{
|
|
92
|
-
* defaultValues: { name: "", email: "" },
|
|
93
|
-
* onSuccess: handleSubmit
|
|
94
|
-
* }}
|
|
95
|
-
* elevation={3}
|
|
96
|
-
* >
|
|
97
|
-
* <TextFieldElement name="name" label="Name" />
|
|
98
|
-
* <TextFieldElement name="email" label="Email" />
|
|
99
|
-
* <Button type="submit">Submit</Button>
|
|
100
|
-
* </PaperForm>
|
|
101
|
-
*
|
|
102
|
-
* @example
|
|
103
|
-
* // With custom styling and validation
|
|
104
|
-
* <PaperForm
|
|
105
|
-
* formProps={{
|
|
106
|
-
* defaultValues: formDefaultValues,
|
|
107
|
-
* onSuccess: handleSubmit,
|
|
108
|
-
* onError: handleError,
|
|
109
|
-
* }}
|
|
110
|
-
* sx={{
|
|
111
|
-
* padding: 3,
|
|
112
|
-
* borderRadius: 2,
|
|
113
|
-
* backgroundColor: 'background.paper',
|
|
114
|
-
* }}
|
|
115
|
-
* elevation={2}
|
|
116
|
-
* >
|
|
117
|
-
* <FormFields />
|
|
118
|
-
* <FormDialogActions submitProps={{ children: "Save Profile" }} />
|
|
119
|
-
* </PaperForm>
|
|
120
|
-
*
|
|
121
|
-
* @template T - The type of form values being handled
|
|
122
|
-
*/
|
|
123
|
-
const PaperForm = ({ children, formProps, ...paperProps }) => (jsx(Paper, { ...paperProps, children: jsx(FormContainer, { ...formProps, children: children }) }));
|
|
124
|
-
|
|
125
75
|
const FormDialogContext = createContext(undefined);
|
|
126
76
|
FormDialogContext.displayName = "FormDialogContext";
|
|
127
77
|
|
|
128
|
-
// Create a context that will hold the parent theme
|
|
129
|
-
const ParentThemeContext = createContext(null);
|
|
130
|
-
const useParentTheme = () => useContext(ParentThemeContext);
|
|
131
|
-
const ThemeBridge = ({ children }) => {
|
|
132
|
-
const parentTheme = useTheme();
|
|
133
|
-
const [themeSnapshot, setThemeSnapshot] = useState(parentTheme);
|
|
134
|
-
// Update the snapshot when the parent theme changes
|
|
135
|
-
useEffect(() => {
|
|
136
|
-
setThemeSnapshot(parentTheme);
|
|
137
|
-
}, [parentTheme]);
|
|
138
|
-
// Pass the parent theme to our components
|
|
139
|
-
return (jsx(ParentThemeContext.Provider, { value: themeSnapshot, children: children }));
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Context provider component for form dialog state management
|
|
144
|
-
*
|
|
145
|
-
* FormDialogProvider creates and manages shared state for form dialogs,
|
|
146
|
-
* allowing child components to access dialog state and controls through
|
|
147
|
-
* the useFormDialog hook.
|
|
148
|
-
*
|
|
149
|
-
* This provider handles:
|
|
150
|
-
* - Dialog open/close state
|
|
151
|
-
* - Form-wide disabled state
|
|
152
|
-
* - Dialog close handler passing
|
|
153
|
-
* - Theme consistency via ThemeBridge
|
|
154
|
-
*
|
|
155
|
-
* Use this provider to wrap form components that need to share state
|
|
156
|
-
* and disabled status, such as dialogs with multiple form fields
|
|
157
|
-
* and action buttons that need to be coordinated.
|
|
158
|
-
*
|
|
159
|
-
* @example
|
|
160
|
-
* // Basic usage wrapping a form
|
|
161
|
-
* <FormDialogProvider>
|
|
162
|
-
* <FormContainer onSuccess={handleSubmit}>
|
|
163
|
-
* <TextFieldElement name="name" />
|
|
164
|
-
* <FormDialogActions />
|
|
165
|
-
* </FormContainer>
|
|
166
|
-
* </FormDialogProvider>
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* // With pre-configured dialog state
|
|
170
|
-
* <FormDialogProvider open={isOpen} closeDialog={handleClose}>
|
|
171
|
-
* <PaperForm formProps={formProps}>
|
|
172
|
-
* <LoginFormFields />
|
|
173
|
-
* <FormDialogActions submitProps={{ maxAttempts: 3 }} />
|
|
174
|
-
* </PaperForm>
|
|
175
|
-
* </FormDialogProvider>
|
|
176
|
-
*/
|
|
177
|
-
const FormDialogProvider = ({ children, ...value }) => {
|
|
178
|
-
const [disabled, setDisabled] = useState(false);
|
|
179
|
-
return (jsx(ThemeBridge, { children: jsx(FormDialogContext.Provider, { value: { ...value, disabled, setDisabled }, children: children }) }));
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* A dialog component specialized for forms with integrated context providers
|
|
184
|
-
*
|
|
185
|
-
* FormDialog combines Material UI dialog functionality with React Hook Form,
|
|
186
|
-
* creating a comprehensive solution for modal forms. It provides context
|
|
187
|
-
* providers for form state management, dialog controls, and accessibility.
|
|
188
|
-
*
|
|
189
|
-
* Key features:
|
|
190
|
-
* - Integrates React Hook Form with Material UI dialogs
|
|
191
|
-
* - Provides form state management through FormProvider
|
|
192
|
-
* - Establishes dialog context through FormDialogProvider
|
|
193
|
-
* - Handles form submission and error states
|
|
194
|
-
* - Supports loading states for form operations
|
|
195
|
-
* - Maintains consistent styling and behavior
|
|
196
|
-
*
|
|
197
|
-
* Child components can access the form and dialog context through hooks:
|
|
198
|
-
* - useFormContext() - Access form methods and state
|
|
199
|
-
* - useFormState() - Access form validation state
|
|
200
|
-
* - useFormDialog() - Access dialog controls and state
|
|
201
|
-
*
|
|
202
|
-
*/
|
|
203
|
-
const FormDialog = ({ formProps, children, open, onClose, ...dialogProps }) => {
|
|
204
|
-
const PaperComponent = useCallback((props) => jsx(PaperForm, { formProps: formProps, ...props }), [formProps]);
|
|
205
|
-
const baseDialogProps = useMemo(() => merge({
|
|
206
|
-
actionsProps: { sx: { pt: 2.5 } },
|
|
207
|
-
contentProps: {
|
|
208
|
-
sx: {
|
|
209
|
-
display: "flex",
|
|
210
|
-
justifyContent: "center",
|
|
211
|
-
boxSizing: "border-box",
|
|
212
|
-
maxHeight: `calc(100vh - 235px)`, // 235 estimate using H4 title & default FormDialogActions
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
}, dialogProps), [dialogProps]);
|
|
216
|
-
return (jsx(FormDialogProvider, { open: open, closeDialog: onClose, children: jsx(BaseDialog, { PaperComponent: PaperComponent, open: open, onClose: onClose, closeButton: jsx(IconButton, { sx: {
|
|
217
|
-
position: "absolute",
|
|
218
|
-
top: 8,
|
|
219
|
-
right: 8,
|
|
220
|
-
}, onClick: (e) => onClose === null || onClose === void 0 ? void 0 : onClose(e, 'escapeKeyDown'), children: jsx(Close, { fontSize: "small", "aria-label": "close-form-dialog" }) }), ...baseDialogProps, children: children }) }));
|
|
221
|
-
};
|
|
222
|
-
|
|
223
78
|
// src/components/dialogs/FormDialog/hooks/useFormDialog.ts
|
|
224
79
|
/**
|
|
225
80
|
* Hook for accessing the FormDialog context values and functions
|
|
226
81
|
*
|
|
227
82
|
* This hook provides access to the form dialog state and controls managed by
|
|
228
|
-
* the FormDialogProvider. It allows components to interact with dialog state,
|
|
83
|
+
* the FormDialogProvider. It allows components to interact with the dialog state,
|
|
229
84
|
* including open/close status and form-wide disabled state.
|
|
230
85
|
*
|
|
231
86
|
* The context provides:
|
|
@@ -294,8 +149,20 @@ const deepCompare = (a, b, equalEmpty = false) => {
|
|
|
294
149
|
deepCompare.displayName = "deepCompare";
|
|
295
150
|
|
|
296
151
|
function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cancelProps, variant = "iconText" }) {
|
|
152
|
+
// Max Attempts
|
|
297
153
|
const maxAttempts = (submitProps === null || submitProps === void 0 ? void 0 : submitProps.maxAttempts) || Infinity;
|
|
298
154
|
const hasMaxAttempts = isFinite(maxAttempts) && maxAttempts > 0;
|
|
155
|
+
// Grid Container
|
|
156
|
+
const gridContainerProps = {
|
|
157
|
+
justifyContent: "space-between",
|
|
158
|
+
minWidth: 275,
|
|
159
|
+
px: 1,
|
|
160
|
+
pb: 1.5,
|
|
161
|
+
columnGap: 1,
|
|
162
|
+
width: "100%",
|
|
163
|
+
...gridProps,
|
|
164
|
+
};
|
|
165
|
+
// Reset Button
|
|
299
166
|
let resetChildren = undefined;
|
|
300
167
|
if (variant == "icon")
|
|
301
168
|
resetChildren = jsx(Refresh, {});
|
|
@@ -306,15 +173,7 @@ function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cance
|
|
|
306
173
|
...(resetChildren ? { children: resetChildren } : {}),
|
|
307
174
|
...omit(resetProps, "disabled"),
|
|
308
175
|
};
|
|
309
|
-
|
|
310
|
-
justifyContent: "space-between",
|
|
311
|
-
minWidth: 275,
|
|
312
|
-
px: 1,
|
|
313
|
-
pb: 1.5,
|
|
314
|
-
columnGap: 1,
|
|
315
|
-
width: "100%",
|
|
316
|
-
...gridProps,
|
|
317
|
-
};
|
|
176
|
+
// Cancel Button
|
|
318
177
|
let cancelChildren = undefined;
|
|
319
178
|
if (variant == "icon")
|
|
320
179
|
cancelChildren = jsx(Close, {});
|
|
@@ -324,6 +183,7 @@ function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cance
|
|
|
324
183
|
...(cancelChildren ? { children: cancelChildren } : {}),
|
|
325
184
|
...cancelProps,
|
|
326
185
|
};
|
|
186
|
+
// Submit Button
|
|
327
187
|
let submitChildren = {};
|
|
328
188
|
if (variant == "icon")
|
|
329
189
|
submitChildren = { children: jsx(Fragment$1, {}), altIcon: jsx(Save, {}) };
|
|
@@ -333,14 +193,22 @@ function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cance
|
|
|
333
193
|
...submitChildren,
|
|
334
194
|
...submitProps,
|
|
335
195
|
};
|
|
336
|
-
return {
|
|
337
|
-
resetButtonProps,
|
|
338
|
-
gridContainerProps,
|
|
339
|
-
cancelButtonProps,
|
|
340
|
-
submitButtonProps,
|
|
341
|
-
};
|
|
196
|
+
return { resetButtonProps, gridContainerProps, cancelButtonProps, submitButtonProps };
|
|
342
197
|
}
|
|
343
198
|
|
|
199
|
+
// Create a context that will hold the parent theme
|
|
200
|
+
const ParentThemeContext = createContext(null);
|
|
201
|
+
const ThemeBridge = ({ children }) => {
|
|
202
|
+
const parentTheme = useTheme();
|
|
203
|
+
const [themeSnapshot, setThemeSnapshot] = useState(parentTheme);
|
|
204
|
+
// Update the snapshot when the parent theme changes
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
setThemeSnapshot(parentTheme);
|
|
207
|
+
}, [parentTheme]);
|
|
208
|
+
// Pass the parent theme to our components
|
|
209
|
+
return (jsx(ParentThemeContext.Provider, { value: themeSnapshot, children: children }));
|
|
210
|
+
};
|
|
211
|
+
|
|
344
212
|
const useMaxAttempts = ({ maxAttempts }) => {
|
|
345
213
|
const { formState, resetField } = useFormContext();
|
|
346
214
|
const { setDisabled, disabled } = useFormDialog();
|
|
@@ -435,36 +303,18 @@ function useOnMount(callback) {
|
|
|
435
303
|
* - Only saves changed fields, not the entire form state
|
|
436
304
|
* - Automatically clears storage when form values match defaults
|
|
437
305
|
*
|
|
438
|
-
* @example
|
|
439
|
-
* // In a form component:
|
|
440
|
-
* const MyPersistedForm = () => {
|
|
441
|
-
* const formMethods = useForm({ defaultValues: { name: '' } });
|
|
442
|
-
* // Connect the form to persistence
|
|
443
|
-
* usePersistedForm({ formName: 'user-registration' });
|
|
444
|
-
*
|
|
445
|
-
* return (<FormProvider {...formMethods}>
|
|
446
|
-
* <TextFieldElement name="name" label="Name" />
|
|
447
|
-
* </FormProvider>);
|
|
448
|
-
* }
|
|
449
|
-
*
|
|
450
|
-
* @example
|
|
451
|
-
* // For convenience, use with the PersistForm wrapper component:
|
|
452
|
-
* <PersistForm formName="user-profile">
|
|
453
|
-
* <ProfileFormFields />
|
|
454
|
-
* </PersistForm>
|
|
455
|
-
*
|
|
456
|
-
* @param props - Configuration options
|
|
457
306
|
*/
|
|
458
|
-
const
|
|
459
|
-
const { setValue, watch, formState } =
|
|
307
|
+
const usePersistForm = ({ formName = "", formContext }) => {
|
|
308
|
+
// const { setValue, watch, formState } = formContext;
|
|
460
309
|
const { formData, updateFormData, resetFormData } = createFormChangeStore(formName)();
|
|
461
|
-
const {
|
|
310
|
+
const { disabled } = useFormDialog();
|
|
462
311
|
const debouncedUpdate = debounce((key, values) => {
|
|
463
312
|
updateFormData(key, values);
|
|
464
313
|
}, 200);
|
|
465
314
|
useEffect(() => {
|
|
466
|
-
if (!formName)
|
|
315
|
+
if (!formName || !formContext)
|
|
467
316
|
return;
|
|
317
|
+
const { watch, formState } = formContext;
|
|
468
318
|
const subscription = watch((newValues, { name }) => {
|
|
469
319
|
if (deepCompare(newValues, formState.defaultValues, true)) {
|
|
470
320
|
resetFormData();
|
|
@@ -483,19 +333,18 @@ const usePersistedForm = ({ formName = "" }) => {
|
|
|
483
333
|
debouncedUpdate.cancel();
|
|
484
334
|
};
|
|
485
335
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
486
|
-
}, [
|
|
336
|
+
}, [formContext, debouncedUpdate]);
|
|
487
337
|
useOnMount(() => {
|
|
488
|
-
if (!isEmpty(formData) && !disabled && !formState.isLoading &&
|
|
338
|
+
if (!isEmpty(formData) && !disabled && !(formContext === null || formContext === void 0 ? void 0 : formContext.formState.isLoading) && formName) {
|
|
489
339
|
setTimeout(() => {
|
|
490
340
|
Object.entries(formData).forEach(([key, value]) => {
|
|
491
|
-
setValue(key, value, { shouldDirty: true, shouldTouch: true });
|
|
341
|
+
formContext === null || formContext === void 0 ? void 0 : formContext.setValue(key, value, { shouldDirty: true, shouldTouch: true });
|
|
492
342
|
});
|
|
493
343
|
}, 200);
|
|
494
344
|
}
|
|
495
345
|
});
|
|
496
346
|
};
|
|
497
347
|
|
|
498
|
-
// src/hooks/useDialog.ts
|
|
499
348
|
/**
|
|
500
349
|
* Hook for managing dialog state and providing dialog control functions
|
|
501
350
|
*
|
|
@@ -503,6 +352,8 @@ const usePersistedForm = ({ formName = "" }) => {
|
|
|
503
352
|
* functions to open and close the dialog, along with props that can be
|
|
504
353
|
* spread onto a Material-UI Dialog component.
|
|
505
354
|
*
|
|
355
|
+
* Restores focus to the opening element, but only after the dialog has fully closed.
|
|
356
|
+
*
|
|
506
357
|
* @example
|
|
507
358
|
* // Basic usage
|
|
508
359
|
* const { openDialog, closeDialog, dialogProps } = useDialog();
|
|
@@ -513,20 +364,18 @@ const usePersistedForm = ({ formName = "" }) => {
|
|
|
513
364
|
*
|
|
514
365
|
* @example
|
|
515
366
|
* // With initial configuration
|
|
516
|
-
* const { dialogProps } = useDialog({ keepMounted:
|
|
517
|
-
*
|
|
367
|
+
* const { dialogProps } = useDialog({ keepMounted: true});
|
|
518
368
|
*
|
|
519
369
|
* @param props - Optional configuration options
|
|
520
370
|
*/
|
|
521
371
|
const useDialog = (props) => {
|
|
522
|
-
var _a;
|
|
523
372
|
const [open, setOpen] = useState(!!(props === null || props === void 0 ? void 0 : props.open));
|
|
524
|
-
const
|
|
373
|
+
const closeDialog = useCallback(() => setOpen(false), []);
|
|
525
374
|
const openDialog = useCallback(() => setOpen(true), []);
|
|
526
375
|
return {
|
|
527
|
-
closeDialog
|
|
376
|
+
closeDialog,
|
|
528
377
|
openDialog,
|
|
529
|
-
dialogProps: { open, onClose, keepMounted: (
|
|
378
|
+
dialogProps: { open, onClose: closeDialog, keepMounted: !!(props === null || props === void 0 ? void 0 : props.keepMounted) }
|
|
530
379
|
};
|
|
531
380
|
};
|
|
532
381
|
|
|
@@ -571,11 +420,143 @@ const useDialog = (props) => {
|
|
|
571
420
|
* </PersistForm>
|
|
572
421
|
* </FormDialog>
|
|
573
422
|
*/
|
|
574
|
-
const PersistForm = ({ children, formName })
|
|
575
|
-
|
|
423
|
+
const PersistForm = memo(function ({ children, formName }) {
|
|
424
|
+
const formContext = useFormContext();
|
|
425
|
+
usePersistForm({ formName, formContext });
|
|
576
426
|
return jsx(Fragment$1, { children: children });
|
|
427
|
+
});
|
|
428
|
+
PersistForm.displayName = "PersistForm";
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* A component that combines a Material UI Paper with a form container
|
|
432
|
+
*
|
|
433
|
+
* PaperForm wraps form content in an elevated paper surface, providing
|
|
434
|
+
* visual distinction and a clean container for form elements. It integrates
|
|
435
|
+
* seamlessly with react-hook-form by using FormContainer internally.
|
|
436
|
+
*
|
|
437
|
+
* This component is useful for creating standalone form surfaces outside of dialogs,
|
|
438
|
+
* such as on pages or within cards. It supports all Paper props for styling
|
|
439
|
+
* while handling form state management through FormContainer.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* // Basic usage
|
|
443
|
+
* <PaperForm
|
|
444
|
+
* formProps={{
|
|
445
|
+
* defaultValues: { name: "", email: "" },
|
|
446
|
+
* onSuccess: handleSubmit
|
|
447
|
+
* }}
|
|
448
|
+
* elevation={3}
|
|
449
|
+
* >
|
|
450
|
+
* <TextFieldElement name="name" label="Name" />
|
|
451
|
+
* <TextFieldElement name="email" label="Email" />
|
|
452
|
+
* <Button type="submit">Submit</Button>
|
|
453
|
+
* </PaperForm>
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* // With custom styling and validation
|
|
457
|
+
* <PaperForm
|
|
458
|
+
* formProps={{
|
|
459
|
+
* defaultValues: formDefaultValues,
|
|
460
|
+
* onSuccess: handleSubmit,
|
|
461
|
+
* onError: handleError,
|
|
462
|
+
* }}
|
|
463
|
+
* sx={{
|
|
464
|
+
* padding: 3,
|
|
465
|
+
* borderRadius: 2,
|
|
466
|
+
* backgroundColor: 'background.paper',
|
|
467
|
+
* }}
|
|
468
|
+
* elevation={2}
|
|
469
|
+
* >
|
|
470
|
+
* <FormFields />
|
|
471
|
+
* <FormDialogActions submitProps={{ children: "Save Profile" }} />
|
|
472
|
+
* </PaperForm>
|
|
473
|
+
*
|
|
474
|
+
* @template T - The type of form values being handled
|
|
475
|
+
*/
|
|
476
|
+
const PaperForm = ({ children, persistKey = "", formProps, ...paperProps }) => (jsx(Paper, { ...paperProps, children: jsx(FormContainer, { ...formProps, children: jsx(PersistForm, { formName: persistKey, children: children }) }) }));
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Context provider component for form dialog state management
|
|
480
|
+
*
|
|
481
|
+
* FormDialogProvider creates and manages shared state for form dialogs,
|
|
482
|
+
* allowing child components to access dialog state and controls through
|
|
483
|
+
* the useFormDialog hook.
|
|
484
|
+
*
|
|
485
|
+
* This provider handles:
|
|
486
|
+
* - Dialog open/close state
|
|
487
|
+
* - Form-wide disabled state
|
|
488
|
+
* - Dialog close handler passing
|
|
489
|
+
* - Theme consistency via ThemeBridge
|
|
490
|
+
*
|
|
491
|
+
* Use this provider to wrap form components that need to share state
|
|
492
|
+
* and disabled status, such as dialogs with multiple form fields
|
|
493
|
+
* and action buttons that need to be coordinated.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* // Basic usage wrapping a form
|
|
497
|
+
* <FormDialogProvider>
|
|
498
|
+
* <FormContainer onSuccess={handleSubmit}>
|
|
499
|
+
* <TextFieldElement name="name" />
|
|
500
|
+
* <FormDialogActions />
|
|
501
|
+
* </FormContainer>
|
|
502
|
+
* </FormDialogProvider>
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* // With pre-configured dialog state
|
|
506
|
+
* <FormDialogProvider open={isOpen} closeDialog={handleClose}>
|
|
507
|
+
* <PaperForm formProps={formProps}>
|
|
508
|
+
* <LoginFormFields />
|
|
509
|
+
* <FormDialogActions submitProps={{ maxAttempts: 3 }} />
|
|
510
|
+
* </PaperForm>
|
|
511
|
+
* </FormDialogProvider>
|
|
512
|
+
*/
|
|
513
|
+
const FormDialogProvider = ({ children, ...value }) => {
|
|
514
|
+
const [disabled, setDisabled] = useState(false);
|
|
515
|
+
return (jsx(ThemeBridge, { children: jsx(FormDialogContext.Provider, { value: { ...value, disabled, setDisabled }, children: children }) }));
|
|
577
516
|
};
|
|
578
517
|
|
|
518
|
+
/**
|
|
519
|
+
* A dialog component specialized for forms with integrated context providers
|
|
520
|
+
*
|
|
521
|
+
* FormDialog combines Material UI dialog functionality with React Hook Form,
|
|
522
|
+
* creating a comprehensive solution for modal forms. It provides context
|
|
523
|
+
* providers for form state management, dialog controls, and accessibility.
|
|
524
|
+
*
|
|
525
|
+
* Key features:
|
|
526
|
+
* - Integrates React Hook Form with Material UI dialogs
|
|
527
|
+
* - Provides form state management through FormProvider
|
|
528
|
+
* - Establishes dialog context through FormDialogProvider
|
|
529
|
+
* - Handles form submission and error states
|
|
530
|
+
* - Supports loading states for form operations
|
|
531
|
+
* - Maintains consistent styling and behavior
|
|
532
|
+
*
|
|
533
|
+
* Child components can access the form and dialog context through hooks:
|
|
534
|
+
* - useFormContext() - Access form methods and state
|
|
535
|
+
* - useFormState() - Access form validation state
|
|
536
|
+
* - useFormDialog() - Access dialog controls and state
|
|
537
|
+
*
|
|
538
|
+
*/
|
|
539
|
+
const FormDialog = function ({ formProps, children, open, onClose, persistKey = "", ...dialogProps }) {
|
|
540
|
+
const PaperComponent = useCallback((props) => jsx(PaperForm, { persistKey: persistKey, formProps: formProps, ...props }), [formProps, persistKey]);
|
|
541
|
+
const baseDialogProps = useMemo(() => merge({
|
|
542
|
+
actionsProps: { sx: { pt: 2.5 } },
|
|
543
|
+
contentProps: {
|
|
544
|
+
sx: {
|
|
545
|
+
display: "flex",
|
|
546
|
+
justifyContent: "center",
|
|
547
|
+
boxSizing: "border-box",
|
|
548
|
+
maxHeight: `calc(100vh - 235px)`, // 235 estimate using H4 title & default FormDialogActions
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
}, dialogProps), [dialogProps]);
|
|
552
|
+
return (jsx(FormDialogProvider, { open: open, closeDialog: onClose, children: jsx(BaseDialog, { PaperComponent: PaperComponent, open: open, onClose: onClose, closeButton: jsx(IconButton, { sx: {
|
|
553
|
+
position: "absolute",
|
|
554
|
+
top: 8,
|
|
555
|
+
right: 8,
|
|
556
|
+
}, onClick: (e) => onClose === null || onClose === void 0 ? void 0 : onClose(e, 'escapeKeyDown'), children: jsx(Close, { fontSize: "small", "aria-label": "close-form-dialog" }) }), ...baseDialogProps, children: children }) }));
|
|
557
|
+
};
|
|
558
|
+
FormDialog.displayName = "FormDialog";
|
|
559
|
+
|
|
579
560
|
/**
|
|
580
561
|
* A cancel button component that integrates with FormDialogContext.
|
|
581
562
|
* Automatically closes the parent dialog when clicked.
|
|
@@ -644,20 +625,20 @@ FormCancelButton.displayName = "FormCancelButton";
|
|
|
644
625
|
* Process Payment
|
|
645
626
|
* </LoadingButton>
|
|
646
627
|
*/
|
|
647
|
-
const LoadingButton = ({ children, loading, altIcon, ...props }) => {
|
|
628
|
+
const LoadingButton = ({ children, loading = false, altIcon, ...props }) => {
|
|
648
629
|
const theme = useTheme$1();
|
|
649
|
-
return (jsxs(Button, { ...props, children: [!loading ? (jsx(Box, { sx: {
|
|
630
|
+
return (jsxs(Button, { ...props, children: [!loading ? altIcon : (jsx(Box, { sx: {
|
|
650
631
|
display: "flex",
|
|
651
632
|
alignItems: "center",
|
|
652
633
|
justifyContent: "center",
|
|
653
634
|
width: "100%",
|
|
654
635
|
height: "auto",
|
|
655
636
|
mr: 1,
|
|
656
|
-
}, children: jsx(CircularProgress, { color: "inherit", size: theme.typography.fontSize }) }))
|
|
637
|
+
}, children: jsx(CircularProgress, { color: "inherit", size: theme.typography.fontSize }) })), children] }));
|
|
657
638
|
};
|
|
658
639
|
|
|
659
640
|
/**
|
|
660
|
-
* A submit button for forms with loading state, attempt tracking and form context awareness
|
|
641
|
+
* A "submit button" for forms with loading state, attempt tracking and form context awareness
|
|
661
642
|
*
|
|
662
643
|
* This component extends the LoadingButton with form-specific features:
|
|
663
644
|
* - Automatically displays loading state during form submission
|
|
@@ -671,9 +652,6 @@ const LoadingButton = ({ children, loading, altIcon, ...props }) => {
|
|
|
671
652
|
* - Explicit-disabled prop
|
|
672
653
|
* - Form-wide disabled state from FormDialogContext
|
|
673
654
|
*
|
|
674
|
-
* @example
|
|
675
|
-
* // Basic usage
|
|
676
|
-
* <FormSubmitButton />
|
|
677
655
|
*
|
|
678
656
|
* @example
|
|
679
657
|
* // With custom text and max attempts
|
|
@@ -692,11 +670,11 @@ const LoadingButton = ({ children, loading, altIcon, ...props }) => {
|
|
|
692
670
|
* </FormSubmitButton>
|
|
693
671
|
*/
|
|
694
672
|
const FormSubmitButton = memo(function ({ showAttempts, maxAttempts, children = "Save", ...props }) {
|
|
695
|
-
const
|
|
673
|
+
const formState = useFormState();
|
|
696
674
|
const { disabled: disabledForm } = useFormDialog();
|
|
697
675
|
const disabled = formState.isSubmitting || formState.isLoading || props.disabled || disabledForm;
|
|
698
676
|
const hasMaxAttempts = maxAttempts && isFinite(maxAttempts);
|
|
699
|
-
return (jsx(LoadingButton, { loading:
|
|
677
|
+
return (jsx(LoadingButton, { loading: formState.isSubmitting || formState.isLoading, variant: "contained", type: "submit", size: "large", disabled: disabled, ...props, children: jsx(Badge, { badgeContent: formState.submitCount || (formState.disabled ? maxAttempts : undefined), color: disabled ? "error" : "warning", invisible: !hasMaxAttempts && !showAttempts, children: children }) }));
|
|
700
678
|
});
|
|
701
679
|
FormSubmitButton.displayName = "FormSubmitButton";
|
|
702
680
|
|
|
@@ -717,10 +695,6 @@ FormSubmitButton.displayName = "FormSubmitButton";
|
|
|
717
695
|
* - Form-wide disabled state from FormDialogContext
|
|
718
696
|
*
|
|
719
697
|
* @example
|
|
720
|
-
* // Basic usage
|
|
721
|
-
* <FormResetButton />
|
|
722
|
-
*
|
|
723
|
-
* @example
|
|
724
698
|
* // With persistence integration
|
|
725
699
|
* <FormResetButton formKey="user-profile-form" />
|
|
726
700
|
*
|
|
@@ -742,7 +716,6 @@ const FormResetButton = memo(function (props) {
|
|
|
742
716
|
const { reset } = useFormContext();
|
|
743
717
|
const { isSubmitting, isLoading, isDirty } = useFormState();
|
|
744
718
|
const { disabled: disabledForm } = useFormDialog();
|
|
745
|
-
useParentTheme();
|
|
746
719
|
const keepSubmitCount = !!(props === null || props === void 0 ? void 0 : props.keepCount);
|
|
747
720
|
const handleOnClick = useCallback(() => {
|
|
748
721
|
reset(undefined, { keepSubmitCount, keepIsSubmitted: keepSubmitCount });
|
|
@@ -804,7 +777,7 @@ FormResetButton.displayName = "FormResetButton";
|
|
|
804
777
|
* </Grid>
|
|
805
778
|
* </Grid>
|
|
806
779
|
*/
|
|
807
|
-
const GridSpacer = () => jsx(Grid, { flex: "1 0 0" });
|
|
780
|
+
const GridSpacer = () => jsx(Grid, { flex: "1 0 0", sx: { w: 0 } });
|
|
808
781
|
|
|
809
782
|
/**
|
|
810
783
|
* Standard set of form dialog action buttons with consistent styling and behavior
|
|
@@ -888,5 +861,5 @@ const AutoGrid = ({ components, columnCount = 1, ...props }) => (jsx(Grid, { con
|
|
|
888
861
|
xs: 12 / columnCount,
|
|
889
862
|
}, children: child }, child.key))) }));
|
|
890
863
|
|
|
891
|
-
export { AutoGrid, BaseDialog, BlackoutDialog, FormCancelButton, FormDialog, FormDialogActions, FormDialogProvider, FormResetButton, FormSubmitButton, GridSpacer, PaperForm, PersistForm,
|
|
864
|
+
export { AutoGrid, BaseDialog, BlackoutDialog, FormCancelButton, FormDialog, FormDialogActions, FormDialogProvider, FormResetButton, FormSubmitButton, GridSpacer, LoadingButton, PaperForm, PersistForm, useDialog, useFormDialog, usePersistForm };
|
|
892
865
|
//# sourceMappingURL=index.js.map
|