import { Search, X } from 'lucide-react'; import React, { useState, useMemo, useCallback } from 'react'; import { useLocalize } from '~/hooks'; import { cn } from '~/utils'; // This is a generic that can be added to Menu and Select components export default function MultiSearch({ value, onChange, placeholder, }: { value: string | null; onChange: (filter: string) => void; placeholder?: string; }) { const localize = useLocalize(); const onChangeHandler: React.ChangeEventHandler = useCallback( (e) => onChange(e.target.value), [], ); return (
onChange('')} />
); } /** * Helper function that will take a multiSearch input * @param node */ function defaultGetStringKey(node: unknown): string { if (typeof node === 'string') { // BUGFIX: Detect psedeo separators and make sure they don't appear in the list when filtering items // it makes sure (for the most part) that the model name starts and ends with dashes // The long-term fix here would be to enable seperators (model groupings) but there's no // feature mocks for such a thing yet if (node.startsWith('---') && node.endsWith('---')) { return ''; } return node.toUpperCase(); } // This should be a noop, but it's here for redundancy return ''; } /** * Hook for conditionally making a multi-element list component into a sortable component * Returns a RenderNode for search input when search functionality is available * @param availableOptions * @param placeholder * @param getTextKeyOverride * @returns */ export function useMultiSearch( availableOptions: OptionsType, placeholder?: string, getTextKeyOverride?: (node: OptionsType[0]) => string, ): [OptionsType, React.ReactNode] { const [filterValue, setFilterValue] = useState(null); // We conditionally show the search when there's more than 10 elements in the menu const shouldShowSearch = availableOptions.length > 10; // Define the helper function used to enable search // If this is invalidly described, we will assume developer error - tf. avoid rendering const getTextKeyHelper = getTextKeyOverride || defaultGetStringKey; // Iterate said options const filteredOptions = useMemo(() => { if (!shouldShowSearch || !filterValue || !availableOptions.length) { // Don't render if available options aren't present, there's no filter active return availableOptions; } // Filter through the values, using a simple text-based search // nothing too fancy, but we can add a better search algo later if we need const upperFilterValue = filterValue.toUpperCase(); return availableOptions.filter((value) => getTextKeyHelper(value).includes(upperFilterValue), ) as OptionsType; }, [availableOptions, getTextKeyHelper, filterValue, shouldShowSearch]); const onSearchChange = useCallback((nextFilterValue) => setFilterValue(nextFilterValue), []); const searchRender = shouldShowSearch ? ( ) : null; return [filteredOptions, searchRender]; }