import { Search, X } from 'lucide-react'; import { type ReactNode, forwardRef, useMemo, useRef, useState } from 'react'; import { pluralize } from '@/react/common/string-utils'; import { Button } from '@@/buttons'; import { filterToPattern } from '../form-components/FilePicker/utils'; import { globToRegex } from './utils'; interface Props { value: string; onChange: (value: string) => void; onCompletion: (pattern: string) => void; allFilePaths: string[]; renderDropdown?: (paths: string[]) => ReactNode; openDropdownOnFocus?: boolean; } export const CommandPalette = forwardRef( function CommandPalette( { value, onChange, onCompletion, allFilePaths, renderDropdown, openDropdownOnFocus = true, }, forwardedRef ) { const [isFocused, setIsFocused] = useState(false); const inputRef = useRef(null); function setRefs(node: HTMLInputElement | null) { inputRef.current = node; if (typeof forwardedRef === 'function') { forwardedRef(node); } else if (forwardedRef) { // eslint-disable-next-line no-param-reassign forwardedRef.current = node; } } const filterTrimmed = value.trim(); const filterActive = filterTrimmed.length > 0; const matchingPaths = useMemo(() => { if (openDropdownOnFocus && !isFocused) return []; if (!filterActive) return allFilePaths; const re = globToRegex(filterToPattern(filterTrimmed)); return allFilePaths.filter((p) => re.test(p)); }, [ openDropdownOnFocus, isFocused, filterActive, allFilePaths, filterTrimmed, ]); return (
onChange(e.target.value)} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} onKeyDown={(e) => { if (e.key === 'Enter' && filterActive) handleAddExpression(); if (e.key === 'Escape') { setIsFocused(false); inputRef.current?.blur(); onChange(''); } }} placeholder="Filter or add expression, e.g. *.yml, src/**/*.ts" className="flex-1 border-0 bg-transparent text-sm text-gray-11 outline-none placeholder:text-gray-6 th-highcontrast:text-white th-dark:text-white" data-cy="command-palette-search-input" /> {filterActive && ( <> {matchingPaths.length}{' '} {pluralize(matchingPaths.length, 'match', 'matches')} )} {value && ( )} {(isFocused || !openDropdownOnFocus) && (openDropdownOnFocus || filterActive) && (renderDropdown ? renderDropdown(matchingPaths) : matchingPaths.length > 0 && (
    {matchingPaths.map((path) => (
  • /{path}
  • ))}
))}
); function handleAddExpression() { onCompletion(filterToPattern(filterTrimmed)); setIsFocused(false); inputRef.current?.blur(); onChange(''); } } );