"use client"; import { CloseTiny } from "components/Icons/CloseTiny"; import { Input } from "components/Input"; import { useState, useRef } from "react"; import { useDebouncedEffect } from "src/hooks/useDebouncedEffect"; import { Popover } from "components/Popover"; import Link from "next/link"; import { searchTags, type TagSearchResult } from "actions/searchTags"; export const Tag = (props: { name: string; selected?: boolean; onDelete?: (tag: string) => void; className?: string; }) => { return (
{props.name}{" "} {props.selected ? ( ) : null}
); }; export const TagSelector = (props: { selectedTags: string[]; setSelectedTags: (tags: string[]) => void; }) => { return (
{props.selectedTags.length > 0 ? (
{props.selectedTags.map((tag) => ( { props.setSelectedTags( props.selectedTags.filter((t) => t !== tag), ); }} /> ))}
) : (
no tags selected
)}
); }; export const TagSearchInput = (props: { selectedTags: string[]; setSelectedTags: (tags: string[]) => void; }) => { let [tagInputValue, setTagInputValue] = useState(""); let [isOpen, setIsOpen] = useState(false); let [highlightedIndex, setHighlightedIndex] = useState(0); let [searchResults, setSearchResults] = useState([]); let [isSearching, setIsSearching] = useState(false); const placeholderInputRef = useRef(null); let inputWidth = placeholderInputRef.current?.clientWidth; // Fetch tags whenever the input value changes useDebouncedEffect( async () => { setIsSearching(true); const results = await searchTags(tagInputValue); if (results) { setSearchResults(results); } setIsSearching(false); }, 300, [tagInputValue], ); const filteredTags = searchResults .filter((tag) => !props.selectedTags.includes(tag.name)) .filter((tag) => tag.name.toLowerCase().includes(tagInputValue.toLowerCase()), ); const showResults = tagInputValue.length >= 3; function clearTagInput() { setHighlightedIndex(0); setTagInputValue(""); } function selectTag(tag: string) { props.setSelectedTags([...props.selectedTags, tag]); clearTagInput(); } const handleKeyDown = ( e: React.KeyboardEvent, ) => { if (!isOpen) return; if (e.key === "ArrowDown") { e.preventDefault(); setHighlightedIndex((prev) => prev < filteredTags.length ? prev + 1 : prev, ); } else if (e.key === "ArrowUp") { e.preventDefault(); setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : 0)); } else if (e.key === "Enter") { e.preventDefault(); selectTag( userInputResult ? highlightedIndex === 0 ? tagInputValue : filteredTags[highlightedIndex - 1].name : filteredTags[highlightedIndex].name, ); clearTagInput(); } else if (e.key === "Escape") { setIsOpen(false); } }; const userInputResult = showResults && tagInputValue !== "" && !filteredTags.some((tag) => tag.name === tagInputValue); return (
{ setTagInputValue(e.target.value); setIsOpen(true); setHighlightedIndex(0); }} onKeyDown={handleKeyDown} onFocus={() => { setIsOpen(true); document.getElementById("tag-search-input")?.focus(); }} /> { setIsOpen(!isOpen); if (!isOpen) setTimeout(() => { document.getElementById("tag-search-input")?.focus(); }, 100); }} className="w-full p-2! min-w-xs text-primary" sideOffset={-39} onOpenAutoFocus={(e) => e.preventDefault()} asChild trigger={ } noArrow >
{ setTagInputValue(e.target.value); setIsOpen(true); setHighlightedIndex(0); }} onKeyDown={handleKeyDown} onFocus={() => { setIsOpen(true); }} /> {props.selectedTags.length > 0 ? (
{props.selectedTags.map((tag) => ( { props.setSelectedTags( props.selectedTags.filter((t) => t !== tag), ); }} /> ))}
) : (
no tags selected
)}
{showResults ? ( <> {userInputResult && ( { selectTag(tagInputValue); }} /> )} {filteredTags.map((tag, i) => ( { selectTag(tag.name); }} /> ))} ) : (
type at least 3 characters to search
)}
); }; const TagResult = (props: { name: string; tagged: number; onSelect: () => void; index: number; highlighted: boolean; setHighlightedIndex: (i: number) => void; }) => { return (
); };