|
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) |
|
|