-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf: use zustand for shared portal area
- Loading branch information
1 parent
2e9e86b
commit 663668b
Showing
4 changed files
with
170 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { PortalProvider } from '@gorhom/portal' | ||
import { nanoid } from 'nanoid' | ||
import React, { useCallback, useEffect } from 'react' | ||
import { Dimensions } from 'react-native' | ||
import Animated, { CurvedTransition } from 'react-native-reanimated' | ||
import { useSafeAreaInsets } from 'react-native-safe-area-context' | ||
import { create } from 'zustand' | ||
|
||
import NativePortal from '../components/NativePortal' | ||
|
||
import type { PropsWithChildren } from 'react' | ||
import type { | ||
LayoutChangeEvent, LayoutRectangle, ViewStyle, StyleProp, Insets, | ||
} from 'react-native' | ||
|
||
type InsetsWithId = Required<Insets> & { readonly id: string } | ||
|
||
interface SharedPortalAreaStore { | ||
readonly allCustomInsets: readonly InsetsWithId[] | ||
readonly defaultInsets: Required<Insets> | ||
readonly setDefaultInsets: (defaultInsets: Required<Insets>) => void | ||
readonly insets: Required<Insets> | ||
readonly size: LayoutRectangle | ||
readonly pushInset: (insets: InsetsWithId) => void | ||
readonly removeInset: (id: string) => void | ||
readonly setSize: (size: LayoutRectangle) => void | ||
} | ||
|
||
function calculateInset(allCustomInsets: SharedPortalAreaStore['allCustomInsets'], defaultInsets: SharedPortalAreaStore['defaultInsets']) { | ||
// eslint-disable-next-line unicorn/prefer-at | ||
const lastInset = allCustomInsets[allCustomInsets.length - 1] | ||
return lastInset ? { ...defaultInsets, ...lastInset } : defaultInsets | ||
} | ||
|
||
const useSharedPortalArea = create<SharedPortalAreaStore>((set, get) => ({ | ||
allCustomInsets: [] as readonly InsetsWithId[], | ||
defaultInsets: DEFAULT_INSETS, | ||
setDefaultInsets: (defaultInsets: Required<Insets>) => set(() => ({ | ||
defaultInsets, | ||
insets: calculateInset(get().allCustomInsets, defaultInsets), | ||
})), | ||
insets: DEFAULT_INSETS, | ||
size: { | ||
x: 0, | ||
y: 0, | ||
width: Dimensions.get('window').width, | ||
height: 0, | ||
}, | ||
pushInset: (insets: InsetsWithId) => set((state) => { | ||
const allCustomInsets = [...state.allCustomInsets, insets] | ||
|
||
return { | ||
allCustomInsets, | ||
insets: calculateInset(allCustomInsets, state.defaultInsets), | ||
} | ||
}), | ||
removeInset: (id: string) => set((state) => { | ||
const allCustomInsets = state.allCustomInsets.filter(({ id: prevId }) => prevId !== id) | ||
|
||
return { | ||
allCustomInsets, | ||
insets: calculateInset(allCustomInsets, state.defaultInsets), | ||
} | ||
}), | ||
setSize: (size: LayoutRectangle) => set(() => ({ size })), | ||
})) | ||
|
||
const DEFAULT_INSETS = { | ||
top: 0, bottom: 0, left: 0, right: 0, | ||
} | ||
|
||
export const SharedPortalAreaProvider: React.FC<PropsWithChildren<{readonly insets?: Insets}>> = ({ children, insets }) => { | ||
const setDefaultInsets = useSharedPortalArea((state) => state.setDefaultInsets) | ||
|
||
useEffect(() => { | ||
setDefaultInsets(insets ? { ...DEFAULT_INSETS, ...insets } : DEFAULT_INSETS) | ||
}, [insets, setDefaultInsets]) | ||
|
||
return ( | ||
<PortalProvider> | ||
{children} | ||
</PortalProvider> | ||
) | ||
} | ||
|
||
// explicitely set all insets | ||
export const useUpdateSharedPortalAreaInsets = (insets: Required<Insets>, enable = true) => { | ||
const pushInset = useSharedPortalArea((state) => state.pushInset) | ||
const removeInset = useSharedPortalArea((state) => state.removeInset) | ||
|
||
useEffect(() => { | ||
if (enable) { | ||
const id = nanoid() | ||
pushInset({ ...insets, id }) | ||
return () => removeInset(id) | ||
} | ||
return () => {} | ||
}, [ | ||
enable, insets, pushInset, removeInset, | ||
]) | ||
} | ||
|
||
// Set insets, but with safe area as default | ||
export const useUpdateSharedPortalSafeAreaInsets = (insets: Insets, enable = true) => { | ||
const safeAreaInsets = useSafeAreaInsets() | ||
const pushInset = useSharedPortalArea((state) => state.pushInset) | ||
const removeInset = useSharedPortalArea((state) => state.removeInset) | ||
|
||
useEffect(() => { | ||
if (enable) { | ||
const id = nanoid() | ||
pushInset({ ...safeAreaInsets, ...insets, id }) | ||
return () => removeInset(id) | ||
} | ||
return () => {} | ||
}, [ | ||
safeAreaInsets, insets, enable, pushInset, removeInset, | ||
]) | ||
} | ||
|
||
type SharedPortalPresentationAreaProps = PropsWithChildren<{ readonly style?: StyleProp<ViewStyle>, readonly colorize?: boolean }> | ||
|
||
export const SharedPortalPresentationArea: React.FC<SharedPortalPresentationAreaProps> = ({ | ||
children, | ||
style, | ||
colorize, | ||
}) => { | ||
const insets = useSharedPortalArea((state) => state.insets) | ||
const setSize = useSharedPortalArea((state) => state.setSize) | ||
|
||
const onLayout = useCallback((event: LayoutChangeEvent) => { | ||
setSize(event.nativeEvent.layout) | ||
}, [setSize]) | ||
|
||
return ( | ||
<NativePortal insets={insets} colorize={colorize}> | ||
<Animated.View | ||
layout={CurvedTransition.duration(500)} | ||
onLayout={onLayout} | ||
style={style} | ||
pointerEvents='box-none' | ||
> | ||
{ children } | ||
</Animated.View> | ||
</NativePortal> | ||
) | ||
} | ||
|
||
export default useSharedPortalArea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18199,6 +18199,11 @@ use-latest-callback@^0.1.5: | |
dependencies: | ||
object-assign "^4.1.1" | ||
|
||
[email protected]: | ||
version "1.2.0" | ||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" | ||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== | ||
|
||
use@^3.1.0: | ||
version "3.1.1" | ||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" | ||
|
@@ -18413,8 +18418,10 @@ watchpack@^1.6.1, watchpack@^1.7.4: | |
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" | ||
integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== | ||
dependencies: | ||
chokidar "^3.4.1" | ||
graceful-fs "^4.1.2" | ||
neo-async "^2.5.0" | ||
watchpack-chokidar2 "^2.0.1" | ||
optionalDependencies: | ||
chokidar "^3.4.1" | ||
watchpack-chokidar2 "^2.0.1" | ||
|
@@ -19105,6 +19112,13 @@ yocto-queue@^0.1.0: | |
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" | ||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== | ||
|
||
zustand@^4.3.6: | ||
version "4.3.6" | ||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.6.tgz#ce7804eb75361af0461a2d0536b65461ec5de86f" | ||
integrity sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw== | ||
dependencies: | ||
use-sync-external-store "1.2.0" | ||
|
||
zwitch@^1.0.0: | ||
version "1.0.5" | ||
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" | ||
|