| 1 | import type { ReactNode, RefObject } from "react"; |
| 2 | import { createContext, useContext, useMemo, useRef, useState } from "react"; |
| 3 | import { View, type ViewInstance } from "react-native"; |
| 4 | |
| 5 | type ComposerPopoverHostValue = { |
| 6 | readonly hostRef: RefObject<ViewInstance | null>; |
| 7 | readonly setContent: (content: ReactNode) => void; |
| 8 | /** Changes when the host resizes, which moves the bottom-anchored composer. */ |
| 9 | readonly layoutVersion: number; |
| 10 | }; |
| 11 | |
| 12 | const ComposerPopoverHostContext = createContext<ComposerPopoverHostValue | null>(null); |
| 13 | |
| 14 | export function useComposerPopoverHost() { |
| 15 | return useContext(ComposerPopoverHostContext); |
| 16 | } |
| 17 | |
| 18 | /** |
| 19 | * Full-size layer around the composer for its popovers. Mount it inside the |
| 20 | * composer's KeyboardStickyView: popovers placed here move with the composer |
| 21 | * and sit inside a parent that covers them, so Android delivers their scroll |
| 22 | * gestures. It wraps the composer because measureLayout needs an ancestor. |
| 23 | * Pass `hidden` whenever the composer itself is hidden: popovers live outside |
| 24 | * the composer's subtree, so they don't hide with it. |
| 25 | */ |
| 26 | export function ComposerPopoverHost(props: { |
| 27 | readonly hidden: boolean; |
| 28 | readonly children: ReactNode; |
| 29 | }) { |
| 30 | const hostRef = useRef<ViewInstance>(null); |
| 31 | const [content, setContent] = useState<ReactNode>(null); |
| 32 | const [layoutVersion, setLayoutVersion] = useState(0); |
| 33 | const value = useMemo(() => ({ hostRef, setContent, layoutVersion }), [layoutVersion]); |
| 34 | |
| 35 | return ( |
| 36 | <ComposerPopoverHostContext.Provider value={value}> |
| 37 | <View |
| 38 | ref={hostRef} |
| 39 | collapsable={false} |
| 40 | pointerEvents="box-none" |
| 41 | className="absolute inset-0" |
| 42 | onLayout={() => setLayoutVersion((version) => version + 1)} |
| 43 | > |
| 44 | {props.children} |
| 45 | {props.hidden ? null : content} |
| 46 | </View> |
| 47 | </ComposerPopoverHostContext.Provider> |
| 48 | ); |
| 49 | } |