@saas-ui/modals 2.0.0-next.2 → 2.0.0-next.21
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/CHANGELOG.md +175 -0
- package/dist/index.d.ts +152 -57
- package/dist/index.js +262 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +261 -168
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
- package/src/create-modals.tsx +30 -0
- package/src/default-modals.ts +14 -0
- package/src/drawer.tsx +47 -9
- package/src/form.tsx +168 -91
- package/src/index.ts +2 -0
- package/src/menu.tsx +12 -1
- package/src/modal.tsx +43 -8
- package/src/provider.tsx +65 -52
- package/src/types.ts +83 -0
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
|
-
|
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>
|
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 {
|
4
|
-
import {
|
5
|
-
import {
|
6
|
-
import {
|
7
|
-
import {
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
+
import { FieldValues, FormType } from '@saas-ui/forms'
|
10
|
+
import { FormDialogHandler, FormHandler } from './types'
|
11
|
+
|
12
|
+
export interface ModalsContextValue<
|
13
|
+
TModals extends Record<string, React.FC<any>> = Record<string, React.FC<any>>,
|
14
|
+
TTypes extends Extract<keyof TModals, string> = Extract<keyof TModals, string>
|
15
|
+
> {
|
16
|
+
open: <T extends OpenOptions<TTypes>>(
|
17
|
+
componentOrOptions: T extends {
|
18
|
+
component: infer TComponent extends React.FC<any>
|
19
|
+
}
|
20
|
+
? WithModalOptions<React.ComponentPropsWithRef<TComponent>>
|
21
|
+
: T extends {
|
22
|
+
type: infer TType extends keyof TModals
|
23
|
+
}
|
24
|
+
? WithModalOptions<React.ComponentPropsWithRef<TModals[TType]>>
|
25
|
+
: T,
|
26
|
+
options?: T extends React.FC<any>
|
27
|
+
? WithModalOptions<React.ComponentPropsWithRef<T>>
|
28
|
+
: never
|
29
|
+
) => ModalId
|
11
30
|
drawer: (options: DrawerOptions) => ModalId
|
12
31
|
alert: (options: ConfirmDialogOptions) => ModalId
|
13
32
|
confirm: (options: ConfirmDialogOptions) => ModalId
|
14
33
|
menu: (options: MenuDialogOptions) => ModalId
|
15
|
-
form:
|
34
|
+
form: FormDialogHandler<TModals['form']>
|
16
35
|
close: (id: ModalId) => void
|
17
36
|
closeAll: () => void
|
18
37
|
}
|
19
38
|
|
20
|
-
export const ModalsContext = React.createContext<ModalsContextValue
|
21
|
-
|
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?:
|
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, '
|
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?:
|
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?:
|
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
|
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
|
159
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
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
|
-
} =
|
185
|
+
} = _options
|
175
186
|
|
176
187
|
const modal: ModalConfig<T> = {
|
177
188
|
id,
|
@@ -188,14 +199,14 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
|
|
188
199
|
return id
|
189
200
|
}
|
190
201
|
|
191
|
-
const drawer = (options: DrawerOptions)
|
202
|
+
const drawer = (options: DrawerOptions) => {
|
192
203
|
return open<DrawerOptions>({
|
193
204
|
...options,
|
194
205
|
type: 'drawer',
|
195
206
|
})
|
196
207
|
}
|
197
208
|
|
198
|
-
const alert = (options: ConfirmDialogOptions)
|
209
|
+
const alert = (options: ConfirmDialogOptions) => {
|
199
210
|
return open({
|
200
211
|
...options,
|
201
212
|
scope: 'alert',
|
@@ -210,7 +221,7 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
|
|
210
221
|
})
|
211
222
|
}
|
212
223
|
|
213
|
-
const confirm = (options: ConfirmDialogOptions)
|
224
|
+
const confirm = (options: ConfirmDialogOptions) => {
|
214
225
|
return open<ConfirmDialogOptions>({
|
215
226
|
...options,
|
216
227
|
scope: 'alert',
|
@@ -218,15 +229,15 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
|
|
218
229
|
})
|
219
230
|
}
|
220
231
|
|
221
|
-
const menu = (options: MenuDialogOptions)
|
232
|
+
const menu = (options: MenuDialogOptions) => {
|
222
233
|
return open<MenuDialogOptions>({
|
223
234
|
...options,
|
224
235
|
type: 'menu',
|
225
236
|
})
|
226
237
|
}
|
227
238
|
|
228
|
-
const form = (options:
|
229
|
-
return open
|
239
|
+
const form = (options: any) => {
|
240
|
+
return open({
|
230
241
|
...options,
|
231
242
|
type: 'form',
|
232
243
|
})
|
@@ -267,6 +278,8 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
|
|
267
278
|
modal.scope
|
268
279
|
)
|
269
280
|
}
|
281
|
+
|
282
|
+
closeComplete(id)
|
270
283
|
}
|
271
284
|
|
272
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
|