import { useEffect, useMemo, useRef, useState } from 'react'; import { Input } from '@/components/ui/input'; export interface LabelNameSuggestion { name: string; scope: 'stack'; nodeCount: number; stackCount: number; nodes?: string[]; } interface LabelNameAutocompleteProps { value: string; onChange: (next: string) => void; suggestions: LabelNameSuggestion[]; disabled?: boolean; placeholder?: string; id?: string; } /** * Free-form label-name input with a suggestion popover. Used by Scheduled * Operations label targeting; the operator may type a name that is not * suggested (membership is resolved at preview/run time). */ export function LabelNameAutocomplete({ value, onChange, suggestions, disabled, placeholder, id = 'label-name-input', }: LabelNameAutocompleteProps) { const [open, setOpen] = useState(false); const wrapperRef = useRef(null); const filtered = useMemo(() => { const q = value.trim().toLowerCase(); if (q.length === 0) return suggestions; return suggestions.filter(s => s.name.toLowerCase().includes(q)); }, [value, suggestions]); useEffect(() => { if (!open) return; const onClick = (e: MouseEvent) => { if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { setOpen(false); } }; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.stopPropagation(); setOpen(false); } }; document.addEventListener('mousedown', onClick); document.addEventListener('keydown', onKey, true); return () => { document.removeEventListener('mousedown', onClick); document.removeEventListener('keydown', onKey, true); }; }, [open]); return (
{ onChange(e.target.value); if (!open) setOpen(true); }} onFocus={() => { if (!disabled) setOpen(true); }} placeholder={placeholder} className="h-9 text-sm" disabled={disabled} autoComplete="off" spellCheck={false} /> {open && filtered.length > 0 && (
    {filtered.map((s) => { const nodes = s.nodes ?? []; return (
  • ); })}
)}
); }