dependency-picker.tsx 2.9 KB

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