@saas-ui/modals 2.4.2 → 3.0.0-alpha.0
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 +11 -5
- package/dist/index.cjs +206 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +182 -0
- package/dist/index.d.ts +110 -185
- package/dist/index.js +86 -531
- package/dist/index.js.map +1 -1
- package/package.json +15 -33
- package/src/alert-dialog.tsx +156 -0
- package/src/default-modals.ts +3 -7
- package/src/drawer.tsx +46 -60
- package/src/form.tsx +15 -22
- package/src/index.ts +22 -38
- package/src/modal.tsx +43 -52
- package/src/types.ts +46 -33
- package/dist/chunk-FRC2HTO7.mjs +0 -154
- package/dist/chunk-FRC2HTO7.mjs.map +0 -1
- package/dist/form-BtCUKHAs.d.mts +0 -82
- package/dist/form-BtCUKHAs.d.ts +0 -82
- package/dist/index.d.mts +0 -257
- package/dist/index.mjs +0 -469
- package/dist/index.mjs.map +0 -1
- package/dist/yup/index.d.mts +0 -15
- package/dist/yup/index.d.ts +0 -15
- package/dist/yup/index.js +0 -165
- package/dist/yup/index.js.map +0 -1
- package/dist/yup/index.mjs +0 -20
- package/dist/yup/index.mjs.map +0 -1
- package/dist/zod/index.d.mts +0 -15
- package/dist/zod/index.d.ts +0 -15
- package/dist/zod/index.js +0 -165
- package/dist/zod/index.js.map +0 -1
- package/dist/zod/index.mjs +0 -20
- package/dist/zod/index.mjs.map +0 -1
- package/src/create-modals.tsx +0 -30
- package/src/dialog.tsx +0 -151
- package/src/menu.tsx +0 -107
- package/src/provider.tsx +0 -357
package/src/menu.tsx
DELETED
@@ -1,107 +0,0 @@
|
|
1
|
-
import * as React from 'react'
|
2
|
-
|
3
|
-
import {
|
4
|
-
ModalFooter,
|
5
|
-
chakra,
|
6
|
-
forwardRef,
|
7
|
-
useMenuContext,
|
8
|
-
useMenuList,
|
9
|
-
createStylesContext,
|
10
|
-
useMultiStyleConfig,
|
11
|
-
Menu,
|
12
|
-
MenuListProps,
|
13
|
-
useBreakpointValue,
|
14
|
-
} from '@chakra-ui/react'
|
15
|
-
|
16
|
-
import { BaseModal, BaseModalProps } from './modal'
|
17
|
-
|
18
|
-
const [StylesProvider] = createStylesContext('SuiMenuDialog')
|
19
|
-
|
20
|
-
export interface MenuDialogProps extends BaseModalProps {
|
21
|
-
/**
|
22
|
-
* The modal footer, wrapped with `ModalFooter`
|
23
|
-
*/
|
24
|
-
footer?: React.ReactNode
|
25
|
-
}
|
26
|
-
|
27
|
-
export const MenuDialog: React.FC<MenuDialogProps> = (props) => {
|
28
|
-
const { onClose, onCloseComplete, ...rest } = props
|
29
|
-
|
30
|
-
return (
|
31
|
-
<Menu
|
32
|
-
variant="dialog"
|
33
|
-
onClose={() => {
|
34
|
-
onClose?.()
|
35
|
-
// Not supported in Menu, so we call it here instead
|
36
|
-
// @todo Refactor this in v2?
|
37
|
-
onCloseComplete?.()
|
38
|
-
}}
|
39
|
-
{...rest}
|
40
|
-
/>
|
41
|
-
)
|
42
|
-
}
|
43
|
-
|
44
|
-
export interface MenuDialogListProps
|
45
|
-
extends Omit<
|
46
|
-
BaseModalProps,
|
47
|
-
'isOpen' | 'onClose' | 'children' | 'scrollBehavior'
|
48
|
-
>,
|
49
|
-
Omit<MenuListProps, 'title'> {}
|
50
|
-
|
51
|
-
export const MenuDialogList = forwardRef<MenuDialogListProps, 'div'>(
|
52
|
-
(props, forwardedRef) => {
|
53
|
-
const {
|
54
|
-
rootProps,
|
55
|
-
title,
|
56
|
-
footer,
|
57
|
-
initialFocusRef,
|
58
|
-
hideCloseButton,
|
59
|
-
motionPreset = 'slideInBottom',
|
60
|
-
isCentered: isCenteredProp,
|
61
|
-
...rest
|
62
|
-
} = props
|
63
|
-
|
64
|
-
const { isOpen, onClose, menuRef } = useMenuContext()
|
65
|
-
|
66
|
-
const { ref, ...ownProps } = useMenuList(rest, forwardedRef)
|
67
|
-
|
68
|
-
const styles = useMultiStyleConfig('Menu', props)
|
69
|
-
|
70
|
-
const isCentered = useBreakpointValue({ base: true, md: false })
|
71
|
-
|
72
|
-
return (
|
73
|
-
<BaseModal
|
74
|
-
isOpen={isOpen}
|
75
|
-
onClose={onClose}
|
76
|
-
initialFocusRef={initialFocusRef || menuRef}
|
77
|
-
title={title}
|
78
|
-
hideCloseButton={hideCloseButton}
|
79
|
-
motionPreset={motionPreset}
|
80
|
-
isCentered={isCenteredProp ?? isCentered}
|
81
|
-
contentProps={{ mx: 4 }}
|
82
|
-
>
|
83
|
-
{/* We forward the styles again, otherwise the modal styles will be picked up */}
|
84
|
-
<StylesProvider value={styles}>
|
85
|
-
<chakra.div
|
86
|
-
{...ownProps}
|
87
|
-
ref={ref as React.Ref<HTMLDivElement>}
|
88
|
-
__css={{
|
89
|
-
outline: 0,
|
90
|
-
maxHeight: '80vh', // can override this in theme
|
91
|
-
overflowY: 'auto', // can override this in theme
|
92
|
-
...styles.list,
|
93
|
-
boxShadow: 'none',
|
94
|
-
border: 0,
|
95
|
-
_dark: {
|
96
|
-
/* @ts-expect-error */
|
97
|
-
...(styles.list._dark || {}),
|
98
|
-
boxShadow: 'none',
|
99
|
-
},
|
100
|
-
}}
|
101
|
-
/>
|
102
|
-
</StylesProvider>
|
103
|
-
{footer && <ModalFooter>{footer}</ModalFooter>}
|
104
|
-
</BaseModal>
|
105
|
-
)
|
106
|
-
}
|
107
|
-
)
|
package/src/provider.tsx
DELETED
@@ -1,357 +0,0 @@
|
|
1
|
-
import * as React from 'react'
|
2
|
-
|
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
|
30
|
-
drawer: (options: DrawerOptions) => ModalId
|
31
|
-
alert: (options: ConfirmDialogOptions) => ModalId
|
32
|
-
confirm: (options: ConfirmDialogOptions) => ModalId
|
33
|
-
menu: (options: MenuDialogOptions) => ModalId
|
34
|
-
form: FormDialogHandler<TModals['form']>
|
35
|
-
close: (id: ModalId) => void
|
36
|
-
closeAll: () => void
|
37
|
-
}
|
38
|
-
|
39
|
-
export const ModalsContext = React.createContext<ModalsContextValue<
|
40
|
-
typeof defaultModals
|
41
|
-
> | null>(null)
|
42
|
-
|
43
|
-
export interface ModalsProviderProps<
|
44
|
-
TModals extends Record<string, React.FC<any>> = Record<string, React.FC<any>>
|
45
|
-
> {
|
46
|
-
children: React.ReactNode
|
47
|
-
modals?: TModals
|
48
|
-
}
|
49
|
-
|
50
|
-
export type ModalId = string | number
|
51
|
-
|
52
|
-
type WithModalOptions<T> = Omit<T, 'isOpen' | 'onClose'> & ModalOptions
|
53
|
-
|
54
|
-
interface ModalOptions
|
55
|
-
extends Omit<BaseModalProps, 'isOpen' | 'onClose' | 'children'> {
|
56
|
-
onClose?: (args: { force?: boolean }) => Promise<boolean | undefined> | void
|
57
|
-
[key: string]: any
|
58
|
-
}
|
59
|
-
|
60
|
-
export interface DrawerOptions
|
61
|
-
extends ModalOptions,
|
62
|
-
Omit<DrawerProps, 'onClose' | 'isOpen' | 'children' | 'title' | 'size'> {}
|
63
|
-
|
64
|
-
export interface ConfirmDialogOptions
|
65
|
-
extends ModalOptions,
|
66
|
-
Omit<ConfirmDialogProps, 'onClose' | 'isOpen' | 'children'> {}
|
67
|
-
|
68
|
-
export interface MenuDialogOptions
|
69
|
-
extends ModalOptions,
|
70
|
-
Omit<MenuDialogProps, 'onClose' | 'isOpen' | 'children'> {}
|
71
|
-
|
72
|
-
export interface FormDialogOptions
|
73
|
-
extends ModalOptions,
|
74
|
-
Omit<FormDialogProps, 'onClose' | 'isOpen' | 'children'> {}
|
75
|
-
|
76
|
-
export interface OpenOptions<TModalTypes extends string> extends ModalOptions {
|
77
|
-
type?: TModalTypes
|
78
|
-
scope?: ModalScopes
|
79
|
-
}
|
80
|
-
|
81
|
-
export type ModalScopes = 'modal' | 'alert'
|
82
|
-
|
83
|
-
export interface ModalConfig<
|
84
|
-
TModalOptions extends ModalOptions = ModalOptions,
|
85
|
-
TModalTypes extends string = string
|
86
|
-
> {
|
87
|
-
/**
|
88
|
-
* The modal id, autogenerated when not set.
|
89
|
-
* Can be used to close modals.
|
90
|
-
*/
|
91
|
-
id?: ModalId | null
|
92
|
-
/**
|
93
|
-
* The modal props
|
94
|
-
*/
|
95
|
-
props?: TModalOptions | null
|
96
|
-
/**
|
97
|
-
* The modal scope
|
98
|
-
* Modals can only have one level per scope.
|
99
|
-
* The default scopes are 'modal' and 'alert', alerts can be openend above modals.
|
100
|
-
*/
|
101
|
-
scope?: ModalScopes | string
|
102
|
-
/**
|
103
|
-
* The modal type to open.
|
104
|
-
* Build in types are 'modal', 'drawer', 'alert', 'confirm'
|
105
|
-
*
|
106
|
-
* Custom types can be configured using the `modals` prop of `ModalProvider`
|
107
|
-
*/
|
108
|
-
type?: TModalTypes
|
109
|
-
/**
|
110
|
-
* Render a custom modal component.
|
111
|
-
* This will ignore the `type` param.
|
112
|
-
*/
|
113
|
-
component?: React.FC<BaseModalProps>
|
114
|
-
/**
|
115
|
-
* Whether the modal is open or not.
|
116
|
-
* This is used internally to keep track of the modal state.
|
117
|
-
*/
|
118
|
-
isOpen?: boolean
|
119
|
-
}
|
120
|
-
|
121
|
-
const initialModalState: ModalConfig = {
|
122
|
-
id: null,
|
123
|
-
props: null,
|
124
|
-
type: 'modal',
|
125
|
-
}
|
126
|
-
|
127
|
-
export function ModalsProvider({ children, modals }: ModalsProviderProps) {
|
128
|
-
// Note that updating the Set doesn't trigger a re-render,
|
129
|
-
// use in conjuction with setActiveModals
|
130
|
-
const _instances = React.useMemo(() => new Set<ModalConfig>(), [])
|
131
|
-
|
132
|
-
const [activeModals, setActiveModals] = React.useState<
|
133
|
-
Record<string, ModalConfig>
|
134
|
-
>({
|
135
|
-
modal: initialModalState,
|
136
|
-
})
|
137
|
-
|
138
|
-
const getModalComponent = React.useMemo(() => {
|
139
|
-
const _modals: Record<string, React.FC<any>> = {
|
140
|
-
...defaultModals,
|
141
|
-
...modals,
|
142
|
-
}
|
143
|
-
|
144
|
-
return (type = 'modal') => {
|
145
|
-
const component = _modals[type] || _modals.modal
|
146
|
-
|
147
|
-
return component
|
148
|
-
}
|
149
|
-
}, [modals])
|
150
|
-
|
151
|
-
const setActiveModal = (modal: ModalConfig, scope?: string) => {
|
152
|
-
if (!scope) {
|
153
|
-
return setActiveModals({
|
154
|
-
modal,
|
155
|
-
})
|
156
|
-
}
|
157
|
-
setActiveModals((prevState) => ({
|
158
|
-
...prevState,
|
159
|
-
[scope]: modal,
|
160
|
-
}))
|
161
|
-
}
|
162
|
-
|
163
|
-
const open = <T extends OpenOptions<any>>(
|
164
|
-
componentOrOptions: any,
|
165
|
-
options?: T extends React.FC<any>
|
166
|
-
? WithModalOptions<React.ComponentPropsWithRef<T>>
|
167
|
-
: never
|
168
|
-
): ModalId => {
|
169
|
-
let _options: ModalOptions
|
170
|
-
if (typeof componentOrOptions === 'function') {
|
171
|
-
_options = {
|
172
|
-
component: componentOrOptions,
|
173
|
-
...options,
|
174
|
-
} as unknown as T
|
175
|
-
} else {
|
176
|
-
_options = componentOrOptions
|
177
|
-
}
|
178
|
-
|
179
|
-
const {
|
180
|
-
id = _instances.size + 1,
|
181
|
-
type = 'modal',
|
182
|
-
scope = 'modal',
|
183
|
-
component,
|
184
|
-
...props
|
185
|
-
} = _options
|
186
|
-
|
187
|
-
const modal: ModalConfig<T> = {
|
188
|
-
id,
|
189
|
-
props: props as T,
|
190
|
-
type,
|
191
|
-
scope,
|
192
|
-
component,
|
193
|
-
isOpen: true,
|
194
|
-
}
|
195
|
-
|
196
|
-
_instances.add(modal)
|
197
|
-
setActiveModal(modal, scope)
|
198
|
-
|
199
|
-
return id
|
200
|
-
}
|
201
|
-
|
202
|
-
const drawer = (options: DrawerOptions) => {
|
203
|
-
return open<DrawerOptions>({
|
204
|
-
...options,
|
205
|
-
type: 'drawer',
|
206
|
-
})
|
207
|
-
}
|
208
|
-
|
209
|
-
const alert = (options: ConfirmDialogOptions) => {
|
210
|
-
return open({
|
211
|
-
...options,
|
212
|
-
scope: 'alert',
|
213
|
-
type: 'alert',
|
214
|
-
cancelProps: {
|
215
|
-
display: 'none',
|
216
|
-
},
|
217
|
-
confirmProps: {
|
218
|
-
label: 'OK',
|
219
|
-
},
|
220
|
-
leastDestructiveFocus: 'confirm',
|
221
|
-
})
|
222
|
-
}
|
223
|
-
|
224
|
-
const confirm = (options: ConfirmDialogOptions) => {
|
225
|
-
return open<ConfirmDialogOptions>({
|
226
|
-
...options,
|
227
|
-
scope: 'alert',
|
228
|
-
type: 'confirm',
|
229
|
-
})
|
230
|
-
}
|
231
|
-
|
232
|
-
const menu = (options: MenuDialogOptions) => {
|
233
|
-
return open<MenuDialogOptions>({
|
234
|
-
...options,
|
235
|
-
type: 'menu',
|
236
|
-
})
|
237
|
-
}
|
238
|
-
|
239
|
-
const form = (options: any) => {
|
240
|
-
return open({
|
241
|
-
...options,
|
242
|
-
type: 'form',
|
243
|
-
})
|
244
|
-
}
|
245
|
-
|
246
|
-
const close = async (id?: ModalId | null, force?: boolean) => {
|
247
|
-
const modals = [...Array.from(_instances)]
|
248
|
-
const modal = modals.filter((modal) => modal.id === id)[0]
|
249
|
-
|
250
|
-
if (!modal) {
|
251
|
-
return
|
252
|
-
}
|
253
|
-
|
254
|
-
const shouldClose = await modal.props?.onClose?.({ force })
|
255
|
-
if (shouldClose === false) {
|
256
|
-
return
|
257
|
-
}
|
258
|
-
|
259
|
-
const scoped = modals.filter(({ scope }) => scope === modal.scope)
|
260
|
-
|
261
|
-
if (scoped.length === 1) {
|
262
|
-
setActiveModal(
|
263
|
-
{
|
264
|
-
...modal,
|
265
|
-
isOpen: false,
|
266
|
-
},
|
267
|
-
modal.scope
|
268
|
-
)
|
269
|
-
} else if (scoped.length > 1) {
|
270
|
-
setActiveModal(scoped[scoped.length - 2], modal.scope)
|
271
|
-
} else {
|
272
|
-
setActiveModal(
|
273
|
-
{
|
274
|
-
id: null,
|
275
|
-
props: null,
|
276
|
-
type: modal.type, // Keep type same as last modal type to make sure the animation isn't interrupted
|
277
|
-
},
|
278
|
-
modal.scope
|
279
|
-
)
|
280
|
-
}
|
281
|
-
|
282
|
-
// @todo this is not ideal, but not all modals support onCloseComplete
|
283
|
-
setTimeout(() => closeComplete(id), 200)
|
284
|
-
}
|
285
|
-
|
286
|
-
const closeComplete = (id?: ModalId | null) => {
|
287
|
-
const modals = [...Array.from(_instances)]
|
288
|
-
const modal = modals.filter((modal) => modal.id === id)[0]
|
289
|
-
|
290
|
-
_instances.delete(modal)
|
291
|
-
|
292
|
-
const scoped = modal && modals.filter(({ scope }) => scope === modal.scope)
|
293
|
-
|
294
|
-
if (scoped?.length === 1) {
|
295
|
-
setActiveModal(initialModalState, modal.scope)
|
296
|
-
}
|
297
|
-
}
|
298
|
-
|
299
|
-
const closeAll = () => {
|
300
|
-
_instances.forEach((modal) => modal.props?.onClose?.({ force: true }))
|
301
|
-
_instances.clear()
|
302
|
-
|
303
|
-
setActiveModal(initialModalState)
|
304
|
-
}
|
305
|
-
|
306
|
-
const context = {
|
307
|
-
open,
|
308
|
-
drawer,
|
309
|
-
alert,
|
310
|
-
confirm,
|
311
|
-
menu,
|
312
|
-
form,
|
313
|
-
close,
|
314
|
-
closeAll,
|
315
|
-
}
|
316
|
-
|
317
|
-
const content = React.useMemo(
|
318
|
-
() =>
|
319
|
-
Object.entries(activeModals).map(([scope, config]) => {
|
320
|
-
const Component = config.component || getModalComponent(config.type)
|
321
|
-
|
322
|
-
const { title, body, children, ...props } = config.props || {}
|
323
|
-
|
324
|
-
return (
|
325
|
-
<Component
|
326
|
-
key={scope}
|
327
|
-
title={title}
|
328
|
-
children={body || children}
|
329
|
-
{...props}
|
330
|
-
isOpen={!!config.isOpen}
|
331
|
-
onClose={() => close(config.id)}
|
332
|
-
onCloseComplete={() => closeComplete(config.id)}
|
333
|
-
/>
|
334
|
-
)
|
335
|
-
}),
|
336
|
-
[activeModals]
|
337
|
-
)
|
338
|
-
|
339
|
-
return (
|
340
|
-
<ModalsContext.Provider value={context}>
|
341
|
-
{content}
|
342
|
-
{children}
|
343
|
-
</ModalsContext.Provider>
|
344
|
-
)
|
345
|
-
}
|
346
|
-
|
347
|
-
export const useModalsContext = () => React.useContext(ModalsContext)
|
348
|
-
|
349
|
-
export const useModals = () => {
|
350
|
-
const modals = useModalsContext()
|
351
|
-
|
352
|
-
if (!modals) {
|
353
|
-
throw new Error('useModals must be used within a ModalsProvider')
|
354
|
-
}
|
355
|
-
|
356
|
-
return modals
|
357
|
-
}
|