@saas-ui/modals 2.0.0-next.1 → 2.0.0-next.10

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.
@@ -0,0 +1,14 @@
1
+ import { ConfirmDialog } from './dialog'
2
+ import { MenuDialog } from './menu'
3
+ import { FormDialog } from './form'
4
+ import { Drawer } from './drawer'
5
+ import { Modal } from './modal'
6
+
7
+ export const defaultModals = {
8
+ alert: ConfirmDialog,
9
+ confirm: ConfirmDialog,
10
+ drawer: Drawer,
11
+ modal: Modal,
12
+ menu: MenuDialog,
13
+ form: FormDialog,
14
+ }
package/src/form.tsx CHANGED
@@ -5,21 +5,24 @@ import { runIfFn } from '@saas-ui/react-utils'
5
5
 
6
6
  import {
7
7
  Form,
8
- Fields,
8
+ AutoFields,
9
9
  SubmitButton,
10
10
  FormProps,
11
11
  FieldValues,
12
12
  FieldResolver,
13
+ FieldProps,
13
14
  } from '@saas-ui/forms'
14
15
 
15
16
  import { BaseModal, BaseModalProps } from './modal'
16
17
 
17
18
  export interface FormDialogProps<
18
19
  TFieldValues extends FieldValues = FieldValues,
19
- TContext extends object = object
20
+ TContext extends object = object,
21
+ TSchema = any,
22
+ TFieldTypes = FieldProps<TFieldValues>
20
23
  > extends Omit<BaseModalProps, 'children'>,
21
24
  Pick<
22
- FormProps<TFieldValues, TContext>,
25
+ FormProps<TFieldValues, TContext, TSchema, TFieldTypes>,
23
26
  | 'schema'
24
27
  | 'defaultValues'
25
28
  | 'values'
@@ -35,6 +38,8 @@ export interface FormDialogProps<
35
38
  | 'shouldUseNativeValidation'
36
39
  | 'criteriaMode'
37
40
  | 'delayError'
41
+ | 'resetOptions'
42
+ | 'children'
38
43
  > {
39
44
  /**
40
45
  * The modal footer, will be wrapped with `ModalFooter`.
@@ -51,16 +56,16 @@ export interface FormDialogProps<
51
56
  * @default "Submit"
52
57
  */
53
58
  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
59
  /**
59
60
  * A schema field resolver used to auto generate form fields.
60
61
  */
61
62
  fieldResolver?: FieldResolver
62
63
  }
