@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 CHANGED
@@ -2,9 +2,9 @@
2
2
  [![npm version](https://img.shields.io/npm/v/@chris-c-brine/form-dialog.svg)](https://www.npmjs.com/package/@chris-c-brine/form-dialog)
3
3
  [![License: AAL](https://img.shields.io/badge/License-AAL-blue.svg)](https://github.com/Chris-C-Brine/form-dialog/blob/main/LICENSE)
4
4
 
5
- Easy Form Dialogs with a persistable state!
5
+ Easy Form Dialogs!
6
6
 
7
- A React component library that seamlessly integrates Material UI dialogs with React Hook Form, providing persistable form state, context-aware components, and simplified dialog management.
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.14.0
18
- - @emotion/styled: ^11.14.0
19
- - @mui/icons-material: ^7.1.0
20
- - @mui/material: ^7.1.0
21
- - react: ^19.1.0
22
- - react-dom: ^19.1.0
23
- - react-hook-form: ^7.56.2
24
- - react-hook-form-mui: ^7.6.0
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
- ## Known Issues
34
+ ## Usage Example
37
35
 
38
- - **Max Attempts & Persistence**: PersistForm does not yet support Max attempts
36
+ ### Basic Form Dialog
39
37
 
40
- ## Login Form Usage Example
38
+ This example shows how to create a simple login dialog using `FormDialog` and `useDialog`.
41
39
 
42
40
  ```tsx
43
- // LoginPage.tsx
44
- import { type FC } from "react";
45
- import { Container, IconButton } from "@mui/material";
46
- import { useDialog } from "@chris-c-brine/form-dialog";
47
- import LoginForm from "./components/forms/LoginForm";
48
- import { Lock as LockIcon } from "@mui/icons-material";
49
- import { LoginDialog } from "./LoginDialog";
50
-
51
- const LoginPage: FC = () => {
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
- return (
55
- <Container
56
- component="main"
57
- maxWidth={"xs"}
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
- <LoginPaperForm>
196
- <Box px={2}>
197
- {children}
198
- <SecureLoginText />
199
- <LoginFormBase name="page-form" columnCount={1} />
200
- </Box>
201
- <FormDialogActions
202
- removeCancelButton={true}
203
- gridProps={{ mt:3, mb:1,px:2 }}
204
- submitProps={{
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
- {children}
267
- </PaperForm>
268
- </FormDialogProvider>
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
- const reset = useMemo(() => formContext.reset, [formContext?.reset]);
86
+ ### Standalone Paper Form
295
87
 
296
- const onSuccess: SubmitHandler<LoginFormValues> = useCallback(
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
- console.log(event);
302
- setUser({ name: data.username, isActive: true }); // Update User (and/or other business logic)
303
- reset(); // reset form
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
- <FormDialog
318
- {...dialogProps}
319
- persistKey={formKey}
320
- formProps={{ onSuccess, formContext }}
321
- title={"Basic Persist Form Dialog Test"}
322
- titleProps={{ variant: "h5", textAlign: "center" }}
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
- <LoginFormBase columnCount={2} />
326
- </FormDialog>
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, persistKey, ...dialogProps }: FormDialogProps<T>): import("react/jsx-runtime").JSX.Element;
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, persistKey, formProps, ...paperProps }: PaperFormProps<T>) => import("react/jsx-runtime").JSX.Element;
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";
@@ -1,5 +1,3 @@
1
1
  export * from "./useFormDialog";
2
2
  export * from "./useMaxAttempts";
3
- export * from "./usePersistForm";
4
- export * from "./useOnMount";
5
3
  export * from "./useDialog";
@@ -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
@@ -1,4 +1,4 @@
1
1
  export * from "./components";
2
- export { usePersistForm, useFormDialog, useDialog } from "./hooks";
2
+ export { useFormDialog, useDialog } from "./hooks";
3
3
  export { FormDialogProvider } from "./state/FormDialogProvider";
4
4
  export * from "./types";