| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const [show, setShow] = useState<boolean>(false) | const [show, setShow] = useState<boolean>(false) | ||||
| const [searchQuery, setSearchQuery] = useState<string>('') | const [searchQuery, setSearchQuery] = useState<string>('') | ||||
| const [cmdVal, setCmdVal] = useState<string>('') | |||||
| const [cmdVal, setCmdVal] = useState<string>('_') | |||||
| const inputRef = useRef<HTMLInputElement>(null) | const inputRef = useRef<HTMLInputElement>(null) | ||||
| const handleNavSearch = useCallback((q: string) => { | const handleNavSearch = useCallback((q: string) => { | ||||
| setShow(true) | setShow(true) | ||||
| }, | }, | ||||
| ) | ) | ||||
| // Prevent automatic selection of the first option when cmdVal is not set | |||||
| const clearSelection = () => { | |||||
| setCmdVal('_') | |||||
| } | |||||
| const handleCommandSelect = useCallback((commandKey: string) => { | const handleCommandSelect = useCallback((commandKey: string) => { | ||||
| setSearchQuery(`${commandKey} `) | setSearchQuery(`${commandKey} `) | ||||
| setCmdVal('') | |||||
| clearSelection() | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| inputRef.current?.focus() | inputRef.current?.focus() | ||||
| }, 0) | }, 0) | ||||
| inputRef.current?.focus() | inputRef.current?.focus() | ||||
| }) | }) | ||||
| } | } | ||||
| return () => { | |||||
| setCmdVal('') | |||||
| } | |||||
| }, [show]) | }, [show]) | ||||
| return ( | return ( | ||||
| onClose={() => { | onClose={() => { | ||||
| setShow(false) | setShow(false) | ||||
| setSearchQuery('') | setSearchQuery('') | ||||
| clearSelection() | |||||
| onHide?.() | onHide?.() | ||||
| }} | }} | ||||
| closable={false} | closable={false} | ||||
| onChange={(e) => { | onChange={(e) => { | ||||
| setSearchQuery(e.target.value) | setSearchQuery(e.target.value) | ||||
| if (!e.target.value.startsWith('@')) | if (!e.target.value.startsWith('@')) | ||||
| setCmdVal('') | |||||
| clearSelection() | |||||
| }} | }} | ||||
| className='flex-1 !border-0 !bg-transparent !shadow-none' | className='flex-1 !border-0 !bg-transparent !shadow-none' | ||||
| wrapperClassName='flex-1 !border-0 !bg-transparent' | wrapperClassName='flex-1 !border-0 !bg-transparent' | ||||
| /> | /> | ||||
| ) : ( | ) : ( | ||||
| Object.entries(groupedResults).map(([type, results], groupIndex) => ( | Object.entries(groupedResults).map(([type, results], groupIndex) => ( | ||||
| <Command.Group key={groupIndex} heading={(() => { | |||||
| const typeMap: Record<string, string> = { | |||||
| 'app': 'app.gotoAnything.groups.apps', | |||||
| 'plugin': 'app.gotoAnything.groups.plugins', | |||||
| 'knowledge': 'app.gotoAnything.groups.knowledgeBases', | |||||
| 'workflow-node': 'app.gotoAnything.groups.workflowNodes', | |||||
| } | |||||
| return t(typeMap[type] || `${type}s`) | |||||
| })()} className='p-2 capitalize text-text-secondary'> | |||||
| {results.map(result => ( | |||||
| <Command.Item | |||||
| key={`${result.type}-${result.id}`} | |||||
| value={result.title} | |||||
| className='flex cursor-pointer items-center gap-3 rounded-md p-3 will-change-[background-color] hover:bg-state-base-hover aria-[selected=true]:bg-state-base-hover-alt data-[selected=true]:bg-state-base-hover-alt' | |||||
| onSelect={() => handleNavigate(result)} | |||||
| > | |||||
| {result.icon} | |||||
| <div className='min-w-0 flex-1'> | |||||
| <div className='truncate font-medium text-text-secondary'> | |||||
| {result.title} | |||||
| </div> | |||||
| {result.description && ( | |||||
| <div className='mt-0.5 truncate text-xs text-text-quaternary'> | |||||
| {result.description} | |||||
| <Command.Group key={groupIndex} heading={(() => { | |||||
| const typeMap: Record<string, string> = { | |||||
| 'app': 'app.gotoAnything.groups.apps', | |||||
| 'plugin': 'app.gotoAnything.groups.plugins', | |||||
| 'knowledge': 'app.gotoAnything.groups.knowledgeBases', | |||||
| 'workflow-node': 'app.gotoAnything.groups.workflowNodes', | |||||
| } | |||||
| return t(typeMap[type] || `${type}s`) | |||||
| })()} className='p-2 capitalize text-text-secondary'> | |||||
| {results.map(result => ( | |||||
| <Command.Item | |||||
| key={`${result.type}-${result.id}`} | |||||
| value={result.title} | |||||
| className='flex cursor-pointer items-center gap-3 rounded-md p-3 will-change-[background-color] aria-[selected=true]:bg-state-base-hover data-[selected=true]:bg-state-base-hover' | |||||
| onSelect={() => handleNavigate(result)} | |||||
| > | |||||
| {result.icon} | |||||
| <div className='min-w-0 flex-1'> | |||||
| <div className='truncate font-medium text-text-secondary'> | |||||
| {result.title} | |||||
| </div> | </div> | ||||
| )} | |||||
| </div> | |||||
| <div className='text-xs capitalize text-text-quaternary'> | |||||
| {result.type} | |||||
| </div> | |||||
| </Command.Item> | |||||
| ))} | |||||
| </Command.Group> | |||||
| )) | |||||
| {result.description && ( | |||||
| <div className='mt-0.5 truncate text-xs text-text-quaternary'> | |||||
| {result.description} | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| <div className='text-xs capitalize text-text-quaternary'> | |||||
| {result.type} | |||||
| </div> | |||||
| </Command.Item> | |||||
| ))} | |||||
| </Command.Group> | |||||
| )) | |||||
| )} | )} | ||||
| {!isCommandsMode && emptyResult} | {!isCommandsMode && emptyResult} | ||||
| {!isCommandsMode && defaultUI} | {!isCommandsMode && defaultUI} | ||||
| {t('app.gotoAnything.resultCount', { count: searchResults.length })} | {t('app.gotoAnything.resultCount', { count: searchResults.length })} | ||||
| {searchMode !== 'general' && ( | {searchMode !== 'general' && ( | ||||
| <span className='ml-2 opacity-60'> | <span className='ml-2 opacity-60'> | ||||
| {t('app.gotoAnything.inScope', { scope: searchMode.replace('@', '') })} | |||||
| {t('app.gotoAnything.inScope', { scope: searchMode.replace('@', '') })} | |||||
| </span> | </span> | ||||
| )} | )} | ||||
| </> | </> |