@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/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
- }