| import type { |
| FC, |
| MouseEventHandler, |
| } from 'react' |
| import { |
| memo, |
| useCallback, |
| useMemo, |
| useState, |
| } from 'react' |
| import { useTranslation } from 'react-i18next' |
| import type { |
| OffsetOptions, |
| Placement, |
| } from '@floating-ui/react' |
| import type { BlockEnum, OnSelectBlock } from '../types' |
| import Tabs from './tabs' |
| import { TabsEnum } from './types' |
| import { |
| PortalToFollowElem, |
| PortalToFollowElemContent, |
| PortalToFollowElemTrigger, |
| } from '@/app/components/base/portal-to-follow-elem' |
| import Input from '@/app/components/base/input' |
| import { |
| Plus02, |
| } from '@/app/components/base/icons/src/vender/line/general' |
|
|
| type NodeSelectorProps = { |
| open?: boolean |
| onOpenChange?: (open: boolean) => void |
| onSelect: OnSelectBlock |
| trigger?: (open: boolean) => React.ReactNode |
| placement?: Placement |
| offset?: OffsetOptions |
| triggerStyle?: React.CSSProperties |
| triggerClassName?: (open: boolean) => string |
| triggerInnerClassName?: string |
| popupClassName?: string |
| asChild?: boolean |
| availableBlocksTypes?: BlockEnum[] |
| disabled?: boolean |
| noBlocks?: boolean |
| } |
| const NodeSelector: FC<NodeSelectorProps> = ({ |
| open: openFromProps, |
| onOpenChange, |
| onSelect, |
| trigger, |
| placement = 'right', |
| offset = 6, |
| triggerClassName, |
| triggerInnerClassName, |
| triggerStyle, |
| popupClassName, |
| asChild, |
| availableBlocksTypes, |
| disabled, |
| noBlocks = false, |
| }) => { |
| const { t } = useTranslation() |
| const [searchText, setSearchText] = useState('') |
| const [localOpen, setLocalOpen] = useState(false) |
| const open = openFromProps === undefined ? localOpen : openFromProps |
| const handleOpenChange = useCallback((newOpen: boolean) => { |
| setLocalOpen(newOpen) |
|
|
| if (!newOpen) |
| setSearchText('') |
|
|
| if (onOpenChange) |
| onOpenChange(newOpen) |
| }, [onOpenChange]) |
| const handleTrigger = useCallback<MouseEventHandler<HTMLDivElement>>((e) => { |
| if (disabled) |
| return |
| e.stopPropagation() |
| handleOpenChange(!open) |
| }, [handleOpenChange, open, disabled]) |
| const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => { |
| handleOpenChange(false) |
| onSelect(type, toolDefaultValue) |
| }, [handleOpenChange, onSelect]) |
|
|
| const [activeTab, setActiveTab] = useState(noBlocks ? TabsEnum.Tools : TabsEnum.Blocks) |
| const handleActiveTabChange = useCallback((newActiveTab: TabsEnum) => { |
| setActiveTab(newActiveTab) |
| }, []) |
| const searchPlaceholder = useMemo(() => { |
| if (activeTab === TabsEnum.Blocks) |
| return t('workflow.tabs.searchBlock') |
|
|
| if (activeTab === TabsEnum.Tools) |
| return t('workflow.tabs.searchTool') |
| return '' |
| }, [activeTab, t]) |
|
|
| return ( |
| <PortalToFollowElem |
| placement={placement} |
| offset={offset} |
| open={open} |
| onOpenChange={handleOpenChange} |
| > |
| <PortalToFollowElemTrigger |
| asChild={asChild} |
| onClick={handleTrigger} |
| className={triggerInnerClassName} |
| > |
| { |
| trigger |
| ? trigger(open) |
| : ( |
| <div |
| className={` |
| flex items-center justify-center |
| w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 |
| ${triggerClassName?.(open)} |
| `} |
| style={triggerStyle} |
| > |
| <Plus02 className='w-2.5 h-2.5 text-white' /> |
| </div> |
| ) |
| } |
| </PortalToFollowElemTrigger> |
| <PortalToFollowElemContent className='z-[1000]'> |
| <div className={`rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`}> |
| <div className='px-2 pt-2' onClick={e => e.stopPropagation()}> |
| <Input |
| showLeftIcon |
| showClearIcon |
| autoFocus |
| value={searchText} |
| placeholder={searchPlaceholder} |
| onChange={e => setSearchText(e.target.value)} |
| onClear={() => setSearchText('')} |
| /> |
| </div> |
| <Tabs |
| activeTab={activeTab} |
| onActiveTabChange={handleActiveTabChange} |
| onSelect={handleSelect} |
| searchText={searchText} |
| availableBlocksTypes={availableBlocksTypes} |
| noBlocks={noBlocks} |
| /> |
| </div> |
| </PortalToFollowElemContent> |
| </PortalToFollowElem> |
| ) |
| } |
|
|
| export default memo(NodeSelector) |
|
|