63
-
64
+ /**
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
68
+ */
64
69
  export const FormDialog = forwardRef(
65
70
  <
66
71
  TFieldValues extends FieldValues = FieldValues,
@@ -121,7 +126,7 @@ export const FormDialog = forwardRef(
121
126
  <>
122
127
  <ModalBody>
123
128
  {runIfFn(children, form) || (
124
- <Fields
129
+ <AutoFields
125
130
  schema={schema}
126
131
  fieldResolver={fieldResolver}
127
132
  focusFirstField
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}>
@@ -86,6 +92,11 @@ export const MenuDialogList = forwardRef<MenuDialogListProps, 'div'>(
86
92
  ...styles.list,
87
93
  boxShadow: 'none',
88
94
  border: 0,
95
+ _dark: {
96
+ /* @ts-expect-error */
97
+ ...(styles.list._dark || {}),
98
+ boxShadow: 'none',
99
+ },
89
100
  }}
90
101
  />
91
102
  </StylesProvider>
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
  }
package/src/provider.tsx CHANGED
@@ -1,38 +1,59 @@
1
1
  import * as React from 'react'
2
2
 
3
- import { Modal, BaseModalProps } from './modal'
4
- import { Drawer, DrawerProps } from './drawer'
5
- import { ConfirmDialog, ConfirmDialogProps } from './dialog'
6
- import { MenuDialog, MenuDialogProps } from './menu'
7
- import { FormDialog, FormDialogProps } from './form'
8
-
9
- export interface ModalsContextValue {
10
- open: (options: OpenOptions) => ModalId
3
+ import { BaseModalProps } from './modal'
4
+ import { DrawerProps } from './drawer'
5
+ import { ConfirmDialogProps } from './dialog'
6
+ import { MenuDialogProps } from './menu'
7
+ import { FormDialogProps } from './form'
8
+ import { defaultModals } from './default-modals'
9
+
10
+ export interface ModalsContextValue<
11
+ TModals extends Record<string, React.FC<any>> = Record<string, React.FC<any>>,
12
+ TTypes extends Extract<keyof TModals, string> = Extract<keyof TModals, string>
13
+ > {
14
+ open: <T extends OpenOptions<TTypes>>(
15
+ componentOrOptions: T extends {
16
+ component: infer TComponent extends React.FC<any>
17
+ }
18
+ ? WithModalOptions<React.ComponentPropsWithRef<TComponent>>
19
+ : T extends {
20
+ type: infer TType extends keyof TModals
21
+ }
22
+ ? WithModalOptions<React.ComponentPropsWithRef<TModals[TType]>>
23
+ : T,
24
+ options?: T extends React.FC<any>
25
+ ? WithModalOptions<React.ComponentPropsWithRef<T>>
26
+ : never
27
+ ) => ModalId
11
28
  drawer: (options: DrawerOptions) => ModalId
12
29
  alert: (options: ConfirmDialogOptions) => ModalId
13
30
  confirm: (options: ConfirmDialogOptions) => ModalId
14
31
  menu: (options: MenuDialogOptions) => ModalId
15
- form: (options: FormDialogOptions) => ModalId
32
+ form: <TProps extends FormDialogProps = FormDialogProps>(
33
+ options: WithModalOptions<TProps>
34
+ ) => ModalId
16
35
  close: (id: ModalId) => void
17
36
  closeAll: () => void
18
37
  }
19
38
 
20
- export const ModalsContext = React.createContext<ModalsContextValue | null>(
21
- null
22
- )
39
+ export const ModalsContext = React.createContext<ModalsContextValue<
40
+ typeof defaultModals
41
+ > | null>(null)
23
42
 
24
- interface ModalsProviderProps {
43
+ export interface ModalsProviderProps<
44
+ TModals extends Record<string, React.FC<any>> = Record<string, React.FC<any>>
45
+ > {
25
46
  children: React.ReactNode
26
- modals?: Record<string, React.FC<any>>
47
+ modals?: TModals
27
48
  }
28
49
 
29
50
  export type ModalId = string | number
30
51
 
52
+ type WithModalOptions<T> = Omit<T, 'isOpen' | 'onClose'> & ModalOptions
53
+
31
54
  interface ModalOptions
32
- extends Omit<BaseModalProps, 'onClose' | 'isOpen' | 'children'> {
55
+ extends Omit<BaseModalProps, 'isOpen' | 'onClose' | 'children'> {
33
56
  onClose?: (args: { force?: boolean }) => Promise<boolean | undefined> | void
34
- body?: React.ReactNode
35
- children?: React.ReactNode
36
57
  [key: string]: any
37
58
  }
38
59
 
@@ -52,23 +73,16 @@ export interface FormDialogOptions
52
73
  extends ModalOptions,
53
74
  Omit<FormDialogProps, 'onClose' | 'isOpen' | 'children'> {}
54
75
 
55
- export interface OpenOptions extends ModalOptions {
56
- type?: ModalTypes
76
+ export interface OpenOptions<TModalTypes extends string> extends ModalOptions {
77
+ type?: TModalTypes
57
78
  scope?: ModalScopes
58
79
  }
59
80
 
60
81
  export type ModalScopes = 'modal' | 'alert'
61
82
 
62
- export type ModalTypes =
63
- | 'modal'
64
- | 'drawer'
65
- | 'alert'
66
- | 'confirm'
67
- | 'menu'
68
- | string
69
-
70
83
  export interface ModalConfig<
71
- TModalOptions extends ModalOptions = ModalOptions
84
+ TModalOptions extends ModalOptions = ModalOptions,
85
+ TModalTypes extends string = string
72
86
  > {
73
87
  /**
74
88
  * The modal id, autogenerated when not set.
@@ -91,7 +105,7 @@ export interface ModalConfig<
91
105
  *
92
106
  * Custom types can be configured using the `modals` prop of `ModalProvider`
93
107
  */
94
- type?: ModalTypes
108
+ type?: TModalTypes
95
109
  /**
96
110
  * Render a custom modal component.
97
111
  * This will ignore the `type` param.
@@ -110,15 +124,6 @@ const initialModalState: ModalConfig = {
110
124
  type: 'modal',
111
125
  }
112
126
 
113
- const defaultModals = {
114
- alert: ConfirmDialog,
115
- confirm: ConfirmDialog,
116
- drawer: Drawer,
117
- modal: Modal,
118
- menu: MenuDialog,
119
- form: FormDialog,
120
- }
121
-
122
127
  export function ModalsProvider({ children, modals }: ModalsProviderProps) {
123
128
  // Note that updating the Set doesn't trigger a re-render,
124
129
  // use in conjuction with setActiveModals
@@ -136,7 +141,7 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
136
141
  ...modals,
137
142
  }
138
143
 
139
- return (type: ModalTypes = 'modal') => {
144
+ return (type = 'modal') => {
140
145
  const component = _modals[type] || _modals.modal
141
146
 
142
147
  return component
@@ -155,14 +160,20 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
155
160
  }))
156
161
  }
157
162
 
158
- const open = <T extends ModalOptions>(
159
- options: T | React.FC<BaseModalProps>
163
+ const open = <T extends OpenOptions<any>>(
164
+ componentOrOptions: any,
165
+ options?: T extends React.FC<any>
166
+ ? WithModalOptions<React.ComponentPropsWithRef<T>>
167
+ : never
160
168
  ): ModalId => {
161
- if (typeof options === 'function') {
162
- const component: React.FC<BaseModalProps> = options
163
- options = {
164
- component,
169
+ let _options: ModalOptions
170
+ if (typeof componentOrOptions === 'function') {
171
+ _options = {
172
+ component: componentOrOptions,
173
+ ...options,
165
174
  } as unknown as T
175
+ } else {
176
+ _options = componentOrOptions
166
177
  }
167
178
 
168
179
  const {
@@ -171,7 +182,7 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
171
182
  scope = 'modal',
172
183
  component,
173
184
  ...props
174
- } = options
185
+ } = _options
175
186
 
176
187
  const modal: ModalConfig<T> = {
177
188
  id,
@@ -225,8 +236,10 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
225
236
  })
226
237
  }
227
238
 
228
- const form = (options: FormDialogOptions): ModalId => {
229
- return open<FormDialogOptions>({
239
+ const form = <TProps extends FormDialogProps = FormDialogProps>(
240
+ options: WithModalOptions<TProps>
241
+ ): ModalId => {
242
+ return open({
230
243
  ...options,
231
244
  type: 'form',
232
245
  })