選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

command-selector.tsx 3.1KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import type { FC } from 'react'
  2. import { useEffect } from 'react'
  3. import { Command } from 'cmdk'
  4. import { useTranslation } from 'react-i18next'
  5. import type { ActionItem } from './actions/types'
  6. type Props = {
  7. actions: Record<string, ActionItem>
  8. onCommandSelect: (commandKey: string) => void
  9. searchFilter?: string
  10. commandValue?: string
  11. onCommandValueChange?: (value: string) => void
  12. }
  13. const CommandSelector: FC<Props> = ({ actions, onCommandSelect, searchFilter, commandValue, onCommandValueChange }) => {
  14. const { t } = useTranslation()
  15. const filteredActions = Object.values(actions).filter((action) => {
  16. if (!searchFilter)
  17. return true
  18. const filterLower = searchFilter.toLowerCase()
  19. return action.shortcut.toLowerCase().includes(filterLower)
  20. || action.key.toLowerCase().includes(filterLower)
  21. })
  22. useEffect(() => {
  23. if (filteredActions.length > 0 && onCommandValueChange) {
  24. const currentValueExists = filteredActions.some(action => action.shortcut === commandValue)
  25. if (!currentValueExists)
  26. onCommandValueChange(filteredActions[0].shortcut)
  27. }
  28. }, [searchFilter, filteredActions.length])
  29. if (filteredActions.length === 0) {
  30. return (
  31. <div className="p-4">
  32. <div className="flex items-center justify-center py-8 text-center text-text-tertiary">
  33. <div>
  34. <div className="text-sm font-medium text-text-tertiary">
  35. {t('app.gotoAnything.noMatchingCommands')}
  36. </div>
  37. <div className="mt-1 text-xs text-text-quaternary">
  38. {t('app.gotoAnything.tryDifferentSearch')}
  39. </div>
  40. </div>
  41. </div>
  42. </div>
  43. )
  44. }
  45. return (
  46. <div className="p-4">
  47. <div className="mb-3 text-left text-sm font-medium text-text-secondary">
  48. {t('app.gotoAnything.selectSearchType')}
  49. </div>
  50. <Command.Group className="space-y-1">
  51. {filteredActions.map(action => (
  52. <Command.Item
  53. key={action.key}
  54. value={action.shortcut}
  55. className="flex cursor-pointer items-center rounded-md
  56. p-2.5
  57. transition-all
  58. duration-150 hover:bg-state-base-hover-alt aria-[selected=true]:bg-state-base-hover"
  59. onSelect={() => onCommandSelect(action.shortcut)}
  60. >
  61. <span className="min-w-[4.5rem] text-left font-mono text-xs text-text-tertiary">
  62. {action.shortcut}
  63. </span>
  64. <span className="ml-3 text-sm text-text-secondary">
  65. {(() => {
  66. const keyMap: Record<string, string> = {
  67. '@app': 'app.gotoAnything.actions.searchApplicationsDesc',
  68. '@plugin': 'app.gotoAnything.actions.searchPluginsDesc',
  69. '@knowledge': 'app.gotoAnything.actions.searchKnowledgeBasesDesc',
  70. '@node': 'app.gotoAnything.actions.searchWorkflowNodesDesc',
  71. }
  72. return t(keyMap[action.key])
  73. })()}
  74. </span>
  75. </Command.Item>
  76. ))}
  77. </Command.Group>
  78. </div>
  79. )
  80. }
  81. export default CommandSelector