Skip to content

Commit d84cb61

Browse files
committed
fix: modal root portal
Signed-off-by: Innei <tukon479@gmail.com>
1 parent ac95920 commit d84cb61

File tree

10 files changed

+92
-42
lines changed

10 files changed

+92
-42
lines changed

packages/kami-design/components/Portal/index.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import { createPortal } from 'react-dom'
44

55
import { useIsClient } from '~/hooks/use-is-client'
66

7-
export const RootPortal: FC = memo((props) => {
7+
import { useRootPortal } from './provider'
8+
9+
export const RootPortal: FC<{
10+
to?: HTMLElement
11+
}> = memo((props) => {
812
const isClient = useIsClient()
13+
const to = useRootPortal()
914
if (!isClient) {
1015
return null
1116
}
1217

13-
return createPortal(props.children, document.body)
18+
return createPortal(props.children, props.to || to || document.body)
1419
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createContext, useContext } from 'react'
2+
3+
import { isClientSide } from '~/utils/env'
4+
5+
export const useRootPortal = () => {
6+
const ctx = useContext(RootPortalContext)
7+
if (!isClientSide()) {
8+
return null
9+
}
10+
return ctx.to || document.body
11+
}
12+
13+
const RootPortalContext = createContext<{
14+
to?: HTMLElement | undefined
15+
}>({
16+
to: undefined,
17+
})
18+
export const RootPortalProvider = RootPortalContext.Provider

src/components/layouts/AppLayout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { loadStyleSheet } from '~/utils/load-script'
2222
import { useStore } from '../../store'
2323

2424
export const Content: FC = observer((props) => {
25-
const { userStore: master } = useStore()
25+
const { userStore: master, appUIStore } = useStore()
2626

2727
useScreenMedia()
2828
const { check: checkBrowser } = useCheckOldBrowser()
@@ -56,7 +56,7 @@ export const Content: FC = observer((props) => {
5656
}, [])
5757

5858
return (
59-
<ModalStackProvider>
59+
<ModalStackProvider isMobileSize={appUIStore.viewport.mobile}>
6060
<DynamicHeadMeta />
6161
<NextSeo
6262
title={`${initialData.seo.title} · ${initialData.seo.description}`}

src/components/layouts/DebugLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const DebugLayout: FC = (props) => {
1414
}
1515
}, [])
1616
return (
17-
<ModalStackProvider>
17+
<ModalStackProvider isMobileSize={false}>
1818
<div
1919
style={{
2020
maxWidth: '600px',

src/components/universal/Modal/Modal.tsx

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {
99
} from 'react'
1010

1111
import { CloseIcon } from '@mx-space/kami-design/components/Icons/layout'
12+
import { RootPortalProvider } from '@mx-space/kami-design/components/Portal/provider'
1213

14+
import { useIsMountedState } from '~/hooks/use-is-mounted'
1315
import { useStore } from '~/store'
1416

1517
import { BottomUpTransitionView } from '../Transition/bottom-up'
@@ -18,17 +20,18 @@ import { ScaleModalTransition } from './scale-transition'
1820

1921
export interface ModalProps {
2022
title?: string
21-
// TODO action
2223
closeable?: boolean
2324
onClose?: () => any
2425
modalClassName?: string
2526
contentClassName?: string
2627
noBlur?: boolean
2728
fixedWidth?: boolean
29+
useRootPortal?: boolean
2830
}
2931

3032
export type ModalRefObject = {
3133
dismiss: () => Promise<void>
34+
getElement: () => HTMLElement
3235
}
3336
export const Modal = observer(
3437
forwardRef<
@@ -50,8 +53,13 @@ export const Modal = observer(
5053
})
5154
}, [props.disposer])
5255

56+
const $wrapper = useRef<HTMLDivElement>(null)
57+
5358
useImperativeHandle(ref, () => ({
5459
dismiss,
60+
getElement: () => {
61+
return $wrapper.current as HTMLElement
62+
},
5563
}))
5664

5765
const {
@@ -60,10 +68,27 @@ export const Modal = observer(
6068
},
6169
} = useStore()
6270

63-
const { title, closeable } = props
71+
const { title, closeable, modalId, useRootPortal } = props
6472
const useDrawerStyle = mobile && props.useBottomDrawerInMobile
73+
74+
const isMounted = useIsMountedState()
75+
76+
const Content = (
77+
<div
78+
className={clsx(
79+
styles['content'],
80+
title && styles['has-title'],
81+
props.contentClassName,
82+
)}
83+
>
84+
{props.children}
85+
</div>
86+
)
87+
6588
const Children = (
6689
<div
90+
id={modalId}
91+
ref={$wrapper}
6792
className={clsx(
6893
styles['modal'],
6994
props.fixedWidth && styles['fixed-width'],
@@ -86,15 +111,15 @@ export const Modal = observer(
86111
<CloseIcon />
87112
</div>
88113
)}
89-
<div
90-
className={clsx(
91-
styles['content'],
92-
title && styles['has-title'],
93-
props.contentClassName,
94-
)}
95-
>
96-
{props.children}
97-
</div>
114+
{useRootPortal ? (
115+
isMounted ? (
116+
<RootPortalProvider value={{ to: $wrapper.current as HTMLElement }}>
117+
{Content}
118+
</RootPortalProvider>
119+
) : null
120+
) : (
121+
Content
122+
)}
98123
</div>
99124
)
100125

src/components/universal/Modal/stack-context.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { clsx } from 'clsx'
22
import uniqueId from 'lodash-es/uniqueId'
3-
import { observer } from 'mobx-react-lite'
43
import type {
54
FC,
65
FunctionComponentElement,
@@ -11,14 +10,14 @@ import type {
1110
import React, {
1211
createContext,
1312
createElement,
13+
memo,
1414
useContext,
1515
useRef,
1616
useState,
1717
} from 'react'
1818
import { useStateToRef } from 'react-shortcut-guide'
1919

2020
import { useIsClient } from '~/hooks/use-is-client'
21-
import { useStore } from '~/store'
2221

2322
import type { OverlayProps } from '../Overlay'
2423
import { Overlay } from '../Overlay'
@@ -93,9 +92,10 @@ interface IModalStackStateType extends UniversalProps {
9392
}
9493

9594
export const ModalStackProvider: FC<{
95+
isMobileSize: boolean
9696
children?: ReactNode | ReactChildren
97-
}> = observer((props) => {
98-
const { children } = props
97+
}> = memo((props) => {
98+
const { children, isMobileSize } = props
9999
const [modalStack, setModalStack] = useState<IModalStackStateType[]>([])
100100
const modalStackRef = useStateToRef(modalStack)
101101

@@ -229,13 +229,6 @@ export const ModalStackProvider: FC<{
229229

230230
const isClient = useIsClient()
231231

232-
// TODO 抽离 不再依赖
233-
const {
234-
appUIStore: {
235-
viewport: { mobile },
236-
},
237-
} = useStore()
238-
239232
return (
240233
<ModalStackContext.Provider
241234
value={
@@ -281,9 +274,11 @@ export const ModalStackProvider: FC<{
281274
dismissFnMapRef.current.set(Component, onClose)
282275
return (
283276
<Overlay
284-
center={!mobile && useBottomDrawerInMobile}
277+
center={!isMobileSize && useBottomDrawerInMobile}
285278
standaloneWrapperClassName={clsx(
286-
mobile && useBottomDrawerInMobile && 'items-end justify-center',
279+
isMobileSize &&
280+
useBottomDrawerInMobile &&
281+
'items-end justify-center',
287282
)}
288283
show={extraProps.overlayShow}
289284
onClose={() => disposer()}

src/components/widgets/Comment/box.tsx

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,6 @@ export const CommentBox: FC<{
289289
>
290290
<div className="flex-shrink-0 flex space-x-2 items-center">
291291
<FloatPopover
292-
popoverWrapperClassNames="z-100 relative"
293292
triggerComponent={
294293
useRef(() => (
295294
<button
@@ -301,15 +300,11 @@ export const CommentBox: FC<{
301300
)).current
302301
}
303302
>
304-
{
305-
useRef(
306-
<div className="leading-7">
307-
<p>评论支持部分 Markdown 语法</p>
308-
<p>评论可能被移入垃圾箱</p>
309-
<p>评论可能需要审核,审核通过后才会显示</p>
310-
</div>,
311-
).current
312-
}
303+
<div className="leading-7">
304+
<p>评论支持部分 Markdown 语法</p>
305+
<p>评论可能被移入垃圾箱</p>
306+
<p>评论可能需要审核,审核通过后才会显示</p>
307+
</div>
313308
</FloatPopover>
314309
<KaomojiButton onClickKaomoji={handleInsertEmoji} />
315310
</div>
@@ -392,8 +387,6 @@ const CommentBoxOption = observer<{ commentId?: string; refId: string }>(
392387
},
393388
)
394389

395-
// TODO root portal to and modal zIndex
396-
// popoverWrapperClassNames="z-100 relative"
397390
const KaomojiButton: FC<{ onClickKaomoji: (kaomoji: string) => any }> = memo(
398391
({ onClickKaomoji }) => {
399392
const { event } = useAnalyze()
@@ -406,7 +399,6 @@ const KaomojiButton: FC<{ onClickKaomoji: (kaomoji: string) => any }> = memo(
406399
<FloatPopover
407400
trigger="both"
408401
wrapperClassNames="flex-shrink-0"
409-
popoverWrapperClassNames="z-100 relative"
410402
triggerComponent={memo(() => (
411403
<button className="btn green mr-[12px] cursor-pointer">
412404
{randomKaomoji.current}

src/hooks/use-is-mounted.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useEffect, useState } from 'react'
2+
3+
export const useIsMountedState = () => {
4+
const [mounted, setMounted] = useState(false)
5+
6+
useEffect(() => {
7+
setMounted(true)
8+
}, [])
9+
return mounted
10+
}

src/pages/recently/index.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
border-radius: 25px;
6363
float: right;
6464

65+
:global(blockquote) {
66+
background: #3b82df;
67+
}
68+
6569
&::after {
6670
border-color: var(--bg);
6771
border-radius: 50%;

src/pages/recently/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ const RecentlyPage: NextPage = () => {
159159
title: '评论',
160160
closeable: true,
161161
fixedWidth: true,
162+
useRootPortal: true,
162163
},
163164
component: (
164165
<CommentLazy allowComment id={id} warpperClassName={'!mt-0'} />

0 commit comments

Comments
 (0)