@saas-ui/modals 2.0.0-next.9 → 2.0.0-rc.23

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.
@@ -6,13 +6,9 @@ import {
6
6
  useModals,
7
7
  } from './provider'
8
8
 
9
- interface CreateModalOptions {}
10
-
11
- const createModal = (Component: React.FC<any>, options: CreateModalOptions) => {
12
- return Component
13
- }
14
-
15
- export interface CreateModalsOptions<TModalDefs> {
9
+ export interface CreateModalsOptions<
10
+ TModalDefs extends Record<string, React.FC<any>>
11
+ > {
16
12
  modals: TModalDefs
17
13
  }
18
14
 
package/src/drawer.tsx CHANGED
@@ -9,13 +9,29 @@ import {
9
9
  DrawerBody,
10
10
  DrawerCloseButton,
11
11
  DrawerProps as ChakraDrawerProps,
12
+ ModalHeaderProps,
13
+ ModalContentProps,
14
+ ModalFooterProps,
12
15
  } from '@chakra-ui/react'
16
+ import { MaybeRenderProp } from '@chakra-ui/react-utils'
17
+ import { runIfFn } from '@chakra-ui/utils'
13
18
 
14
19
  export interface BaseDrawerProps extends Omit<ChakraDrawerProps, 'children'> {
15
20
  /**
16
21
  * The drawer title
17
22
  */
18
23
  title: React.ReactNode
24
+ /**
25
+ * The modal children
26
+ */
27
+ children: MaybeRenderProp<{
28
+ isOpen: boolean
29
+ onClose: () => void
30
+ }>
31
+ /**
32
+ * The modal footer
33
+ */
34
+ footer?: React.ReactNode
19
35
  /**
20
36
  * Hide the close button
21
37
  */
@@ -24,26 +40,45 @@ export interface BaseDrawerProps extends Omit<ChakraDrawerProps, 'children'> {
24
40
  * Hide the overflow
25
41
  */
26
42
  hideOverlay?: boolean
27
- children?: React.ReactNode
43
+ /**
44
+ * Props for the modal header
45
+ */
46
+ headerProps?: ModalHeaderProps
47
+ /**
48
+ * Props for the modal content
49
+ */
50
+ contentProps?: ModalContentProps
51
+ /**
52
+ * Props for the modal footer
53
+ */
54
+ footerProps?: ModalFooterProps
28
55
  }
29
56
 
30
57
  export const BaseDrawer: React.FC<BaseDrawerProps> = (props) => {
31
58
  const {
32
59
  title,
33
60
  children,
61
+ footer,
34
62
  isOpen,
35
63
  onClose,
36
64
  hideCloseButton,
37
65
  hideOverlay,
66
+ headerProps,
67
+ contentProps,
68
+ footerProps,
38
69
  ...rest
39
70
  } = props
40
71
  return (
41
72
  <ChakraDrawer isOpen={isOpen} onClose={onClose} {...rest}>
42
73
  {!hideOverlay && <DrawerOverlay />}
43
- <DrawerContent>
44
- <DrawerHeader>{title}</DrawerHeader>
74
+ <DrawerContent {...contentProps}>
75
+ {title && <DrawerHeader {...headerProps}>{title}</DrawerHeader>}
45
76
  {!hideCloseButton && <DrawerCloseButton />}
46
- {children}
77
+ {runIfFn(children, {
78
+ isOpen,
79
+ onClose,
80
+ })}
81
+ {footer && <DrawerFooter {...footerProps}>{footer}</DrawerFooter>}
47
82
  </DrawerContent>
48
83
  </ChakraDrawer>
49
84
  )
@@ -57,12 +92,15 @@ export interface DrawerProps extends BaseDrawerProps {
57
92
  }
58
93
 
59
94
  export const Drawer: React.FC<DrawerProps> = (props) => {
60
- const { footer, children, ...rest } = props
95
+ const { children, isOpen, onClose, ...rest } = props
61
96
  return (
62
- <BaseDrawer {...rest}>
63
- <DrawerBody>{children}</DrawerBody>
64
-
65
- {footer && <DrawerFooter>{footer}</DrawerFooter>}
97
+ <BaseDrawer isOpen={isOpen} onClose={onClose} {...rest}>
98
+ <DrawerBody>
99
+ {runIfFn(children, {
100
+ isOpen,
101
+ onClose,
102
+ })}
103
+ </DrawerBody>
66
104
  </BaseDrawer>
67
105
  )
68
106
  }
package/src/form.tsx CHANGED
@@ -1,6 +1,12 @@
1
1
  import * as React from 'react'
2
2
 
3
- import { ModalBody, ModalFooter, Button, forwardRef } from '@chakra-ui/react'
3
+ import {
4
+ ModalBody,
5
+ ModalFooter,
6
+ Button,
7
+ forwardRef,
8
+ ButtonProps,
9
+ } from '@chakra-ui/react'
4
10
  import { runIfFn } from '@saas-ui/react-utils'
5
11
 
6
12
  import {
@@ -11,18 +17,27 @@ import {
11
17
  FieldValues,
12
18
  FieldResolver,
13
19
  FieldProps,
20
+ FormType,
21
+ DefaultFieldOverrides,
14
22
  } from '@saas-ui/forms'
15
23
 
24
+ import { YupFormType } from '@saas-ui/forms/yup'
25
+ import { ZodFormType } from '@saas-ui/forms/zod'
26
+
16
27
  import { BaseModal, BaseModalProps } from './modal'
17
28
 
29
+ export type FormDialogFieldOverrides = DefaultFieldOverrides & {
30
+ cancel?: ButtonProps
31
+ }
32
+
18
33
  export interface FormDialogProps<
34
+ TSchema = any,
19
35
  TFieldValues extends FieldValues = FieldValues,
20
36
  TContext extends object = object,
21
- TSchema = any,
22
37
  TFieldTypes = FieldProps<TFieldValues>
23
38
  > extends Omit<BaseModalProps, 'children'>,
24
39
  Pick<
25
- FormProps<TFieldValues, TContext, TSchema, TFieldTypes>,
40
+ FormProps<TSchema, TFieldValues, TContext, TFieldTypes>,
26
41
  | 'schema'
27
42
  | 'defaultValues'
28
43
  | 'values'
@@ -46,100 +61,149 @@ export interface FormDialogProps<
46
61
  * Defaults to a cancel and submit button.
47
62
  */
48
63
  footer?: React.ReactNode
49
- /**
50
- * The cancel button label
51
- * @default "Cancel"
52
- */
53
- cancelLabel?: React.ReactNode
54
- /**
55
- * The submit button label
56
- * @default "Submit"
57
- */
58
- submitLabel?: React.ReactNode
59
64
  /**
60
65
  * A schema field resolver used to auto generate form fields.
61
66
  */
62
67
  fieldResolver?: FieldResolver
68
+ /**
69
+ * Field overrides
70
+ */
71
+ fields?: FormDialogFieldOverrides
63
72
  }
73
+
74
+ const useFormProps = (props: FormDialogProps) => {
75
+ const {
76
+ schema,
77
+ resolver,
78
+ fieldResolver,
79
+ defaultValues,
80
+ values,
81
+ context,
82
+ onChange,
83
+ onSubmit,
84
+ onError,
85
+ mode,
86
+ reValidateMode,
87
+ shouldFocusError = true,
88
+ shouldUnregister,
89
+ shouldUseNativeValidation,
90
+ criteriaMode,
91
+ delayError = 100,
92
+ fields,
93
+ ...modalProps
94
+ } = props
95
+
96
+ const formProps = {
97
+ schema,
98
+ resolver,
99
+ defaultValues,
100
+ values,
101
+ context,
102
+ onChange,
103
+ onSubmit,
104
+ onError,
105
+ mode,
106
+ reValidateMode,
107
+ shouldFocusError,
108
+ shouldUnregister,
109
+ shouldUseNativeValidation,
110
+ criteriaMode,
111
+ delayError,
112
+ fields,
113
+ }
114
+
115
+ return { modalProps, formProps, fields }
116
+ }
117
+
64
118
  /**
65
- * Can be used to quickly request information from people without leaving the current page.
66
- *
67
- * @see Docs https://saas-ui.dev/docs/components/overlay/form-dialog
119
+ * @todo make this dynamic to support other schema types
68
120
  */
69
- export const FormDialog = forwardRef(
70
- <
71
- TFieldValues extends FieldValues = FieldValues,
72
- TContext extends object = object
73
- >(
74
- props: FormDialogProps<TFieldValues, TContext>,
75
- ref: React.ForwardedRef<HTMLFormElement>
76
- ) => {
77
- const {
78
- children,
79
- schema,
80
- resolver,
81
- fieldResolver,
82
- defaultValues,
83
- values,
84
- context,
85
- onChange,
86
- onSubmit,
87
- onError,
88
- mode,
89
- reValidateMode,
90
- shouldFocusError = true,
91
- shouldUnregister,
92
- shouldUseNativeValidation,
93
- criteriaMode,
94
- delayError = 100,
95
- cancelLabel = 'Cancel',
96
- submitLabel = 'Submit',
97
- footer,
98
- isOpen,
99
- onClose,
100
- ...rest
101
- } = props
102
-
103
- const formProps = {
104
- ref,
105
- schema,
106
- resolver,
107
- defaultValues,
108
- values,
109
- context,
110
- onChange,
111
- onSubmit,
112
- onError,
113
- mode,
114
- reValidateMode,
115
- shouldFocusError,
116
- shouldUnregister,
117
- shouldUseNativeValidation,
118
- criteriaMode,
119
- delayError,
120
- }
121
+ type MergeDialogProps<T> = T extends YupFormType<
122
+ infer FieldDefs,
123
+ infer ExtraProps,
124
+ infer ExtraOverrides,
125
+ 'yup'
126
+ >
127
+ ? YupFormType<
128
+ FieldDefs,
129
+ ExtraProps & Omit<BaseModalProps, 'children'>,
130
+ ExtraOverrides & FormDialogFieldOverrides
131
+ >
132
+ : T extends ZodFormType<
133
+ infer FieldDefs,
134
+ infer ExtraProps,
135
+ infer ExtraOverrides,
136
+ 'zod'
137
+ >
138
+ ? ZodFormType<
139
+ FieldDefs,
140
+ ExtraProps & Omit<BaseModalProps, 'children'>,
141
+ ExtraOverrides & FormDialogFieldOverrides
142
+ >
143
+ : T extends FormType<infer FieldDefs, infer ExtraProps, infer ExtraOverrides>
144
+ ? FormType<
145
+ FieldDefs,
146
+ ExtraProps & Omit<BaseModalProps, 'children'>,
147
+ ExtraOverrides & FormDialogFieldOverrides
148
+ >
149
+ : never
121
150
 
151
+ type IsSchemaType<T, Schema, FieldDefs> = T extends DefaultFormType<FieldDefs>
152
+ ? T extends (
153
+ props: FormProps<infer TSchema, infer TFieldValues, infer TContext>
154
+ ) => any
155
+ ? Schema extends TSchema
156
+ ? true
157
+ : false
158
+ : false
159
+ : false
160
+
161
+ export type DefaultFormType<
162
+ FieldDefs = any,
163
+ ExtraProps = object,
164
+ ExtraOverrides = FormDialogFieldOverrides
165
+ > = (<
166
+ TSchema = unknown,
167
+ TFieldValues extends Record<string, any> = any,
168
+ TContext extends object = object
169
+ >(
170
+ props: any
171
+ ) => React.ReactElement) & {
172
+ displayName?: string
173
+ id?: string
174
+ }
175
+
176
+ export function createFormDialog<
177
+ FieldDefs = any,
178
+ ExtraProps = object,
179
+ ExtraOverrides = FormDialogFieldOverrides,
180
+ TFormType extends DefaultFormType<
181
+ FieldDefs,
182
+ ExtraProps,
183
+ ExtraOverrides
184
+ > = DefaultFormType<FieldDefs, ExtraProps, ExtraOverrides>
185
+ >(Form: TFormType) {
186
+ const Dialog = forwardRef<any, 'div'>((props, ref) => {
187
+ const { isOpen, onClose, footer, children, ...rest } = props
188
+ const { modalProps, formProps, fields } = useFormProps(rest)
122
189
  return (
123
- <BaseModal isOpen={isOpen} onClose={onClose} {...rest}>
124
- <Form {...formProps} ref={ref}>
125
- {(form) => (
190
+ <BaseModal {...modalProps} isOpen={isOpen} onClose={onClose}>
191
+ <Form ref={ref} {...(formProps as any)}>
192
+ {(form: any) => (
126
193
  <>
127
- <ModalBody>
128
- {runIfFn(children, form) || (
129
- <AutoFields
130
- schema={schema}
131
- fieldResolver={fieldResolver}
132
- focusFirstField
133
- />
134
- )}
135
- </ModalBody>
194
+ <ModalBody>{runIfFn(children, form) || <AutoFields />}</ModalBody>
136
195
 
137
196
  {footer || (
138
197
  <ModalFooter>
139
- <Button variant="ghost" mr={3} onClick={onClose}>
140
- {cancelLabel}
198
+ <Button
199
+ variant="ghost"
200
+ mr={3}
201
+ onClick={onClose}
202
+ {...fields?.cancel}
203
+ >
204
+ {fields?.cancel?.children ?? 'Cancel'}
141
205
  </Button>
142
- <SubmitButton>{submitLabel}</SubmitButton>
206
+ <SubmitButton {...fields?.submit} />
143
207
  </ModalFooter>
144
208
  )}
145
209
  </>
@@ -147,9 +211,17 @@ export const FormDialog = forwardRef(
147
211
  </Form>
148
212
  </BaseModal>
149
213
  )
150
- }
151
- ) as <TFieldValues extends FieldValues>(
152
- props: FormDialogProps<TFieldValues> & {
153
- ref?: React.ForwardedRef<HTMLFormElement>
154
- }
155
- ) => React.ReactElement
214
+ }) as MergeDialogProps<TFormType>
215
+
216
+ Dialog.displayName = `${Form.displayName || Form.name}Dialog`
217
+ Dialog.id = Form.id
218
+
219
+ return Dialog
220
+ }
221
+
222
+ /**
223
+ * Can be used to quickly request information from people without leaving the current page.
224
+ *
225
+ * @see Docs https://saas-ui.dev/docs/components/overlay/form-dialog
226
+ */
227
+ export const FormDialog = createFormDialog(Form)
package/src/provider.tsx CHANGED
@@ -6,6 +6,8 @@ import { ConfirmDialogProps } from './dialog'
6
6
  import { MenuDialogProps } from './menu'
7
7
  import { FormDialogProps } from './form'
8
8
  import { defaultModals } from './default-modals'
9
+ import { FieldValues, FormType } from '@saas-ui/forms'
10
+ import { FormDialogHandler, FormHandler } from './types'
9
11
 
10
12
  export interface ModalsContextValue<
11
13
  TModals extends Record<string, React.FC<any>> = Record<string, React.FC<any>>,
@@ -29,9 +31,7 @@ export interface ModalsContextValue<
29
31
  alert: (options: ConfirmDialogOptions) => ModalId
30
32
  confirm: (options: ConfirmDialogOptions) => ModalId
31
33
  menu: (options: MenuDialogOptions) => ModalId
32
- form: <TProps extends FormDialogProps = FormDialogProps>(
33
- options: WithModalOptions<TProps>
34
- ) => ModalId
34
+ form: FormDialogHandler<TModals['form']>
35
35
  close: (id: ModalId) => void
36
36
  closeAll: () => void
37
37
  }
@@ -199,14 +199,14 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
199
199
  return id
200
200
  }
201
201
 
202
- const drawer = (options: DrawerOptions): ModalId => {
202
+ const drawer = (options: DrawerOptions) => {
203
203
  return open<DrawerOptions>({
204
204
  ...options,
205
205
  type: 'drawer',
206
206
  })
207
207
  }
208
208
 
209
- const alert = (options: ConfirmDialogOptions): ModalId => {
209
+ const alert = (options: ConfirmDialogOptions) => {
210
210
  return open({
211
211
  ...options,
212
212
  scope: 'alert',
@@ -221,7 +221,7 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
221
221
  })
222
222
  }
223
223
 
224
- const confirm = (options: ConfirmDialogOptions): ModalId => {
224
+ const confirm = (options: ConfirmDialogOptions) => {
225
225
  return open<ConfirmDialogOptions>({
226
226
  ...options,
227
227
  scope: 'alert',
@@ -229,16 +229,14 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
229
229
  })
230
230
  }
231
231
 
232
- const menu = (options: MenuDialogOptions): ModalId => {
232
+ const menu = (options: MenuDialogOptions) => {
233
233
  return open<MenuDialogOptions>({
234
234
  ...options,
235
235
  type: 'menu',
236
236
  })
237
237
  }
238
238
 
239
- const form = <TProps extends FormDialogProps = FormDialogProps>(
240
- options: WithModalOptions<TProps>
241
- ): ModalId => {
239
+ const form = (options: any) => {
242
240
  return open({
243
241
  ...options,
244
242
  type: 'form',
@@ -280,6 +278,8 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
280
278
  modal.scope
281
279
  )
282
280
  }
281
+
282
+ closeComplete(id)
283
283
  }
284
284
 
285
285
  const closeComplete = (id?: ModalId | null) => {
package/src/types.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { FormProps, FormType } from '@saas-ui/forms'
2
+ import { ModalId } from './provider'
3
+ import { AnyObjectSchema, YupFormType } from '@saas-ui/forms/yup'
4
+ import { FormDialogFieldOverrides } from './form'
5
+ import { WithFields } from '@saas-ui/forms'
6
+ import { FieldValues } from '@saas-ui/forms'
7
+ import type { z } from 'zod'
8
+ import type { InferType } from 'yup'
9
+ import { ZodFormType } from '@saas-ui/forms/zod'
10
+
11
+ export type FormDialogHandler<T> = T extends YupFormType<
12
+ infer FieldDefs,
13
+ infer ExtraProps,
14
+ infer ExtraOverrides,
15
+ 'yup'
16
+ >
17
+ ? YupHandler<FieldDefs, object, ExtraOverrides & FormDialogFieldOverrides>
18
+ : T extends ZodFormType<
19
+ infer FieldDefs,
20
+ infer ExtraProps,
21
+ infer ExtraOverrides,
22
+ 'zod'
23
+ >
24
+ ? ZodHandler<FieldDefs, object, ExtraOverrides & FormDialogFieldOverrides>
25
+ : T extends FormType<infer FieldDefs, infer ExtraProps, infer ExtraOverrides>
26
+ ? FormHandler<FieldDefs, object, ExtraOverrides & FormDialogFieldOverrides>
27
+ : never
28
+
29
+ export type ZodHandler<
30
+ FieldDefs,
31
+ ExtraProps = object,
32
+ ExtraOverrides = object,
33
+ Type extends 'zod' = 'zod'
34
+ > = <
35
+ TSchema extends z.AnyZodObject = z.AnyZodObject,
36
+ TFieldValues extends z.infer<TSchema> = z.infer<TSchema>,
37
+ TContext extends object = object
38
+ >(
39
+ props: WithFields<
40
+ FormProps<TSchema, TFieldValues, TContext>,
41
+ FieldDefs,
42
+ ExtraOverrides
43
+ > & {
44
+ ref?: React.ForwardedRef<HTMLFormElement>
45
+ } & ExtraProps
46
+ ) => ModalId
47
+
48
+ export type FormHandler<
49
+ FieldDefs,
50
+ ExtraProps = object,
51
+ ExtraOverrides = object
52
+ > = <
53
+ TSchema = unknown,
54
+ TFieldValues extends FieldValues = FieldValues,
55
+ TContext extends object = object
56
+ >(
57
+ props: WithFields<
58
+ FormProps<TSchema, TFieldValues, TContext>,
59
+ FieldDefs,
60
+ ExtraOverrides
61
+ > & {
62
+ ref?: React.ForwardedRef<HTMLFormElement>
63
+ } & ExtraProps
64
+ ) => ModalId
65
+
66
+ export type YupHandler<
67
+ FieldDefs,
68
+ ExtraProps = object,
69
+ ExtraOverrides = object,
70
+ Type extends 'yup' = 'yup'
71
+ > = <
72
+ TSchema extends AnyObjectSchema = AnyObjectSchema,
73
+ TFieldValues extends InferType<TSchema> = InferType<TSchema>, // placeholder
74
+ TContext extends object = object
75
+ >(
76
+ props: WithFields<
77
+ FormProps<TFieldValues, TContext, TSchema>,
78
+ FieldDefs,
79
+ ExtraOverrides
80
+ > & {
81
+ ref?: React.ForwardedRef<HTMLFormElement>
82
+ } & ExtraProps
83
+ ) => ModalId