-
-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathHeadlessModal.jsx
127 lines (111 loc) · 4.93 KB
/
HeadlessModal.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { useMemo, useState, forwardRef, useImperativeHandle, useEffect, useRef } from 'react'
import { getConfig, getConfigByType } from './config'
import { useModalIndex } from './ModalRenderer.jsx'
import { useModalStack } from './ModalRoot.jsx'
import ModalRenderer from './ModalRenderer'
const HeadlessModal = forwardRef(({ name, children, ...props }, ref) => {
const modalIndex = useModalIndex()
const { stack, registerLocalModal, removeLocalModal } = useModalStack()
const [localModalContext, setLocalModalContext] = useState(null)
const modalContext = useMemo(() => (name ? localModalContext : stack[modalIndex]), [name, localModalContext, modalIndex, stack])
const nextIndex = useMemo(() => {
return stack.find((m) => m.shouldRender && m.index > modalContext?.index)?.index
}, [modalIndex, stack])
const configSlideover = useMemo(() => modalContext?.config.slideover ?? props.slideover ?? getConfig('type') === 'slideover', [props.slideover])
const config = useMemo(
() => ({
slideover: configSlideover,
closeButton: props.closeButton ?? getConfigByType(configSlideover, 'closeButton'),
closeExplicitly: props.closeExplicitly ?? getConfigByType(configSlideover, 'closeExplicitly'),
maxWidth: props.maxWidth ?? getConfigByType(configSlideover, 'maxWidth'),
paddingClasses: props.paddingClasses ?? getConfigByType(configSlideover, 'paddingClasses'),
panelClasses: props.panelClasses ?? getConfigByType(configSlideover, 'panelClasses'),
position: props.position ?? getConfigByType(configSlideover, 'position'),
...modalContext?.config,
}),
[props, modalContext?.config],
)
useEffect(() => {
if (name) {
let removeListeners = null
registerLocalModal(name, (localContext) => {
removeListeners = localContext.registerEventListenersFromProps(props)
setLocalModalContext(localContext)
})
return () => {
removeListeners?.()
removeListeners = null
removeLocalModal(name)
}
}
return modalContext.registerEventListenersFromProps(props)
}, [name])
// Store the latest modalContext in a ref to maintain reference
const modalContextRef = useRef(modalContext)
// Update the ref whenever modalContext changes
useEffect(() => {
modalContextRef.current = modalContext
}, [modalContext])
useImperativeHandle(
ref,
() => ({
afterLeave: () => modalContextRef.current?.afterLeave(),
close: () => modalContextRef.current?.close(),
emit: (...args) => modalContextRef.current?.emit(...args),
getChildModal: () => modalContextRef.current?.getChildModal(),
getParentModal: () => modalContextRef.current?.getParentModal(),
reload: (...args) => modalContextRef.current?.reload(...args),
setOpen: () => modalContextRef.current?.setOpen(),
get id() {
return modalContextRef.current?.id
},
get index() {
return modalContextRef.current?.index
},
get isOpen() {
return modalContextRef.current?.isOpen
},
get config() {
return modalContextRef.current?.config
},
get modalContext() {
return modalContextRef.current
},
get onTopOfStack() {
return modalContextRef.current?.onTopOfStack
},
get shouldRender() {
return modalContextRef.current?.shouldRender
},
}),
[modalContext],
)
return (
modalContext?.shouldRender && (
<>
{typeof children === 'function'
? children({
afterLeave: modalContext.afterLeave,
close: modalContext.close,
config,
emit: modalContext.emit,
getChildModal: modalContext.getChildModal,
getParentModal: modalContext.getParentModal,
id: modalContext.id,
index: modalContext.index,
isOpen: modalContext.isOpen,
modalContext,
onTopOfStack: modalContext.onTopOfStack,
reload: modalContext.reload,
setOpen: modalContext.setOpen,
shouldRender: modalContext.shouldRender,
})
: children}
{/* Next modal in the stack */}
{nextIndex && <ModalRenderer index={nextIndex} />}
</>
)
)
})
HeadlessModal.displayName = 'HeadlessModal'
export default HeadlessModal