"use client"; import { useEffect, useRef, useState, useSyncExternalStore } from "react"; import { VoiceBeam } from "voice-glow"; import { Orb } from "./fx/Orb"; const emptySubscribe = () => () => {}; export type SearchComposerProps = { value?: string; defaultValue?: string; onChange?: (v: string) => void; onSubmit?: (v: string) => void; onClear?: () => void; busy?: boolean; placeholder?: string; className?: string; autoFocus?: boolean; }; export function SearchComposer({ value: controlledValue, defaultValue = "", onChange, onSubmit, onClear, busy = false, placeholder = "What are we overthinking today?", className, autoFocus = true, }: SearchComposerProps) { const isControlled = controlledValue !== undefined; const [internalValue, setInternalValue] = useState(defaultValue); const currentValue = isControlled ? controlledValue : internalValue; const input = useRef(null); const energy = useRef(0); const ready = useSyncExternalStore( emptySubscribe, () => true, () => false, ); useEffect(() => { let raf = 0; const tick = () => { energy.current *= 0.91; raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, []); useEffect(() => { const typed = input.current?.value; if (typed && typed !== currentValue) { if (!isControlled) { setInternalValue(typed); } onChange?.(typed); } }, [currentValue, isControlled, onChange]); const submit = () => onSubmit?.(input.current?.value ?? currentValue); useEffect(() => { const onKey = (e: KeyboardEvent) => { const typing = document.activeElement instanceof HTMLInputElement; if (e.key === "/" && !typing) { e.preventDefault(); input.current?.focus(); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); const form = (
{ e.preventDefault(); submit(); }} className="flex w-full items-center gap-3 rounded-[5px] border border-line bg-panel/95 py-3 pl-6 pr-3 backdrop-blur-2xl transition-colors duration-300 focus-within:border-line-strong has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-inset has-[input:focus-visible]:ring-zinc-500 dark:has-[input:focus-visible]:ring-white/50" > { energy.current = Math.min(1, energy.current + 0.5); if (!isControlled) { setInternalValue(e.target.value); } onChange?.(e.target.value); }} onKeyDown={(e) => { if (e.key === "Enter" && !e.nativeEvent.isComposing) { e.preventDefault(); submit(); } if (e.key === "Escape") { if (!isControlled) { setInternalValue(""); } onClear?.(); input.current?.blur(); } }} placeholder={placeholder} className="min-w-0 flex-1 bg-transparent py-2 text-[18px] leading-8 tracking-[-0.01em] text-text outline-none placeholder:text-faint" />
); if (!ready) return form; return ( energy.current} processing={busy} strength={0.9} className={className ? `w-full ${className}` : "w-full"} > {form} ); } export const SearchInput = SearchComposer; export type SearchInputProps = SearchComposerProps; export { Orb, type OrbProps } from "./fx/Orb";