dependency-picker.tsx 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import type { FC } from 'react'
  2. import React, { useCallback, useState } from 'react'
  3. import { t } from 'i18next'
  4. import type { CodeDependency } from './types'
  5. import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
  6. import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
  7. import { Check, SearchLg } from '@/app/components/base/icons/src/vender/line/general'
  8. import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
  9. type Props = {
  10. value: CodeDependency
  11. available_dependencies: CodeDependency[]
  12. onChange: (dependency: CodeDependency) => void
  13. }
  14. const DependencyPicker: FC<Props> = ({
  15. available_dependencies,
  16. value,
  17. onChange,
  18. }) => {
  19. const [open, setOpen] = useState(false)
  20. const [searchText, setSearchText] = useState('')
  21. const handleChange = useCallback((dependency: CodeDependency) => {
  22. return () => {
  23. setOpen(false)
  24. onChange(dependency)
  25. }
  26. }, [onChange])
  27. return (
  28. <PortalToFollowElem
  29. open={open}
  30. onOpenChange={setOpen}
  31. placement='bottom-start'
  32. offset={4}
  33. >
  34. <PortalToFollowElemTrigger onClick={() => setOpen(!open)} className='flex-grow cursor-pointer'>
  35. <div className='flex items-center h-8 justify-between px-2.5 rounded-lg border-0 bg-gray-100 text-gray-900 text-[13px]'>
  36. <div className='grow w-0 truncate' title={value.name}>{value.name}</div>
  37. <ChevronDown className='shrink-0 w-3.5 h-3.5 text-gray-700' />
  38. </div>
  39. </PortalToFollowElemTrigger>
  40. <PortalToFollowElemContent style={{
  41. zIndex: 100,
  42. }}>
  43. <div className='p-1 bg-white rounded-lg shadow-sm' style={{
  44. width: 350,
  45. }}>
  46. <div
  47. className='shadow-sm bg-white mb-2 mx-1 flex items-center px-2 rounded-lg bg-gray-100'
  48. >
  49. <SearchLg className='shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400' />
  50. <input
  51. value={searchText}
  52. className='grow px-0.5 py-[7px] text-[13px] text-gray-700 bg-transparent appearance-none outline-none caret-primary-600 placeholder:text-gray-400'
  53. placeholder={t('workflow.nodes.code.searchDependencies') || ''}
  54. onChange={e => setSearchText(e.target.value)}
  55. autoFocus
  56. />
  57. {
  58. searchText && (
  59. <div
  60. className='flex items-center justify-center ml-[5px] w-[18px] h-[18px] cursor-pointer'
  61. onClick={() => setSearchText('')}
  62. >
  63. <XCircle className='w-[14px] h-[14px] text-gray-400' />
  64. </div>
  65. )
  66. }
  67. </div>
  68. <div className='max-h-[30vh] overflow-y-auto'>
  69. {available_dependencies.filter((v) => {
  70. if (!searchText)
  71. return true
  72. return v.name.toLowerCase().includes(searchText.toLowerCase())
  73. }).map(dependency => (
  74. <div
  75. key={dependency.name}
  76. className='flex items-center h-[30px] justify-between pl-3 pr-2 rounded-lg hover:bg-gray-100 text-gray-900 text-[13px] cursor-pointer'
  77. onClick={handleChange(dependency)}
  78. >
  79. <div className='w-0 grow truncate'>{dependency.name}</div>
  80. {dependency.name === value.name && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
  81. </div>
  82. ))}
  83. </div>
  84. </div>
  85. </PortalToFollowElemContent>
  86. </PortalToFollowElem>
  87. )
  88. }
  89. export default React.memo(DependencyPicker)