operation-selector.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import type { FC } from 'react'
  2. import { useState } from 'react'
  3. import {
  4. RiArrowDownSLine,
  5. RiCheckLine,
  6. } from '@remixicon/react'
  7. import classNames from 'classnames'
  8. import { useTranslation } from 'react-i18next'
  9. import type { WriteMode } from '../types'
  10. import { getOperationItems } from '../utils'
  11. import {
  12. PortalToFollowElem,
  13. PortalToFollowElemContent,
  14. PortalToFollowElemTrigger,
  15. } from '@/app/components/base/portal-to-follow-elem'
  16. import type { VarType } from '@/app/components/workflow/types'
  17. import Divider from '@/app/components/base/divider'
  18. type Item = {
  19. value: string | number
  20. name: string
  21. }
  22. type OperationSelectorProps = {
  23. value: string | number
  24. onSelect: (value: Item) => void
  25. placeholder?: string
  26. disabled?: boolean
  27. className?: string
  28. popupClassName?: string
  29. assignedVarType?: VarType
  30. writeModeTypes?: WriteMode[]
  31. writeModeTypesArr?: WriteMode[]
  32. writeModeTypesNum?: WriteMode[]
  33. }
  34. const i18nPrefix = 'workflow.nodes.assigner'
  35. const OperationSelector: FC<OperationSelectorProps> = ({
  36. value,
  37. onSelect,
  38. disabled = false,
  39. className,
  40. popupClassName,
  41. assignedVarType,
  42. writeModeTypes,
  43. writeModeTypesArr,
  44. writeModeTypesNum,
  45. }) => {
  46. const { t } = useTranslation()
  47. const [open, setOpen] = useState(false)
  48. const items = getOperationItems(assignedVarType, writeModeTypes, writeModeTypesArr, writeModeTypesNum)
  49. const selectedItem = items.find(item => item.value === value)
  50. return (
  51. <PortalToFollowElem
  52. open={open}
  53. onOpenChange={setOpen}
  54. placement='bottom-start'
  55. offset={4}
  56. >
  57. <PortalToFollowElemTrigger
  58. onClick={() => !disabled && setOpen(v => !v)}
  59. >
  60. <div
  61. className={classNames(
  62. 'flex items-center px-2 py-1 gap-0.5 rounded-lg bg-components-input-bg-normal',
  63. disabled ? 'cursor-not-allowed !bg-components-input-bg-disabled' : 'cursor-pointer hover:bg-state-base-hover-alt',
  64. open && 'bg-state-base-hover-alt',
  65. className,
  66. )}
  67. >
  68. <div className='flex p-1 items-center'>
  69. <span
  70. className={`truncate overflow-hidden text-ellipsis system-sm-regular
  71. ${selectedItem ? 'text-components-input-text-filled' : 'text-components-input-text-disabled'}`}
  72. >
  73. {selectedItem?.name ? t(`${i18nPrefix}.operations.${selectedItem?.name}`) : t(`${i18nPrefix}.operations.title`)}
  74. </span>
  75. </div>
  76. <RiArrowDownSLine className={`h-4 w-4 text-text-quaternary ${disabled && 'text-components-input-text-placeholder'} ${open && 'text-text-secondary'}`} />
  77. </div>
  78. </PortalToFollowElemTrigger>
  79. <PortalToFollowElemContent className={`z-20 ${popupClassName}`}>
  80. <div className='flex w-[140px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
  81. <div className='flex p-1 flex-col items-start self-stretch'>
  82. <div className='flex px-3 pt-1 pb-0.5 items-start self-stretch'>
  83. <div className='flex grow text-text-tertiary system-xs-medium-uppercase'>{t(`${i18nPrefix}.operations.title`)}</div>
  84. </div>
  85. {items.map(item => (
  86. item.value === 'divider'
  87. ? (
  88. <Divider key="divider" className="my-1" />
  89. )
  90. : (
  91. <div
  92. key={item.value}
  93. className={classNames(
  94. 'flex items-center px-2 py-1 gap-1 self-stretch rounded-lg',
  95. 'cursor-pointer hover:bg-state-base-hover',
  96. )}
  97. onClick={() => {
  98. onSelect(item)
  99. setOpen(false)
  100. }}
  101. >
  102. <div className='flex min-h-5 px-1 items-center gap-1 grow'>
  103. <span className={'flex flex-grow text-text-secondary system-sm-medium'}>{t(`${i18nPrefix}.operations.${item.name}`)}</span>
  104. </div>
  105. {item.value === value && (
  106. <div className='flex justify-center items-center'>
  107. <RiCheckLine className='h-4 w-4 text-text-accent' />
  108. </div>
  109. )}
  110. </div>
  111. )
  112. ))}
  113. </div>
  114. </div>
  115. </PortalToFollowElemContent>
  116. </PortalToFollowElem>
  117. )
  118. }
  119. export default OperationSelector