block-selector edit

This commit is contained in:
StyleZhang
2024-02-20 20:12:20 +08:00
parent d58a1b1359
commit 13a54c3f56
8 changed files with 249 additions and 157 deletions

View File

@ -0,0 +1,123 @@
'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
className?: string
callback?: OnSelect
}
export type BlockSelectorContextValue = {
from: string
open: boolean
setOpen: (open: boolean) => void
referenceRef: any
handleToggle: (v: UpdateParams) => void
}
export const BlockSelectorContext = createContext<BlockSelectorContextValue>({
from: '',
open: false,
setOpen: () => {},
referenceRef: null,
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,
placement,
offset,
className,
callback,
}: UpdateParams) => {
setFrom(from || 'node')
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,
}}>
{children}
{
open && (
<FloatingPortal>
<div
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
className='z-[1000]'
>
<BlockSelector
className={className}
onSelect={handleSelect}
/>
</div>
</FloatingPortal>
)
}
</BlockSelectorContext.Provider>
)
}

View File

@ -1,87 +1,30 @@
import type { FC, ReactElement } from 'react'
import {
memo,
useState,
} from 'react'
import type {
OffsetOptions,
Placement,
} from '@floating-ui/react'
import {
FloatingPortal,
flip,
offset,
shift,
useClick,
useDismiss,
useFloating,
useInteractions,
} from '@floating-ui/react'
import type { FC } from 'react'
import { memo } from 'react'
import Tabs from './tabs'
import type { OnSelect } from './types'
import { SearchLg } from '@/app/components/base/icons/src/vender/line/general'
type NodeSelectorProps = {
placement?: Placement
offset?: OffsetOptions
onSelect: OnSelect
className?: string
children: (props: any) => ReactElement
}
const NodeSelector: FC<NodeSelectorProps> = ({
placement = 'top',
offset: offsetValue = 0,
onSelect,
className,
children,
}) => {
const [open, setOpen] = useState(false)
const { refs, floatingStyles, context } = useFloating({
placement,
strategy: 'fixed',
open,
onOpenChange: setOpen,
middleware: [
flip(),
shift(),
offset(offsetValue),
],
})
const click = useClick(context)
const dismiss = useDismiss(context, {
bubbles: false,
})
const { getReferenceProps, getFloatingProps } = useInteractions([
click,
dismiss,
])
return (
<>
{children({ ...getReferenceProps(), ref: refs.setReference, open })}
{
open && (
<FloatingPortal>
<div
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
className='z-[1000]'
>
<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'
/>
</div>
</div>
<Tabs />
</div>
</div>
</FloatingPortal>
)
}
</>
<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'
/>
</div>
</div>
<Tabs onSelect={onSelect} />
</div>
)
}

View File

@ -1,24 +1,23 @@
import type { FC } from 'react'
import {
memo,
useState,
} from 'react'
import { useNodeId } from 'reactflow'
import BlockIcon from '../block-icon'
import { useWorkflowContext } from '../context'
import type { OnSelect } from './types'
import {
BLOCK_CLASSIFICATIONS,
BLOCK_GROUP_BY_CLASSIFICATION,
TABS,
} from './constants'
const Tabs = () => {
const {
nodes,
handleAddNextNode,
} = useWorkflowContext()
export type TabsProps = {
onSelect: OnSelect
}
const Tabs: FC<TabsProps> = ({
onSelect,
}) => {
const [activeTab, setActiveTab] = useState(TABS[0].key)
const nodeId = useNodeId()
const currentNode = nodes.find(node => node.id === nodeId)
return (
<div>
@ -59,7 +58,7 @@ const Tabs = () => {
className='flex items-center px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
onClick={(e) => {
e.stopPropagation()
handleAddNextNode(currentNode!, block.type)
onSelect(block.type)
}}
>
<BlockIcon

View File

@ -0,0 +1,3 @@
import type { BlockEnum } from '../types'
export type OnSelect = (type: BlockEnum) => void