"use client"; import { useAtomValue, useSetAtom } from "jotai"; import { Plus, Zap } from "lucide-react"; import { forwardRef, useCallback, useDeferredValue, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react"; import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms"; import { userSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; import { Skeleton } from "@/components/ui/skeleton"; import { cn } from "@/lib/utils"; export interface PromptPickerRef { selectHighlighted: () => void; moveUp: () => void; moveDown: () => void; } interface PromptPickerProps { onSelect: (action: { name: string; prompt: string; mode: "transform" | "explore" }) => void; onDone: () => void; externalSearch?: string; } export const PromptPicker = forwardRef(function PromptPicker( { onSelect, onDone, externalSearch = "" }, ref ) { const setUserSettingsDialog = useSetAtom(userSettingsDialogAtom); const { data: prompts, isLoading, isError } = useAtomValue(promptsAtom); const [highlightedIndex, setHighlightedIndex] = useState(0); const scrollContainerRef = useRef(null); const shouldScrollRef = useRef(false); const itemRefs = useRef>(new Map()); // Defer the search value so filtering is non-urgent and the input stays responsive const deferredSearch = useDeferredValue(externalSearch); const filtered = useMemo(() => { const list = prompts ?? []; if (!deferredSearch) return list; return list.filter((a) => a.name.toLowerCase().includes(deferredSearch.toLowerCase())); }, [prompts, deferredSearch]); // Reset highlight when the deferred (filtered) search changes const prevSearchRef = useRef(deferredSearch); if (prevSearchRef.current !== deferredSearch) { prevSearchRef.current = deferredSearch; if (highlightedIndex !== 0) { setHighlightedIndex(0); } } const createPromptIndex = filtered.length; const totalItems = filtered.length + 1; const handleSelect = useCallback( (index: number) => { if (index === createPromptIndex) { onDone(); setUserSettingsDialog({ open: true, initialTab: "prompts" }); return; } const action = filtered[index]; if (!action) return; onSelect({ name: action.name, prompt: action.prompt, mode: action.mode }); }, [filtered, onSelect, createPromptIndex, onDone, setUserSettingsDialog] ); useEffect(() => { if (!shouldScrollRef.current) return; shouldScrollRef.current = false; const rafId = requestAnimationFrame(() => { const item = itemRefs.current.get(highlightedIndex); const container = scrollContainerRef.current; if (item && container) { const itemRect = item.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); if (itemRect.top < containerRect.top || itemRect.bottom > containerRect.bottom) { item.scrollIntoView({ block: "nearest" }); } } }); return () => cancelAnimationFrame(rafId); }, [highlightedIndex]); useImperativeHandle( ref, () => ({ selectHighlighted: () => handleSelect(highlightedIndex), moveUp: () => { shouldScrollRef.current = true; setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : totalItems - 1)); }, moveDown: () => { shouldScrollRef.current = true; setHighlightedIndex((prev) => (prev < totalItems - 1 ? prev + 1 : 0)); }, }), [totalItems, highlightedIndex, handleSelect] ); return (
{isLoading ? (
{["a", "b", "c", "d", "e"].map((id, i) => (
= 3 && "hidden sm:flex" )} >
))}
) : isError ? (

Failed to load prompts

) : filtered.length === 0 ? (

No matching prompts

) : (
Saved Prompts
{filtered.map((action, index) => ( ))}
)}
); });