mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 01:18:05 +08:00
block-selector
This commit is contained in:
@ -1,137 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { createContext, useContext } from 'use-context-selector'
|
||||
import type {
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
} from '@floating-ui/react'
|
||||
import {
|
||||
FloatingPortal,
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useInteractions,
|
||||
} from '@floating-ui/react'
|
||||
import type { OnSelect } from './types'
|
||||
import BlockSelector from './index'
|
||||
|
||||
type UpdateParams = {
|
||||
from?: string
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
open?: boolean
|
||||
className?: string
|
||||
callback?: OnSelect
|
||||
}
|
||||
export type BlockSelectorContextValue = {
|
||||
from: string
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
referenceRef: any
|
||||
floatingRef: any
|
||||
floatingStyles: React.CSSProperties
|
||||
getFloatingProps: any
|
||||
handleToggle: (v: UpdateParams) => void
|
||||
}
|
||||
|
||||
export const BlockSelectorContext = createContext<BlockSelectorContextValue>({
|
||||
from: '',
|
||||
open: false,
|
||||
setOpen: () => {},
|
||||
referenceRef: null,
|
||||
floatingRef: null,
|
||||
floatingStyles: {},
|
||||
getFloatingProps: () => {},
|
||||
handleToggle: () => {},
|
||||
})
|
||||
export const useBlockSelectorContext = () => useContext(BlockSelectorContext)
|
||||
|
||||
type BlockSelectorContextProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
export const BlockSelectorContextProvider = ({
|
||||
children,
|
||||
}: BlockSelectorContextProviderProps) => {
|
||||
const [from, setFrom] = useState('node')
|
||||
const [open, setOpen] = useState(false)
|
||||
const [placement, setPlacement] = useState<Placement>('top')
|
||||
const [offsetValue, setOffsetValue] = useState<OffsetOptions>(0)
|
||||
const [className, setClassName] = useState<string>('')
|
||||
const callbackRef = useRef<OnSelect | undefined>(undefined)
|
||||
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
placement,
|
||||
strategy: 'fixed',
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
middleware: [
|
||||
flip(),
|
||||
shift(),
|
||||
offset(offsetValue),
|
||||
],
|
||||
})
|
||||
const dismiss = useDismiss(context)
|
||||
const { getFloatingProps } = useInteractions([
|
||||
dismiss,
|
||||
])
|
||||
|
||||
const handleToggle = useCallback(({
|
||||
from,
|
||||
open,
|
||||
placement,
|
||||
offset,
|
||||
className,
|
||||
callback,
|
||||
}: UpdateParams) => {
|
||||
setFrom(from || 'node')
|
||||
if (open !== undefined)
|
||||
setOpen(open)
|
||||
else
|
||||
setOpen(v => !v)
|
||||
setPlacement(placement || 'top')
|
||||
setOffsetValue(offset || 0)
|
||||
setClassName(className || '')
|
||||
callbackRef.current = callback
|
||||
}, [])
|
||||
|
||||
const handleSelect = useCallback<OnSelect>((type) => {
|
||||
if (callbackRef.current)
|
||||
callbackRef.current(type)
|
||||
setOpen(v => !v)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<BlockSelectorContext.Provider value={{
|
||||
from,
|
||||
open,
|
||||
setOpen,
|
||||
handleToggle,
|
||||
referenceRef: refs.setReference,
|
||||
floatingRef: refs.setFloating,
|
||||
floatingStyles,
|
||||
getFloatingProps,
|
||||
}}>
|
||||
{children}
|
||||
{
|
||||
open && (from === 'node' || from === 'panel') && (
|
||||
<FloatingPortal>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
className='z-[1000]'
|
||||
>
|
||||
<BlockSelector
|
||||
className={className}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</div>
|
||||
</FloatingPortal>
|
||||
)
|
||||
}
|
||||
</BlockSelectorContext.Provider>
|
||||
)
|
||||
}
|
||||
@ -1,30 +1,80 @@
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type {
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
} from '@floating-ui/react'
|
||||
import type { BlockEnum } from '../types'
|
||||
import Tabs from './tabs'
|
||||
import type { OnSelect } from './types'
|
||||
import { SearchLg } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import {
|
||||
Plus02,
|
||||
SearchLg,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
type NodeSelectorProps = {
|
||||
onSelect: OnSelect
|
||||
className?: string
|
||||
onSelect: (type: BlockEnum) => void
|
||||
trigger?: (open: boolean) => React.ReactNode
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
popupClassName?: string
|
||||
asChild?: boolean
|
||||
}
|
||||
const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
onSelect,
|
||||
className,
|
||||
trigger,
|
||||
placement = 'right',
|
||||
offset = 6,
|
||||
popupClassName,
|
||||
asChild,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
return (
|
||||
<div className={`w-[256px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${className}`}>
|
||||
<div className='px-2 pt-2'>
|
||||
<div className='flex items-center px-2 rounded-lg bg-gray-100'>
|
||||
<SearchLg className='shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400' />
|
||||
<input
|
||||
className='grow px-0.5 py-[7px] text-[13px] bg-transparent appearance-none outline-none'
|
||||
placeholder='Search block'
|
||||
/>
|
||||
<PortalToFollowElem
|
||||
placement={placement}
|
||||
offset={offset}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PortalToFollowElemTrigger asChild={asChild} onClick={() => setOpen(v => !v)}>
|
||||
{
|
||||
trigger
|
||||
? trigger(open)
|
||||
: (
|
||||
<div
|
||||
className={`
|
||||
hidden absolute -right-2 top-4 items-center justify-center
|
||||
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
|
||||
${open && '!flex'}
|
||||
`}
|
||||
>
|
||||
<Plus02 className='w-2.5 h-2.5 text-white' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className={`w-[256px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${popupClassName}`}>
|
||||
<div className='px-2 pt-2'>
|
||||
<div className='flex items-center px-2 rounded-lg bg-gray-100'>
|
||||
<SearchLg className='shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400' />
|
||||
<input
|
||||
className='grow px-0.5 py-[7px] text-[13px] bg-transparent appearance-none outline-none'
|
||||
placeholder='Search block'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs onSelect={onSelect} />
|
||||
</div>
|
||||
</div>
|
||||
<Tabs onSelect={onSelect} />
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import BlockIcon from '../block-icon'
|
||||
import type { OnSelect } from './types'
|
||||
import type { BlockEnum } from '../types'
|
||||
import {
|
||||
BLOCK_CLASSIFICATIONS,
|
||||
BLOCK_GROUP_BY_CLASSIFICATION,
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from './constants'
|
||||
|
||||
export type TabsProps = {
|
||||
onSelect: OnSelect
|
||||
onSelect: (type: BlockEnum) => void
|
||||
}
|
||||
const Tabs: FC<TabsProps> = ({
|
||||
onSelect,
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
import type { BlockEnum } from '../types'
|
||||
|
||||
export type OnSelect = (type: BlockEnum) => void
|
||||
Reference in New Issue
Block a user