@inertiaui/modal-react 2.1.0 → 2.1.2
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/LICENSE.md +21 -0
- package/dist/ModalLink.d.ts +1 -1
- package/dist/inertiaui-modal.js +1573 -1797
- package/dist/inertiaui-modal.js.map +1 -1
- package/dist/inertiaui-modal.umd.cjs +1670 -1853
- package/dist/inertiaui-modal.umd.cjs.map +1 -1
- package/dist/inertiauiModal.d.ts +3 -3
- package/dist/types.d.ts +1 -1
- package/package.json +40 -67
- package/src/CloseButton.tsx +3 -19
- package/src/Deferred.tsx +1 -0
- package/src/HeadlessModal.tsx +151 -155
- package/src/Modal.tsx +132 -144
- package/src/ModalContent.tsx +13 -14
- package/src/ModalLink.tsx +4 -3
- package/src/ModalRenderer.tsx +1 -0
- package/src/ModalRoot.tsx +11 -22
- package/src/SlideoverContent.tsx +14 -15
- package/src/WhenVisible.tsx +2 -1
- package/src/config.ts +3 -4
- package/src/inertiauiModal.ts +11 -10
- package/src/types.ts +1 -1
package/src/Modal.tsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { lockScroll, markAriaHidden } from '@inertiaui/vanilla'
|
|
1
2
|
import { forwardRef, useRef, useImperativeHandle, useState, useEffect, useCallback, useMemo, ReactNode } from 'react'
|
|
2
3
|
import { createPortal } from 'react-dom'
|
|
4
|
+
|
|
5
|
+
import { getConfig } from './config'
|
|
3
6
|
import HeadlessModal, { HeadlessModalRef } from './HeadlessModal'
|
|
4
7
|
import ModalContent from './ModalContent'
|
|
5
8
|
import SlideoverContent from './SlideoverContent'
|
|
6
|
-
import { lockScroll, markAriaHidden } from '@inertiaui/vanilla'
|
|
7
|
-
import { getConfig } from './config'
|
|
8
9
|
import type { Modal as ModalType, ReloadOptions } from './types'
|
|
9
10
|
|
|
10
11
|
interface ModalConfig {
|
|
@@ -61,158 +62,145 @@ interface BackdropTransitionProps {
|
|
|
61
62
|
onAfterAppear?: () => void
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
const Modal = forwardRef<HeadlessModalRef, ModalProps>(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return children(contentProps)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return children
|
|
65
|
+
const Modal = forwardRef<HeadlessModalRef, ModalProps>((allProps, ref) => {
|
|
66
|
+
const { name, children, onFocus, onBlur, onClose, onSuccess, onAfterLeave, ...props } = allProps as ModalBaseProps & Record<string, unknown>
|
|
67
|
+
const renderChildren = (contentProps: ModalRenderProps) => {
|
|
68
|
+
if (typeof children === 'function') {
|
|
69
|
+
return children(contentProps)
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const cleanupAriaHiddenRef = useRef<(() => void) | null>(null)
|
|
78
|
-
const [rendered, setRendered] = useState(false)
|
|
79
|
-
const useNativeDialog = useMemo(() => getConfig('useNativeDialog') as boolean, [])
|
|
72
|
+
return children
|
|
73
|
+
}
|
|
80
74
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
cleanupScrollLockRef.current?.()
|
|
87
|
-
cleanupAriaHiddenRef.current?.()
|
|
88
|
-
}
|
|
89
|
-
}, [])
|
|
75
|
+
const headlessModalRef = useRef<HeadlessModalRef>(null)
|
|
76
|
+
const cleanupScrollLockRef = useRef<(() => void) | null>(null)
|
|
77
|
+
const cleanupAriaHiddenRef = useRef<(() => void) | null>(null)
|
|
78
|
+
const [rendered, setRendered] = useState(false)
|
|
79
|
+
const useNativeDialog = useMemo(() => getConfig('useNativeDialog') as boolean, [])
|
|
90
80
|
|
|
91
|
-
|
|
92
|
-
onSuccess?.()
|
|
93
|
-
if (!cleanupScrollLockRef.current) {
|
|
94
|
-
cleanupScrollLockRef.current = lockScroll()
|
|
95
|
-
cleanupAriaHiddenRef.current = markAriaHidden(getConfig('appElement') as string)
|
|
96
|
-
}
|
|
97
|
-
}, [onSuccess])
|
|
81
|
+
useImperativeHandle(ref, () => headlessModalRef.current!, [headlessModalRef])
|
|
98
82
|
|
|
99
|
-
|
|
100
|
-
|
|
83
|
+
// Cleanup on unmount
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
return () => {
|
|
101
86
|
cleanupScrollLockRef.current?.()
|
|
102
87
|
cleanupAriaHiddenRef.current?.()
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}, [onClose])
|
|
88
|
+
}
|
|
89
|
+
}, [])
|
|
106
90
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
const handleSuccess = useCallback(() => {
|
|
92
|
+
onSuccess?.()
|
|
93
|
+
if (!cleanupScrollLockRef.current) {
|
|
94
|
+
cleanupScrollLockRef.current = lockScroll()
|
|
95
|
+
cleanupAriaHiddenRef.current = markAriaHidden(getConfig('appElement') as string)
|
|
96
|
+
}
|
|
97
|
+
}, [onSuccess])
|
|
110
98
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
onSuccess={handleSuccess}
|
|
119
|
-
{...props}
|
|
120
|
-
>
|
|
121
|
-
{({
|
|
122
|
-
afterLeave,
|
|
123
|
-
close,
|
|
124
|
-
config,
|
|
125
|
-
emit,
|
|
126
|
-
getChildModal,
|
|
127
|
-
getParentModal,
|
|
128
|
-
id,
|
|
129
|
-
index,
|
|
130
|
-
isOpen,
|
|
131
|
-
modalContext,
|
|
132
|
-
onTopOfStack,
|
|
133
|
-
reload,
|
|
134
|
-
setOpen,
|
|
135
|
-
shouldRender,
|
|
136
|
-
...extraProps
|
|
137
|
-
}) => (
|
|
138
|
-
<ModalPortal>
|
|
139
|
-
<div
|
|
140
|
-
className="im-dialog relative z-20"
|
|
141
|
-
data-inertiaui-modal-id={id}
|
|
142
|
-
data-inertiaui-modal-index={index}
|
|
143
|
-
aria-hidden={!onTopOfStack}
|
|
144
|
-
>
|
|
145
|
-
{/* Only render backdrop for the first modal (non-native dialog mode) */}
|
|
146
|
-
{/* Native dialog uses ::backdrop pseudo-element instead */}
|
|
147
|
-
{index === 0 && !useNativeDialog && (
|
|
148
|
-
<BackdropTransition
|
|
149
|
-
show={isOpen}
|
|
150
|
-
appear={!rendered}
|
|
151
|
-
onAfterAppear={() => setRendered(true)}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
99
|
+
const handleClose = useCallback(() => {
|
|
100
|
+
onClose?.()
|
|
101
|
+
cleanupScrollLockRef.current?.()
|
|
102
|
+
cleanupAriaHiddenRef.current?.()
|
|
103
|
+
cleanupScrollLockRef.current = null
|
|
104
|
+
cleanupAriaHiddenRef.current = null
|
|
105
|
+
}, [onClose])
|
|
154
106
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
107
|
+
const handleAfterLeave = useCallback(() => {
|
|
108
|
+
onAfterLeave?.()
|
|
109
|
+
}, [onAfterLeave])
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<HeadlessModal
|
|
113
|
+
ref={headlessModalRef}
|
|
114
|
+
name={name}
|
|
115
|
+
onFocus={onFocus ?? undefined}
|
|
116
|
+
onBlur={onBlur ?? undefined}
|
|
117
|
+
onClose={handleClose}
|
|
118
|
+
onSuccess={handleSuccess}
|
|
119
|
+
{...props}
|
|
120
|
+
>
|
|
121
|
+
{({
|
|
122
|
+
afterLeave,
|
|
123
|
+
close,
|
|
124
|
+
config,
|
|
125
|
+
emit,
|
|
126
|
+
getChildModal,
|
|
127
|
+
getParentModal,
|
|
128
|
+
id,
|
|
129
|
+
index,
|
|
130
|
+
isOpen,
|
|
131
|
+
modalContext,
|
|
132
|
+
onTopOfStack,
|
|
133
|
+
reload,
|
|
134
|
+
setOpen,
|
|
135
|
+
shouldRender,
|
|
136
|
+
...extraProps
|
|
137
|
+
}) => (
|
|
138
|
+
<ModalPortal>
|
|
139
|
+
<div className="im-dialog relative z-20" data-inertiaui-modal-id={id} data-inertiaui-modal-index={index} aria-hidden={!onTopOfStack}>
|
|
140
|
+
{/* Only render backdrop for the first modal (non-native dialog mode) */}
|
|
141
|
+
{/* Native dialog uses ::backdrop pseudo-element instead */}
|
|
142
|
+
{index === 0 && !useNativeDialog && <BackdropTransition show={isOpen} appear={!rendered} onAfterAppear={() => setRendered(true)} />}
|
|
143
|
+
|
|
144
|
+
{/* The modal/slideover content itself */}
|
|
145
|
+
{config.slideover ? (
|
|
146
|
+
<SlideoverContent
|
|
147
|
+
modalContext={modalContext}
|
|
148
|
+
config={config}
|
|
149
|
+
useNativeDialog={useNativeDialog}
|
|
150
|
+
isFirstModal={index === 0}
|
|
151
|
+
onAfterLeave={handleAfterLeave}
|
|
152
|
+
>
|
|
153
|
+
{renderChildren({
|
|
154
|
+
...extraProps,
|
|
155
|
+
afterLeave,
|
|
156
|
+
close,
|
|
157
|
+
config,
|
|
158
|
+
emit,
|
|
159
|
+
getChildModal,
|
|
160
|
+
getParentModal,
|
|
161
|
+
id,
|
|
162
|
+
index,
|
|
163
|
+
isOpen,
|
|
164
|
+
modalContext,
|
|
165
|
+
onTopOfStack,
|
|
166
|
+
reload,
|
|
167
|
+
setOpen,
|
|
168
|
+
shouldRender,
|
|
169
|
+
})}
|
|
170
|
+
</SlideoverContent>
|
|
171
|
+
) : (
|
|
172
|
+
<ModalContent
|
|
173
|
+
modalContext={modalContext}
|
|
174
|
+
config={config}
|
|
175
|
+
useNativeDialog={useNativeDialog}
|
|
176
|
+
isFirstModal={index === 0}
|
|
177
|
+
onAfterLeave={handleAfterLeave}
|
|
178
|
+
>
|
|
179
|
+
{renderChildren({
|
|
180
|
+
...extraProps,
|
|
181
|
+
afterLeave,
|
|
182
|
+
close,
|
|
183
|
+
config,
|
|
184
|
+
emit,
|
|
185
|
+
getChildModal,
|
|
186
|
+
getParentModal,
|
|
187
|
+
id,
|
|
188
|
+
index,
|
|
189
|
+
isOpen,
|
|
190
|
+
modalContext,
|
|
191
|
+
onTopOfStack,
|
|
192
|
+
reload,
|
|
193
|
+
setOpen,
|
|
194
|
+
shouldRender,
|
|
195
|
+
})}
|
|
196
|
+
</ModalContent>
|
|
197
|
+
)}
|
|
198
|
+
</div>
|
|
199
|
+
</ModalPortal>
|
|
200
|
+
)}
|
|
201
|
+
</HeadlessModal>
|
|
202
|
+
)
|
|
203
|
+
})
|
|
216
204
|
|
|
217
205
|
// Simple portal component
|
|
218
206
|
function ModalPortal({ children }: { children: ReactNode }) {
|
package/src/ModalContent.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
2
|
+
import clsx from 'clsx'
|
|
1
3
|
import { useState, useEffect, useRef, useCallback, useMemo, ReactNode, SyntheticEvent, MouseEvent } from 'react'
|
|
4
|
+
|
|
2
5
|
import CloseButton from './CloseButton'
|
|
3
|
-
import clsx from 'clsx'
|
|
4
|
-
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
5
6
|
import { getMaxWidthClass } from './constants'
|
|
6
7
|
import type { Modal } from './types'
|
|
7
8
|
|
|
@@ -26,8 +27,8 @@ interface ModalContentProps {
|
|
|
26
27
|
|
|
27
28
|
const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onAfterLeave, children }: ModalContentProps) => {
|
|
28
29
|
const [isRendered, setIsRendered] = useState(false)
|
|
29
|
-
const [isVisible, setIsVisible] = useState(false)
|
|
30
|
-
const [entered, setEntered] = useState(false)
|
|
30
|
+
const [isVisible, setIsVisible] = useState(false) // For backdrop sync
|
|
31
|
+
const [entered, setEntered] = useState(false) // After animation completes
|
|
31
32
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
32
33
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
33
34
|
const nativeWrapperRef = useRef<HTMLDivElement>(null)
|
|
@@ -230,12 +231,9 @@ const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onA
|
|
|
230
231
|
// ============ Render ============
|
|
231
232
|
|
|
232
233
|
const renderContent = () => (
|
|
233
|
-
<div
|
|
234
|
-
className={`im-modal-content relative ${config.paddingClasses} ${config.panelClasses}`}
|
|
235
|
-
data-inertiaui-modal-entered={entered}
|
|
236
|
-
>
|
|
234
|
+
<div className={`im-modal-content relative ${config.paddingClasses} ${config.panelClasses}`} data-inertiaui-modal-entered={entered}>
|
|
237
235
|
{config.closeButton && (
|
|
238
|
-
<div className="absolute
|
|
236
|
+
<div className="absolute top-0 right-0 pt-3 pr-3">
|
|
239
237
|
<CloseButton onClick={modalContext.close} />
|
|
240
238
|
</div>
|
|
241
239
|
)}
|
|
@@ -268,7 +266,11 @@ const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onA
|
|
|
268
266
|
>
|
|
269
267
|
<div
|
|
270
268
|
ref={nativeWrapperRef}
|
|
271
|
-
className={clsx(
|
|
269
|
+
className={clsx(
|
|
270
|
+
'im-modal-wrapper w-full transition-[filter] duration-300',
|
|
271
|
+
modalContext.onTopOfStack ? '' : 'blur-xs',
|
|
272
|
+
maxWidthClass,
|
|
273
|
+
)}
|
|
272
274
|
>
|
|
273
275
|
{renderContent()}
|
|
274
276
|
</div>
|
|
@@ -282,10 +284,7 @@ const ModalContent = ({ modalContext, config, useNativeDialog, isFirstModal, onA
|
|
|
282
284
|
if (!isRendered) return null
|
|
283
285
|
|
|
284
286
|
return (
|
|
285
|
-
<div
|
|
286
|
-
className="im-modal-container fixed inset-0 z-40 overflow-y-auto p-4"
|
|
287
|
-
onMouseDown={handleClickOutside}
|
|
288
|
-
>
|
|
287
|
+
<div className="im-modal-container fixed inset-0 z-40 overflow-y-auto p-4" onMouseDown={handleClickOutside}>
|
|
289
288
|
<div
|
|
290
289
|
className={clsx('im-modal-positioner flex min-h-full justify-center', {
|
|
291
290
|
'items-start': config.position === 'top',
|
package/src/ModalLink.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { RequestPayload } from '@inertiajs/core'
|
|
1
2
|
import { useCallback, useState, useEffect, useMemo, useRef, ReactNode, ElementType, MouseEvent } from 'react'
|
|
2
|
-
|
|
3
|
-
import { only, rejectNullValues, isStandardDomEvent } from './helpers'
|
|
3
|
+
|
|
4
4
|
import { getConfig } from './config'
|
|
5
|
+
import { only, rejectNullValues, isStandardDomEvent } from './helpers'
|
|
6
|
+
import { useModalStack, modalPropNames, prefetch as prefetchModal } from './ModalRoot'
|
|
5
7
|
import type { Modal, PrefetchOption } from './types'
|
|
6
|
-
import type { RequestPayload } from '@inertiajs/core'
|
|
7
8
|
|
|
8
9
|
interface ModalLinkProps {
|
|
9
10
|
href: string
|
package/src/ModalRenderer.tsx
CHANGED
package/src/ModalRoot.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { createElement, useEffect, useState, useRef, useReducer, ReactNode, ComponentType } from 'react'
|
|
2
|
-
import { default as Axios, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
|
3
|
-
import { except, kebabCase, generateId, sameUrlPath } from './helpers'
|
|
4
|
-
import { router, usePage, progress } from '@inertiajs/react'
|
|
5
1
|
import { mergeDataIntoQueryString, type RequestPayload } from '@inertiajs/core'
|
|
2
|
+
import { router, usePage, progress } from '@inertiajs/react'
|
|
3
|
+
import { default as Axios, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
|
|
4
|
+
import { createElement, useEffect, useState, useRef, useReducer, ReactNode, ComponentType } from 'react'
|
|
6
5
|
import { createContext, useContext } from 'react'
|
|
7
|
-
|
|
6
|
+
|
|
8
7
|
import { getConfig } from './config'
|
|
8
|
+
import { except, kebabCase, generateId, sameUrlPath } from './helpers'
|
|
9
|
+
import ModalRenderer from './ModalRenderer'
|
|
9
10
|
import type {
|
|
10
11
|
Modal,
|
|
11
12
|
ModalConfig,
|
|
@@ -380,7 +381,7 @@ export const ModalStackProvider = ({ children }: ModalStackProviderProps) => {
|
|
|
380
381
|
data: method === 'get' ? {} : data,
|
|
381
382
|
params: method === 'get' ? data : {},
|
|
382
383
|
headers: {
|
|
383
|
-
...
|
|
384
|
+
...options.headers,
|
|
384
385
|
Accept: 'text/html, application/xhtml+xml',
|
|
385
386
|
'X-Inertia': 'true',
|
|
386
387
|
'X-Inertia-Partial-Component': this.response.component,
|
|
@@ -410,12 +411,7 @@ export const ModalStackProvider = ({ children }: ModalStackProviderProps) => {
|
|
|
410
411
|
}
|
|
411
412
|
|
|
412
413
|
const isValidModalResponse = (data: unknown): data is ModalResponseData => {
|
|
413
|
-
return (
|
|
414
|
-
typeof data === 'object' &&
|
|
415
|
-
data !== null &&
|
|
416
|
-
'component' in data &&
|
|
417
|
-
typeof (data as ModalResponseData).component === 'string'
|
|
418
|
-
)
|
|
414
|
+
return typeof data === 'object' && data !== null && 'component' in data && typeof (data as ModalResponseData).component === 'string'
|
|
419
415
|
}
|
|
420
416
|
|
|
421
417
|
const pushFromResponseData = (
|
|
@@ -437,9 +433,7 @@ export const ModalStackProvider = ({ children }: ModalStackProviderProps) => {
|
|
|
437
433
|
)
|
|
438
434
|
}
|
|
439
435
|
|
|
440
|
-
return resolveComponent(responseData.component).then((component) =>
|
|
441
|
-
push(component, responseData, config, onClose, onAfterLeave),
|
|
442
|
-
)
|
|
436
|
+
return resolveComponent(responseData.component).then((component) => push(component, responseData, config, onClose, onAfterLeave))
|
|
443
437
|
}
|
|
444
438
|
|
|
445
439
|
const loadDeferredProps = (modal: Modal) => {
|
|
@@ -704,10 +698,7 @@ export const renderApp = (App: ComponentType<{ children: (props: RenderInertiaAp
|
|
|
704
698
|
return Component.layout
|
|
705
699
|
.slice()
|
|
706
700
|
.reverse()
|
|
707
|
-
.reduce(
|
|
708
|
-
(acc, Layout) => createElement(Layout as ComponentType<Record<string, unknown>>, props, acc),
|
|
709
|
-
child as ReactNode,
|
|
710
|
-
)
|
|
701
|
+
.reduce((acc, Layout) => createElement(Layout as ComponentType<Record<string, unknown>>, props, acc), child as ReactNode)
|
|
711
702
|
}
|
|
712
703
|
|
|
713
704
|
return child
|
|
@@ -866,9 +857,7 @@ export const ModalRoot = ({ children }: ModalRootProps) => {
|
|
|
866
857
|
// If there's no previous modal but we have modals in the stack (opened via XHR),
|
|
867
858
|
// check if the new modal matches any open modal and update its props
|
|
868
859
|
if (!previousModal && context && context.stack.length > 0) {
|
|
869
|
-
const existingModal = context.stack.find(
|
|
870
|
-
(m) => m.response?.component === newModal.component && sameUrlPath(m.response?.url, newModal.url),
|
|
871
|
-
)
|
|
860
|
+
const existingModal = context.stack.find((m) => m.response?.component === newModal.component && sameUrlPath(m.response?.url, newModal.url))
|
|
872
861
|
if (existingModal) {
|
|
873
862
|
existingModal.updateProps(newModal.props ?? {})
|
|
874
863
|
}
|
package/src/SlideoverContent.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
2
|
+
import clsx from 'clsx'
|
|
1
3
|
import { useState, useEffect, useRef, useCallback, useMemo, ReactNode, SyntheticEvent, MouseEvent } from 'react'
|
|
4
|
+
|
|
2
5
|
import CloseButton from './CloseButton'
|
|
3
|
-
import clsx from 'clsx'
|
|
4
|
-
import { createFocusTrap, onEscapeKey, animate, cancelAnimations } from '@inertiaui/vanilla'
|
|
5
6
|
import { getMaxWidthClass } from './constants'
|
|
6
7
|
import type { Modal } from './types'
|
|
7
8
|
|
|
@@ -26,8 +27,8 @@ interface SlideoverContentProps {
|
|
|
26
27
|
|
|
27
28
|
const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal, onAfterLeave, children }: SlideoverContentProps) => {
|
|
28
29
|
const [isRendered, setIsRendered] = useState(false)
|
|
29
|
-
const [isVisible, setIsVisible] = useState(false)
|
|
30
|
-
const [entered, setEntered] = useState(false)
|
|
30
|
+
const [isVisible, setIsVisible] = useState(false) // For backdrop sync
|
|
31
|
+
const [entered, setEntered] = useState(false) // After animation completes
|
|
31
32
|
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
32
33
|
const dialogRef = useRef<HTMLDialogElement>(null)
|
|
33
34
|
const nativeWrapperRef = useRef<HTMLDivElement>(null)
|
|
@@ -240,12 +241,9 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
240
241
|
// ============ Render ============
|
|
241
242
|
|
|
242
243
|
const renderContent = () => (
|
|
243
|
-
<div
|
|
244
|
-
className={`im-slideover-content relative ${config.paddingClasses} ${config.panelClasses}`}
|
|
245
|
-
data-inertiaui-modal-entered={entered}
|
|
246
|
-
>
|
|
244
|
+
<div className={`im-slideover-content relative ${config.paddingClasses} ${config.panelClasses}`} data-inertiaui-modal-entered={entered}>
|
|
247
245
|
{config.closeButton && (
|
|
248
|
-
<div className="absolute
|
|
246
|
+
<div className="absolute top-0 right-0 pt-3 pr-3">
|
|
249
247
|
<CloseButton onClick={modalContext.close} />
|
|
250
248
|
</div>
|
|
251
249
|
)}
|
|
@@ -268,7 +266,7 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
268
266
|
onCancel={handleCancel}
|
|
269
267
|
onClick={handleDialogClick}
|
|
270
268
|
>
|
|
271
|
-
<div className="im-slideover-container fixed inset-0 overflow-
|
|
269
|
+
<div className="im-slideover-container fixed inset-0 overflow-x-hidden overflow-y-auto">
|
|
272
270
|
<div
|
|
273
271
|
className={clsx('im-slideover-positioner flex min-h-full items-center', {
|
|
274
272
|
'justify-start rtl:justify-end': config?.position === 'left',
|
|
@@ -277,7 +275,11 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
277
275
|
>
|
|
278
276
|
<div
|
|
279
277
|
ref={nativeWrapperRef}
|
|
280
|
-
className={clsx(
|
|
278
|
+
className={clsx(
|
|
279
|
+
'im-slideover-wrapper w-full transition-[filter] duration-300',
|
|
280
|
+
modalContext.onTopOfStack ? '' : 'blur-xs',
|
|
281
|
+
maxWidthClass,
|
|
282
|
+
)}
|
|
281
283
|
>
|
|
282
284
|
{renderContent()}
|
|
283
285
|
</div>
|
|
@@ -291,10 +293,7 @@ const SlideoverContent = ({ modalContext, config, useNativeDialog, isFirstModal,
|
|
|
291
293
|
if (!isRendered) return null
|
|
292
294
|
|
|
293
295
|
return (
|
|
294
|
-
<div
|
|
295
|
-
className="im-slideover-container fixed inset-0 z-40 overflow-y-auto overflow-x-hidden"
|
|
296
|
-
onMouseDown={handleClickOutside}
|
|
297
|
-
>
|
|
296
|
+
<div className="im-slideover-container fixed inset-0 z-40 overflow-x-hidden overflow-y-auto" onMouseDown={handleClickOutside}>
|
|
298
297
|
<div
|
|
299
298
|
className={clsx('im-slideover-positioner flex min-h-full items-center', {
|
|
300
299
|
'justify-start rtl:justify-end': config?.position === 'left',
|
package/src/WhenVisible.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// See: https://github.com/inertiajs/inertia/blob/48bcd21fb7daf467d0df1bfde2408f161f94a579/packages/react/src/WhenVisible.ts
|
|
2
2
|
import { createElement, useCallback, useEffect, useRef, useState, ReactNode, ElementType } from 'react'
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import type { ReloadOptions } from './types'
|
|
5
|
+
import useModal from './useModal'
|
|
5
6
|
|
|
6
7
|
interface WhenVisibleProps {
|
|
7
8
|
children: ReactNode
|
package/src/config.ts
CHANGED
|
@@ -61,8 +61,8 @@ class Config {
|
|
|
61
61
|
navigate: key.navigate ?? defaultConfig.navigate,
|
|
62
62
|
useNativeDialog: key.useNativeDialog ?? defaultConfig.useNativeDialog,
|
|
63
63
|
appElement: key.appElement !== undefined ? key.appElement : defaultConfig.appElement,
|
|
64
|
-
modal: { ...defaultConfig.modal, ...
|
|
65
|
-
slideover: { ...defaultConfig.slideover, ...
|
|
64
|
+
modal: { ...defaultConfig.modal, ...key.modal },
|
|
65
|
+
slideover: { ...defaultConfig.slideover, ...key.slideover },
|
|
66
66
|
}
|
|
67
67
|
return
|
|
68
68
|
}
|
|
@@ -95,5 +95,4 @@ const configInstance = new Config()
|
|
|
95
95
|
export const resetConfig = (): void => configInstance.reset()
|
|
96
96
|
export const putConfig = (key: string | Partial<ModalConfig>, value?: unknown): void => configInstance.put(key, value)
|
|
97
97
|
export const getConfig = (key?: string): unknown => configInstance.get(key)
|
|
98
|
-
export const getConfigByType = (isSlideover: boolean, key: string): unknown =>
|
|
99
|
-
configInstance.get(isSlideover ? `slideover.${key}` : `modal.${key}`)
|
|
98
|
+
export const getConfigByType = (isSlideover: boolean, key: string): unknown => configInstance.get(isSlideover ? `slideover.${key}` : `modal.${key}`)
|
package/src/inertiauiModal.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
import * as dialogUtils from '@inertiaui/vanilla'
|
|
1
2
|
import { createElement } from 'react'
|
|
3
|
+
|
|
2
4
|
import { getConfig, putConfig, resetConfig } from './config'
|
|
3
|
-
import { useModalIndex } from './ModalRenderer'
|
|
4
|
-
import { useModalStack, ModalRoot, ModalStackProvider, renderApp, initFromPageProps, modalPropNames, prefetch } from './ModalRoot'
|
|
5
|
-
import useModal from './useModal'
|
|
6
5
|
import Deferred from './Deferred'
|
|
7
6
|
import HeadlessModal from './HeadlessModal'
|
|
8
7
|
import Modal from './Modal'
|
|
9
8
|
import ModalLink from './ModalLink'
|
|
9
|
+
import { useModalIndex } from './ModalRenderer'
|
|
10
|
+
import { useModalStack, ModalRoot, ModalStackProvider, renderApp, initFromPageProps, modalPropNames, prefetch } from './ModalRoot'
|
|
11
|
+
import useModal from './useModal'
|
|
10
12
|
import WhenVisible from './WhenVisible'
|
|
11
|
-
import * as dialogUtils from '@inertiaui/vanilla'
|
|
12
13
|
|
|
13
14
|
// Types
|
|
14
15
|
export type {
|
|
@@ -33,12 +34,12 @@ export type { ModalTypeConfig } from './config'
|
|
|
33
34
|
|
|
34
35
|
export type { CleanupFunction, FocusTrapOptions, EscapeKeyOptions } from '@inertiaui/vanilla'
|
|
35
36
|
|
|
36
|
-
const setPageLayout =
|
|
37
|
-
layout: React.ComponentType<{ children?: React.ReactNode }
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
37
|
+
const setPageLayout =
|
|
38
|
+
<T extends { default: { layout?: (page: React.ReactNode) => React.ReactNode } }>(layout: React.ComponentType<{ children?: React.ReactNode }>) =>
|
|
39
|
+
(module: T): T => {
|
|
40
|
+
module.default.layout = (page) => createElement(layout, { children: page })
|
|
41
|
+
return module
|
|
42
|
+
}
|
|
42
43
|
|
|
43
44
|
export {
|
|
44
45
|
Deferred,
|
package/src/types.ts
CHANGED