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, className = '', }: { value: string | null; onChange: (filter: string) => void; placeholder?: string; className?: string; }) { const localize = useLocalize(); const onChangeHandler: React.ChangeEventHandler = useCallback( (e) => onChange(e.target.value), [onChange], ); 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 * @param className - Additional classnames to add to the search container * @param disabled - If the search should be disabled * @returns */ export function useMultiSearch({ availableOptions, placeholder, getTextKeyOverride, className, disabled = false, }: { availableOptions: OptionsType; placeholder?: string; getTextKeyOverride?: (node: OptionsType[0]) => string; className?: string; disabled?: boolean; }): [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 && !disabled; // 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(() => { const currentFilter = filterValue ?? ''; if (!shouldShowSearch || !currentFilter || !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 = currentFilter.toUpperCase(); return availableOptions.filter((value) => getTextKeyHelper(value).includes(upperFilterValue), ) as OptionsType; }, [availableOptions, getTextKeyHelper, filterValue, shouldShowSearch]); const onSearchChange = useCallback( (nextFilterValue: string) => setFilterValue(nextFilterValue), [], ); const searchRender = shouldShowSearch ? ( ) : null; return [filteredOptions, searchRender]; }