@chris-c-brine/form-dialog 1.1.8 → 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/types.d.ts +7 -0
- package/package.json +2 -2
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
|
package/dist/types.d.ts
CHANGED
|
@@ -57,6 +57,13 @@ export type LoadingButtonProps = ButtonProps & {
|
|
|
57
57
|
* When false, displays a loading spinner; when true or undefined, displays normal content
|
|
58
58
|
*/
|
|
59
59
|
loading?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Properties to configure the appearance and behavior of a circular progress indicator.
|
|
62
|
+
* This variable allows customization of a loading icon using the attributes defined in `CircularProgressProps`.
|
|
63
|
+
* It can be used to control the size, color, and additional settings of the circular progress component.
|
|
64
|
+
*
|
|
65
|
+
* @type {CircularProgressProps | undefined}
|
|
66
|
+
*/
|
|
60
67
|
loadingIconProps?: CircularProgressProps;
|
|
61
68
|
/**
|
|
62
69
|
* Optional icon to display when the button is not in loading state
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chris-c-brine/form-dialog",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Easy Form Dialogs with
|
|
3
|
+
"version": "1.1.9",
|
|
4
|
+
"description": "Easy MUI Form Dialogs with react-hook-form!",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|