@chris-c-brine/form-dialog 1.1.7 → 1.1.9
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 +66 -289
- package/dist/components/buttons/LoadingButton.d.ts +1 -1
- package/dist/components/dialogs/FormDialog.d.ts +1 -1
- package/dist/components/forms/PaperForm.d.ts +1 -1
- package/dist/components/index.d.ts +0 -1
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/useMaxAttempts.d.ts +11 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +154 -343
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +154 -343
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +33 -43
- package/dist/utils/applyDefaultFormDialogProps.d.ts +5 -2
- package/dist/utils/index.d.ts +0 -1
- package/package.json +11 -14
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
[](https://www.npmjs.com/package/@chris-c-brine/form-dialog)
|
|
3
3
|
[](https://github.com/Chris-C-Brine/form-dialog/blob/main/LICENSE)
|
|
4
4
|
|
|
5
|
-
Easy Form Dialogs
|
|
5
|
+
Easy Form Dialogs!
|
|
6
6
|
|
|
7
|
-
A React component library that seamlessly integrates Material UI dialogs with React Hook Form, providing
|
|
7
|
+
A React component library that seamlessly integrates Material UI dialogs with React Hook Form, providing context-aware components and simplified dialog management.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -14,321 +14,98 @@ A React component library that seamlessly integrates Material UI dialogs with Re
|
|
|
14
14
|
|
|
15
15
|
## Dependencies
|
|
16
16
|
This package has the following peer dependencies that need to be installed in your project:
|
|
17
|
-
- @emotion/react: ^11.
|
|
18
|
-
- @emotion/styled: ^11.
|
|
19
|
-
- @mui/icons-material: ^7.
|
|
20
|
-
- @mui/material: ^7.
|
|
21
|
-
- react: ^19.
|
|
22
|
-
- react-dom: ^19.
|
|
23
|
-
- react-hook-form: ^7.
|
|
24
|
-
- react-hook-form-mui: ^7.
|
|
25
|
-
- zustand: ^5.0.4
|
|
17
|
+
- @emotion/react: ^11.0.0
|
|
18
|
+
- @emotion/styled: ^11.0.0
|
|
19
|
+
- @mui/icons-material: ^7.0.0
|
|
20
|
+
- @mui/material: ^7.0.0
|
|
21
|
+
- react: ^19.0.0
|
|
22
|
+
- react-dom: ^19.0.0
|
|
23
|
+
- react-hook-form: ^7.0.0
|
|
24
|
+
- react-hook-form-mui: ^7.0.0 || ^8.0.0
|
|
26
25
|
|
|
27
26
|
## Features
|
|
28
27
|
|
|
29
28
|
- **Integrated Form Dialogs**: Combines Material UI dialogs with React Hook Form
|
|
30
|
-
- **Persistable State**: Store form data across page refreshes using Zustand
|
|
31
29
|
- **Simplified Workflow**: Streamlined API for common form dialog patterns
|
|
32
30
|
- **Context-Aware Components**: Dialog components that share form state
|
|
33
31
|
- **TypeScript Support**: Fully typed for a better developer experience
|
|
34
32
|
- **Customizable UI**: Extends Material UI components
|
|
35
33
|
|
|
36
|
-
##
|
|
34
|
+
## Usage Example
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
### Basic Form Dialog
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
This example shows how to create a simple login dialog using `FormDialog` and `useDialog`.
|
|
41
39
|
|
|
42
40
|
```tsx
|
|
43
|
-
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
import {
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
import { Button, TextField } from "@mui/material";
|
|
42
|
+
import {
|
|
43
|
+
FormDialog,
|
|
44
|
+
FormDialogActions,
|
|
45
|
+
useDialog
|
|
46
|
+
} from "@chris-c-brine/form-dialog";
|
|
47
|
+
import { TextFieldElement, type SubmitHandler } from "react-hook-form-mui";
|
|
48
|
+
|
|
49
|
+
interface LoginFormValues {
|
|
50
|
+
username: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const SimpleLoginExample = () => {
|
|
52
54
|
const { dialogProps, openDialog } = useDialog();
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
sx={{
|
|
59
|
-
display: "flex",
|
|
60
|
-
alignItems: "center",
|
|
61
|
-
justifyContent: "center",
|
|
62
|
-
height: "85vh",
|
|
63
|
-
overflow: "hidden",
|
|
64
|
-
animation: "fadeIn 1s ease-in-out",
|
|
65
|
-
"@keyframes fadeIn": {
|
|
66
|
-
from: { opacity: 0 },
|
|
67
|
-
to: { opacity: 1 },
|
|
68
|
-
},
|
|
69
|
-
}}
|
|
70
|
-
>
|
|
71
|
-
<LoginForm>
|
|
72
|
-
<IconButton color="primary" onClick={openDialog} sx={{ py: 1 }}>
|
|
73
|
-
<LockIcon sx={{ fontSize: 50 }} />
|
|
74
|
-
</IconButton>
|
|
75
|
-
</LoginForm>
|
|
76
|
-
<LoginDialog dialogProps={dialogProps} />
|
|
77
|
-
</Container>
|
|
78
|
-
);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export default LoginPage;
|
|
82
|
-
```
|
|
83
|
-
```tsx
|
|
84
|
-
// LoginPageConstants.ts
|
|
85
|
-
import type { SubmitHandler } from "react-hook-form";
|
|
86
|
-
|
|
87
|
-
export const defaultLoginFormValues = { username: "", password: "" };
|
|
88
|
-
export type LoginFormValues = typeof defaultLoginFormValues;
|
|
89
|
-
```
|
|
90
|
-
```tsx
|
|
91
|
-
// LoginFormBase.tsx
|
|
92
|
-
import { TextFieldElement, PasswordElement, type PasswordElementProps, useFormContext} from "react-hook-form-mui";
|
|
93
|
-
import { memo, useEffect } from "react";
|
|
94
|
-
import { useFormDialog } from "@chris-c-brine/form-dialog";
|
|
95
|
-
import { AutoGrid, type AutoGridProps } from "@chris-c-brine/autogrid";
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Login Form
|
|
99
|
-
*/
|
|
100
|
-
export type NameProp = {
|
|
101
|
-
name?: string;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export type LoginFormProps = NameProp & Pick<AutoGridProps, "columnCount">;
|
|
105
|
-
const LoginFormBase = memo(function ({ name, columnCount = 1 }: LoginFormProps) {
|
|
106
|
-
const { disabled } = useFormDialog();
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<AutoGrid
|
|
110
|
-
columnCount={columnCount}
|
|
111
|
-
columnSpacing={2}
|
|
112
|
-
rowSpacing={1}
|
|
113
|
-
components={[
|
|
114
|
-
<UserName key={`${name}-username`} disabled={disabled} />,
|
|
115
|
-
<Password key={`${name}-password`} disabled={disabled} />,
|
|
116
|
-
]}
|
|
117
|
-
/>
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
LoginFormBase.displayName = "LoginForm";
|
|
122
|
-
|
|
123
|
-
export default LoginFormBase;
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Inputs
|
|
127
|
-
*/
|
|
128
|
-
const UserName = ({ disabled }: Pick<PasswordElementProps, "disabled">) => (
|
|
129
|
-
<TextFieldElement
|
|
130
|
-
name="username"
|
|
131
|
-
label="Username"
|
|
132
|
-
required
|
|
133
|
-
autoFocus
|
|
134
|
-
autoComplete="off"
|
|
135
|
-
fullWidth
|
|
136
|
-
margin={"dense"}
|
|
137
|
-
size={"medium"}
|
|
138
|
-
disabled={disabled}
|
|
139
|
-
slotProps={{
|
|
140
|
-
inputLabel: { shrink: true },
|
|
141
|
-
}}
|
|
142
|
-
/>
|
|
143
|
-
);
|
|
144
|
-
UserName.displayName = "UserName";
|
|
145
|
-
|
|
146
|
-
const Password = ({ disabled }: Pick<PasswordElementProps, "disabled">) => {
|
|
147
|
-
const { setValue, getValues } = useFormContext();
|
|
148
|
-
const password = getValues("password");
|
|
149
|
-
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
if (disabled && password !== "") {
|
|
152
|
-
setValue("disabledPassword", "");
|
|
153
|
-
}
|
|
154
|
-
}, [disabled, setValue, password]);
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<PasswordElement
|
|
158
|
-
name={"password"}
|
|
159
|
-
label={"Password"}
|
|
160
|
-
fullWidth
|
|
161
|
-
required
|
|
162
|
-
autoComplete="off"
|
|
163
|
-
size="medium"
|
|
164
|
-
margin={"dense"}
|
|
165
|
-
disabled={disabled}
|
|
166
|
-
slotProps={{
|
|
167
|
-
inputLabel: { shrink: true },
|
|
168
|
-
}}
|
|
169
|
-
{...(disabled && { renderIcon: () => <></> })}
|
|
170
|
-
/>
|
|
171
|
-
);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
Password.displayName = "Password";
|
|
175
|
-
```
|
|
176
|
-
```tsx
|
|
177
|
-
// LoginForm.tsx
|
|
178
|
-
import { Lock as LockIcon } from "@mui/icons-material";
|
|
179
|
-
import { Box, Typography, type TypographyProps } from "@mui/material";
|
|
180
|
-
import { merge } from "lodash";
|
|
181
|
-
import { FC, PropsWithChildren, useCallback, useMemo } from "react";
|
|
182
|
-
import { FormDialogActions, FormDialogProvider, PaperForm } from "@chris-c-brine/form-dialog";
|
|
183
|
-
import { SubmitHandler, useForm } from "react-hook-form-mui";
|
|
184
|
-
import { defaultLoginFormValues, LoginFormValues } from "./LoginPageConstants";
|
|
185
|
-
import LoginFormBase from "./LoginFormBase";
|
|
186
|
-
import { globalErrorAtom, useUser } from "@features";
|
|
187
|
-
import { useSetAtom } from "jotai/index";
|
|
188
|
-
import { Person as PersonIcon } from "@mui/icons-material";
|
|
189
|
-
|
|
190
|
-
const AltIcon = () => <LockIcon sx={{ mr: 1, fontSize: 20 }} />;
|
|
191
|
-
|
|
192
|
-
export const LoginForm: FC<PropsWithChildren> = ({ children }) => {
|
|
56
|
+
const onSubmit: SubmitHandler<LoginFormValues> = (data) => {
|
|
57
|
+
console.log("Form Data:", data);
|
|
58
|
+
dialogProps.onClose();
|
|
59
|
+
};
|
|
193
60
|
|
|
194
61
|
return (
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
altIcon: <AltIcon />,
|
|
206
|
-
children: "Log In",
|
|
207
|
-
maxAttempts: 5,
|
|
208
|
-
}}
|
|
209
|
-
/>
|
|
210
|
-
</LoginPaperForm>
|
|
211
|
-
);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const SecureLoginText: FC<TypographyProps> = (props) => {
|
|
215
|
-
const typographyProps = merge(
|
|
216
|
-
{
|
|
217
|
-
component: "h1",
|
|
218
|
-
variant: "h5",
|
|
219
|
-
sx: { marginBottom: "20px" },
|
|
220
|
-
},
|
|
221
|
-
props,
|
|
222
|
-
);
|
|
223
|
-
return <Typography {...typographyProps}>Secure Login</Typography>;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
const LoginPaperForm: FC<PropsWithChildren> = ({ children }) => {
|
|
227
|
-
const formContext = useForm({defaultValues: defaultLoginFormValues});
|
|
228
|
-
const { setUser } = useUser();
|
|
229
|
-
const setError = useSetAtom(globalErrorAtom);
|
|
230
|
-
const reset = useMemo(() => formContext.reset, [formContext?.reset]);
|
|
231
|
-
|
|
232
|
-
const onSuccess: SubmitHandler<LoginFormValues> = useCallback(
|
|
233
|
-
(data, event) => {
|
|
234
|
-
event?.preventDefault(); // Stop default html form submit
|
|
235
|
-
event?.stopPropagation(); // STOP!!!!!
|
|
236
|
-
|
|
237
|
-
setUser({ name: data.username, isActive: true }); // Update User (and/or other business logic)
|
|
238
|
-
reset(); // reset form
|
|
239
|
-
setError({ // Signal Success!
|
|
240
|
-
message: <>Hello {data.username}!</>,
|
|
241
|
-
title: "Successful Login!",
|
|
242
|
-
severity: "success",
|
|
243
|
-
icon: <PersonIcon sx={{ fontSize: 35 }} />,
|
|
244
|
-
});
|
|
245
|
-
}, [setUser, setError, reset]);
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<FormDialogProvider>
|
|
249
|
-
<PaperForm
|
|
250
|
-
persistKey={'login-page-form'}
|
|
251
|
-
formProps={{
|
|
252
|
-
formContext,
|
|
253
|
-
onSuccess,
|
|
254
|
-
onError: (errors, event) => {
|
|
255
|
-
event?.preventDefault();
|
|
256
|
-
console.log(errors)
|
|
257
|
-
},
|
|
258
|
-
}}
|
|
259
|
-
elevation={3}
|
|
260
|
-
sx={{
|
|
261
|
-
padding: "20px",
|
|
262
|
-
marginTop: "50px",
|
|
263
|
-
textAlign: "center",
|
|
264
|
-
}}
|
|
62
|
+
<>
|
|
63
|
+
<Button variant="contained" onClick={openDialog}>
|
|
64
|
+
Open Login Dialog
|
|
65
|
+
</Button>
|
|
66
|
+
|
|
67
|
+
<FormDialog
|
|
68
|
+
{...dialogProps}
|
|
69
|
+
title="Login"
|
|
70
|
+
formProps={{ onSuccess: onSubmit }}
|
|
71
|
+
actions={<FormDialogActions />}
|
|
265
72
|
>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
73
|
+
<TextFieldElement
|
|
74
|
+
name="username"
|
|
75
|
+
label="Username"
|
|
76
|
+
required
|
|
77
|
+
fullWidth
|
|
78
|
+
margin="dense"
|
|
79
|
+
/>
|
|
80
|
+
</FormDialog>
|
|
81
|
+
</>
|
|
269
82
|
);
|
|
270
83
|
};
|
|
271
84
|
```
|
|
272
|
-
```tsx
|
|
273
|
-
// LoginDialog.tsx
|
|
274
|
-
import { useDialog, FormDialog, FormDialogActions } from "@chris-c-brine/form-dialog";
|
|
275
|
-
import { memo, useCallback, useMemo } from "react";
|
|
276
|
-
import LoginFormBase from "./LoginFormBase";
|
|
277
|
-
import { defaultLoginFormValues, LoginFormValues } from "./LoginPageConstants";
|
|
278
|
-
import { SubmitHandler, useForm } from "react-hook-form-mui";
|
|
279
|
-
import { globalErrorAtom, useUser } from "@features";
|
|
280
|
-
import { useSetAtom } from "jotai/index";
|
|
281
|
-
import { Person as PersonIcon } from "@mui/icons-material";
|
|
282
|
-
|
|
283
|
-
const formKey = "dialog-login-form";
|
|
284
|
-
|
|
285
|
-
export type LoginDialogProps = {
|
|
286
|
-
dialogProps: ReturnType<typeof useDialog>["dialogProps"];
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
export const LoginDialog = memo(function ({ dialogProps }: LoginDialogProps) {
|
|
290
|
-
const setError = useSetAtom(globalErrorAtom);
|
|
291
|
-
const { setUser } = useUser();
|
|
292
|
-
const formContext = useForm({ defaultValues: defaultLoginFormValues });
|
|
293
85
|
|
|
294
|
-
|
|
86
|
+
### Standalone Paper Form
|
|
295
87
|
|
|
296
|
-
|
|
297
|
-
(data, event) => {
|
|
298
|
-
event?.preventDefault();
|
|
299
|
-
event?.stopPropagation();
|
|
88
|
+
You can also use `PaperForm` for forms that aren't in a dialog but still want the same consistent styling and integrated form handling.
|
|
300
89
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
setError({
|
|
305
|
-
// Signal Success!
|
|
306
|
-
message: <>Hello {data.username}!</>,
|
|
307
|
-
title: "Successful Login!",
|
|
308
|
-
severity: "success",
|
|
309
|
-
icon: <PersonIcon sx={{ fontSize: 35 }} />,
|
|
310
|
-
});
|
|
311
|
-
dialogProps?.onClose();
|
|
312
|
-
},
|
|
313
|
-
[setUser, setError, dialogProps, reset],
|
|
314
|
-
);
|
|
90
|
+
```tsx
|
|
91
|
+
import { PaperForm, FormDialogActions } from "@chris-c-brine/form-dialog";
|
|
92
|
+
import { TextFieldElement } from "react-hook-form-mui";
|
|
315
93
|
|
|
94
|
+
const StandaloneForm = () => {
|
|
316
95
|
return (
|
|
317
|
-
<
|
|
318
|
-
{
|
|
319
|
-
|
|
320
|
-
formProps={{
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
actions={<FormDialogActions resetProps={{ formKey }} submitProps={{ maxAttempts: 3 }} />}
|
|
96
|
+
<PaperForm
|
|
97
|
+
elevation={3}
|
|
98
|
+
sx={{ p: 4, maxWidth: 400, mx: "auto" }}
|
|
99
|
+
formProps={{
|
|
100
|
+
onSuccess: (data) => console.log(data),
|
|
101
|
+
}}
|
|
324
102
|
>
|
|
325
|
-
<
|
|
326
|
-
|
|
103
|
+
<h3>Account Settings</h3>
|
|
104
|
+
<TextFieldElement name="displayName" label="Display Name" fullWidth />
|
|
105
|
+
<FormDialogActions removeCancelButton />
|
|
106
|
+
</PaperForm>
|
|
327
107
|
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
LoginDialog.displayName = "LoginDialog";
|
|
331
|
-
|
|
108
|
+
};
|
|
332
109
|
```
|
|
333
110
|
|
|
334
111
|
## License
|
|
@@ -38,4 +38,4 @@ import { LoadingButtonProps } from "../../types";
|
|
|
38
38
|
* Process Payment
|
|
39
39
|
* </LoadingButton>
|
|
40
40
|
*/
|
|
41
|
-
export declare const LoadingButton: ({ children, loading, altIcon, ...props }: LoadingButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
export declare const LoadingButton: ({ children, loading, altIcon, loadingIconProps, ...props }: LoadingButtonProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -22,6 +22,6 @@ import { FormDialogProps } from "../../types";
|
|
|
22
22
|
*
|
|
23
23
|
*/
|
|
24
24
|
export declare const FormDialog: {
|
|
25
|
-
<T extends FieldValues>({ formProps, children, open, onClose,
|
|
25
|
+
<T extends FieldValues>({ formProps, children, open, onClose, ...dialogProps }: FormDialogProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
26
26
|
displayName: string;
|
|
27
27
|
};
|
|
@@ -46,4 +46,4 @@ import { PaperFormProps } from "../../types";
|
|
|
46
46
|
*
|
|
47
47
|
* @template T - The type of form values being handled
|
|
48
48
|
*/
|
|
49
|
-
export declare const PaperForm: <T extends FieldValues>({ children,
|
|
49
|
+
export declare const PaperForm: <T extends FieldValues>({ children, formProps, ...paperProps }: PaperFormProps<T>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,7 +2,6 @@ export * from "./dialogs/FormDialog";
|
|
|
2
2
|
export * from "./dialogs/BaseDialog";
|
|
3
3
|
export * from "./dialogs/BlackoutDialog";
|
|
4
4
|
export * from "./forms/PaperForm";
|
|
5
|
-
export * from "./forms/PersistForm";
|
|
6
5
|
export * from "./buttons/FormCancelButton";
|
|
7
6
|
export * from "./buttons/FormSubmitButton";
|
|
8
7
|
export * from "./buttons/FormResetButton";
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import { UseMaxAttemptProps } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Hook that monitors form submission attempts and disables the form when a limit is reached.
|
|
4
|
+
*
|
|
5
|
+
* It performs two main functions:
|
|
6
|
+
* 1. Monitors `formState.submitCount` and calls `setDisabled(true)` from the dialog context
|
|
7
|
+
* once it reaches or exceeds the specified `maxAttempts`.
|
|
8
|
+
* 2. When the dialog context enters a `disabled` state, it automatically resets form fields
|
|
9
|
+
* that have errors or are dirty to ensure a clean state for the user.
|
|
10
|
+
*
|
|
11
|
+
* @param props - UseMaxAttemptProps
|
|
12
|
+
*/
|
|
2
13
|
export declare const useMaxAttempts: ({ maxAttempts }: UseMaxAttemptProps) => void;
|
package/dist/index.d.ts
CHANGED