@saas-ui/modals 2.0.0-next.0 → 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.
- package/CHANGELOG.md +86 -0
- package/dist/index.d.ts +77 -26
- package/dist/index.js +196 -119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +195 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -7
- package/src/create-modals.tsx +34 -0
- package/src/default-modals.ts +14 -0
- package/src/form.tsx +14 -9
- package/src/index.ts +2 -0
- package/src/menu.tsx +12 -1
- package/src/modal.tsx +43 -8
- package/src/provider.tsx +61 -48
@@ -0,0 +1,34 @@
|
|
1
|
+
import { defaultModals } from './default-modals'
|
2
|
+
import {
|
3
|
+
ModalsContextValue,
|
4
|
+
ModalsProvider,
|
5
|
+
ModalsProviderProps,
|
6
|
+
useModals,
|
7
|
+
} from './provider'
|
8
|
+
|
9
|
+
interface CreateModalOptions {}
|
10
|
+
|
11
|
+
const createModal = (Component: React.FC<any>, options: CreateModalOptions) => {
|
12
|
+
return Component
|
13
|
+
}
|
14
|
+
|
15
|
+
export interface CreateModalsOptions<TModalDefs> {
|
16
|
+
modals: TModalDefs
|
17
|
+
}
|
18
|
+
|
19
|
+
export const createModals = <TModalDefs extends Record<string, React.FC<any>>>(
|
20
|
+
options: CreateModalsOptions<TModalDefs>
|
21
|
+
) => {
|
22
|
+
const modals = {
|
23
|
+
...defaultModals,
|
24
|
+
...options.modals,
|
25
|
+
}
|
26
|
+
const Provider = (props: Omit<ModalsProviderProps, 'modals'>) => {
|
27
|
+
return <ModalsProvider children={props.children} modals={modals} />
|
28
|
+
}
|
29
|
+
|
30
|
+
return {
|
31
|
+
ModalsProvider: Provider,
|
32
|
+
useModals: useModals as () => ModalsContextValue<typeof modals>,
|
33
|
+
}
|
34
|
+
}
|
@@ -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
|
-
|
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
|
-
<
|
129
|
+
<AutoFields
|
125
130
|
schema={schema}
|
126
131
|
fieldResolver={fieldResolver}
|
127
132
|
focusFirstField
|
package/src/index.ts
CHANGED
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
|
+
|
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:
|
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
|
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,
|
@@ -225,8 +236,10 @@ export function ModalsProvider({ children, modals }: ModalsProviderProps) {
|
|
225
236
|
})
|
226
237
|
}
|
227
238
|
|
228
|
-
const form =
|
229
|
-
|
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
|
})
|