| 1 | import { |
| 2 | createContext, |
| 3 | use, |
| 4 | useImperativeHandle, |
| 5 | useLayoutEffect, |
| 6 | type ReactNode, |
| 7 | type ComponentProps, |
| 8 | type Ref, |
| 9 | } from "react"; |
| 10 | import { useThreadFind } from "./useThreadFind"; |
| 11 | import { ThreadFindBar } from "./ThreadFindBar"; |
| 12 | import { ChatCanvas } from "./ChatCanvas"; |
| 13 | |
| 14 | type Find = ReturnType<typeof useThreadFind>; |
| 15 | export type ThreadFindControls = Pick<Find, "open" | "close">; |
| 16 | export const ThreadFindTimelineContext = createContext<Find["timelineProps"] | null>(null); |
| 17 | const ThreadFindBarContext = createContext<Find["barProps"] | null>(null); |
| 18 | |
| 19 | export function ThreadFindCanvas({ |
| 20 | findOptions, |
| 21 | controlsRef, |
| 22 | onOpenChange, |
| 23 | ...canvasProps |
| 24 | }: ComponentProps<typeof ChatCanvas> & { |
| 25 | findOptions: Parameters<typeof useThreadFind>[0]; |
| 26 | controlsRef: Ref<ThreadFindControls>; |
| 27 | onOpenChange: (open: boolean) => void; |
| 28 | }) { |
| 29 | return ( |
| 30 | <ThreadFindProvider {...findOptions} controlsRef={controlsRef} onOpenChange={onOpenChange}> |
| 31 | <ChatCanvas {...canvasProps} /> |
| 32 | </ThreadFindProvider> |
| 33 | ); |
| 34 | } |
| 35 | |
| 36 | /** Owns search updates below the chat view so cycling does not rerender the composer and panels. */ |
| 37 | export function ThreadFindProvider({ |
| 38 | controlsRef, |
| 39 | onOpenChange, |
| 40 | children, |
| 41 | ...options |
| 42 | }: Parameters<typeof useThreadFind>[0] & { |
| 43 | controlsRef: Ref<ThreadFindControls>; |
| 44 | onOpenChange: (open: boolean) => void; |
| 45 | children: ReactNode; |
| 46 | }) { |
| 47 | const find = useThreadFind(options); |
| 48 | useImperativeHandle(controlsRef, () => ({ open: find.open, close: find.close }), [ |
| 49 | find.open, |
| 50 | find.close, |
| 51 | ]); |
| 52 | // Before paint, so the details card makes room in the same frame the bar appears. |
| 53 | useLayoutEffect(() => onOpenChange(find.isOpen), [find.isOpen, onOpenChange]); |
| 54 | return ( |
| 55 | <ThreadFindBarContext value={find.barProps}> |
| 56 | <ThreadFindTimelineContext value={find.timelineProps}>{children}</ThreadFindTimelineContext> |
| 57 | </ThreadFindBarContext> |
| 58 | ); |
| 59 | } |
| 60 | |
| 61 | export function ThreadFind({ onClose }: { onClose: () => void }) { |
| 62 | const bar = use(ThreadFindBarContext); |
| 63 | if (!bar) return null; |
| 64 | return ( |
| 65 | <ThreadFindBar |
| 66 | {...bar} |
| 67 | onClose={() => { |
| 68 | bar.onClose(); |
| 69 | onClose(); |
| 70 | }} |
| 71 | /> |
| 72 | ); |
| 73 | } |