@saas-ui/modals 2.0.0-next.8 → 2.0.0-rc.22

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/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 {
@@ -10,16 +16,28 @@ import {
10
16
  FormProps,
11
17
  FieldValues,
12
18
  FieldResolver,
19
+ FieldProps,
20
+ FormType,
21
+ DefaultFieldOverrides,
13
22
  } from '@saas-ui/forms'
14
23
 
24
+ import { YupFormType } from '@saas-ui/forms/yup'
25
+ import { ZodFormType } from '@saas-ui/forms/zod'
26
+
15
27
  import { BaseModal, BaseModalProps } from './modal'
16
28
 
29
+ export type FormDialogFieldOverrides = DefaultFieldOverrides & {
30
+ cancel?: ButtonProps
31
+ }
32
+
17
33
  export interface FormDialogProps<
34
+ TSchema = any,
18
35
  TFieldValues extends FieldValues = FieldValues,
19
- TContext extends object = object
36
+ TContext extends object = object,
37
+ TFieldTypes = FieldProps<TFieldValues>
20
38
  > extends Omit<BaseModalProps, 'children'>,
21
39
  Pick<
22
- FormProps<TFieldValues, TContext>,
40
+ FormProps<TSchema, TFieldValues, TContext, TFieldTypes>,
23
41
  | 'schema'
24
42
  | 'defaultValues'
25
43
  | 'values'
@@ -35,110 +53,157 @@ export interface FormDialogProps<
35
53
  | 'shouldUseNativeValidation'
36
54
  | 'criteriaMode'
37
55
  | 'delayError'
56
+ | 'resetOptions'
57
+ | 'children'
38
58
  > {
39
59
  /**
40
60
  * The modal footer, will be wrapped with `ModalFooter`.
41
61
  * Defaults to a cancel and submit button.
42
62
  */
43
63
  footer?: React.ReactNode
44
- /**
45
- * The cancel button label
46
- * @default "Cancel"
47
- */
48
- cancelLabel?: React.ReactNode
49
- /**
50
- * The submit button label
51
- * @default "Submit"
52
- */
53
- submitLabel?: React.ReactNode
54
- /**
55
- * If no children are passed, this will auto render fields based on the supplied schema.
56
- */
57
- children?: React.ReactNode
58
64
  /**
59
65
  * A schema field resolver used to auto generate form fields.
60
66
  */
61
67
  fieldResolver?: FieldResolver
68
+ /**
69
+ * Field overrides
70
+ */
71
+ fields?: FormDialogFieldOverrides
62
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
+
63
118
  /**
64
- * Can be used to quickly request information from people without leaving the current page.
65
- *
66
- * @see Docs https://saas-ui.dev/docs/components/overlay/form-dialog
119
+ * @todo make this dynamic to support other schema types
67
120
  */
68
- export const FormDialog = forwardRef(
69
- <
70
- TFieldValues extends FieldValues = FieldValues,
71
- TContext extends object = object
72
- >(
73
- props: FormDialogProps<TFieldValues, TContext>,
74
- ref: React.ForwardedRef<HTMLFormElement>
75
- ) => {
76
- const {
77
- children,
78
- schema,
79
- resolver,
80
- fieldResolver,
81
- defaultValues,
82
- values,
83
- context,
84
- onChange,
85
- onSubmit,
86
- onError,
87
- mode,
88
- reValidateMode,
89
- shouldFocusError = true,
90
- shouldUnregister,
91
- shouldUseNativeValidation,
92
- criteriaMode,
93
- delayError = 100,
94
- cancelLabel = 'Cancel',
95
- submitLabel = 'Submit',
96
- footer,
97
- isOpen,
98
- onClose,
99
- ...rest
100
- } = props
101
-
102
- const formProps = {
103
- ref,
104
- schema,
105
- resolver,
106
- defaultValues,
107
- values,
108
- context,
109
- onChange,
110
- onSubmit,
111
- onError,
112
- mode,
113
- reValidateMode,
114
- shouldFocusError,
115
- shouldUnregister,
116
- shouldUseNativeValidation,
117
- criteriaMode,
118
- delayError,
119
- }
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
120
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)
121
189
  return (
122
- <BaseModal isOpen={isOpen} onClose={onClose} {...rest}>
123
- <Form {...formProps} ref={ref}>
124
- {(form) => (
190
+ <BaseModal {...modalProps} isOpen={isOpen} onClose={onClose}>
191
+ <Form ref={ref} {...(formProps as any)}>
192
+ {(form: any) => (
125
193
  <>
126
- <ModalBody>
127
- {runIfFn(children, form) || (
128
- <AutoFields
129
- schema={schema}
130
- fieldResolver={fieldResolver}
131
- focusFirstField
132
- />
133
- )}
134
- </ModalBody>
194
+ <ModalBody>{runIfFn(children, form) || <AutoFields />}</ModalBody>
135
195
 
136
196
  {footer || (
137
197
  <ModalFooter>
138
- <Button variant="ghost" mr={3} onClick={onClose}>
139
- {cancelLabel}
198
+ <Button
199
+ variant="ghost"
200
+ mr={3}
201
+ onClick={onClose}
202
+ {...fields?.cancel}
203
+ >
204
+ {fields?.cancel?.children ?? 'Cancel'}
140
205
  </Button>
141
- <SubmitButton>{submitLabel}</SubmitButton>
206
+ <SubmitButton {...fields?.submit} />
142
207
  </ModalFooter>
143
208
  )}
144
209
  </>
@@ -146,9 +211,17 @@ export const FormDialog = forwardRef(
146
211
  </Form>
147
212
  </BaseModal>
148
213
  )
149
- }
150
- ) as <TFieldValues extends FieldValues>(
151
- props: FormDialogProps<TFieldValues> & {
152
- ref?: React.ForwardedRef<HTMLFormElement>
153
- }
154
- ) => 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/index.ts CHANGED
@@ -4,3 +4,5 @@ export * from './modal'
4
4
  export * from './menu'
5
5
  export * from './form'
6
6
  export * from './provider'
7
+
8
+ export { createModals, type CreateModalsOptions } from './create-modals'
package/src/menu.tsx CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  useMultiStyleConfig,
11
11
  Menu,
12
12
  MenuListProps,
13
+ useBreakpointValue,
13
14
  } from '@chakra-ui/react'
14
15
 
15
16
  import { BaseModal, BaseModalProps } from './modal'
@@ -55,7 +56,8 @@ export const MenuDialogList = forwardRef<MenuDialogListProps, 'div'>(
55
56
  footer,
56
57
  initialFocusRef,
57
58
  hideCloseButton,
58
- motionPreset,
59
+ motionPreset = 'slideInBottom',
60
+ isCentered: isCenteredProp,
59
61
  ...rest
60
62
  } = props
61
63
 
@@ -65,6 +67,8 @@ export const MenuDialogList = forwardRef<MenuDialogListProps, 'div'>(
65
67
 
66
68
  const styles = useMultiStyleConfig('Menu', props)
67
69
 
70
+ const isCentered = useBreakpointValue({ base: true, md: false })
71
+
68
72
  return (
69
73
  <BaseModal
70
74
  isOpen={isOpen}
@@ -73,6 +77,8 @@ export const MenuDialogList = forwardRef<MenuDialogListProps, 'div'>(
73
77
  title={title}
74
78
  hideCloseButton={hideCloseButton}
75
79
  motionPreset={motionPreset}
80
+ isCentered={isCenteredProp ?? isCentered}
81
+ contentProps={{ mx: 4 }}
76
82
  >
77
83
  {/* We forward the styles again, otherwise the modal styles will be picked up */}
78
84
  <StylesProvider value={styles}>
package/src/modal.tsx CHANGED
@@ -9,13 +9,25 @@ import {
9
9
  ModalBody,
10
10
  ModalCloseButton,
11
11
  ModalProps as ChakraModalProps,
12
+ ModalContentProps,
13
+ ModalHeaderProps,
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
- export interface BaseModalProps extends ChakraModalProps {
19
+ export interface BaseModalProps extends Omit<ChakraModalProps, 'children'> {
15
20
  /**
16
21
  * The modal title
17
22
  */
18
23
  title?: React.ReactNode
24
+ /**
25
+ * The modal children
26
+ */
27
+ children: MaybeRenderProp<{
28
+ isOpen: boolean
29
+ onClose: () => void
30
+ }>
19
31
  /**
20
32
  * The modal footer
21
33
  */
@@ -28,6 +40,18 @@ export interface BaseModalProps extends ChakraModalProps {
28
40
  * Hide the overlay
29
41
  */
30
42
  hideOverlay?: boolean
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
31
55
  }
32
56
 
33
57
  export const BaseModal: React.FC<BaseModalProps> = (props) => {
@@ -39,26 +63,37 @@ export const BaseModal: React.FC<BaseModalProps> = (props) => {
39
63
  onClose,
40
64
  hideCloseButton,
41
65
  hideOverlay,
66
+ headerProps,
67
+ contentProps,
68
+ footerProps,
42
69
  ...rest
43
70
  } = props
44
71
  return (
45
72
  <ChakraModal isOpen={isOpen} onClose={onClose} {...rest}>
46
73
  {!hideOverlay && <ModalOverlay />}
47
- <ModalContent>
48
- {title && <ModalHeader>{title}</ModalHeader>}
74
+ <ModalContent {...contentProps}>
75
+ {title && <ModalHeader {...headerProps}>{title}</ModalHeader>}
49
76
  {!hideCloseButton && <ModalCloseButton />}
50
- {children}
51
- {footer && <ModalFooter>{footer}</ModalFooter>}
77
+ {runIfFn(children, {
78
+ isOpen,
79
+ onClose,
80
+ })}
81
+ {footer && <ModalFooter {...footerProps}>{footer}</ModalFooter>}
52
82
  </ModalContent>
53
83
  </ChakraModal>
54
84
  )
55
85
  }
56
86
 
57
87
  export const Modal: React.FC<BaseModalProps> = (props) => {
58
- const { children, ...rest } = props
88
+ const { children, isOpen, onClose, ...rest } = props
59
89
  return (
60
- <BaseModal {...rest}>
61
- <ModalBody>{children}</ModalBody>
90
+ <BaseModal {...rest} isOpen={isOpen} onClose={onClose}>
91
+ <ModalBody>
92
+ {runIfFn(children, {
93
+ isOpen,
94
+ onClose,
95
+ })}
96
+ </ModalBody>
62
97
  </BaseModal>
63
98
  )
64
99
  }