dify/web/app/components/base/chat/chat-with-history/sidebar/operation.tsx
2026-05-20 03:39:44 +00:00

124 lines
3.8 KiB
TypeScript

'use client'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
type Props = {
isActive?: boolean
isItemHovering?: boolean
isPinned: boolean
isShowRenameConversation?: boolean
onRenameConversation?: () => void
isShowDelete: boolean
togglePin: () => void
onDelete: () => void
}
const Operation: FC<Props> = ({
isActive,
isItemHovering,
isPinned,
togglePin,
isShowRenameConversation,
onRenameConversation,
isShowDelete,
onDelete,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const [isHovering, { setTrue: setIsHovering, setFalse: setNotHovering }] = useBoolean(false)
useEffect(() => {
if (!isItemHovering && !isHovering)
setOpen(false)
}, [isItemHovering, isHovering])
const handleDeferredAction = useCallback((action?: () => void) => {
if (!action)
return
setOpen(false)
queueMicrotask(action)
}, [])
return (
<DropdownMenu
modal={false}
open={open}
onOpenChange={setOpen}
>
<DropdownMenuTrigger
render={(
<ActionButton
className={cn((isItemHovering || open) ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0')}
state={
isActive
? ActionButtonState.Active
: open
? ActionButtonState.Hover
: ActionButtonState.Default
}
>
<span aria-hidden className="i-ri-more-fill size-4" />
</ActionButton>
)}
onClick={e => e.stopPropagation()}
/>
<DropdownMenuContent
placement="bottom-end"
sideOffset={4}
popupClassName="min-w-[120px]"
popupProps={{
onMouseEnter: setIsHovering,
onMouseLeave: setNotHovering,
onClick: e => e.stopPropagation(),
}}
>
<DropdownMenuItem
className="gap-2 px-2 system-md-regular"
onClick={(e) => {
e.stopPropagation()
togglePin()
}}
>
{isPinned && <span aria-hidden className="i-ri-unpin-line size-4 shrink-0 text-text-tertiary" />}
{!isPinned && <span aria-hidden className="i-ri-pushpin-line size-4 shrink-0 text-text-tertiary" />}
<span className="grow">{isPinned ? t('sidebar.action.unpin', { ns: 'explore' }) : t('sidebar.action.pin', { ns: 'explore' })}</span>
</DropdownMenuItem>
{isShowRenameConversation && (
<DropdownMenuItem
className="gap-2 px-2 system-md-regular"
onClick={(e) => {
e.stopPropagation()
handleDeferredAction(onRenameConversation)
}}
>
<span aria-hidden className="i-ri-edit-line size-4 shrink-0 text-text-tertiary" />
<span className="grow">{t('sidebar.action.rename', { ns: 'explore' })}</span>
</DropdownMenuItem>
)}
{isShowDelete && (
<DropdownMenuItem
variant="destructive"
className="gap-2 px-2 system-md-regular"
onClick={(e) => {
e.stopPropagation()
handleDeferredAction(onDelete)
}}
>
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
<span className="grow">{t('sidebar.action.delete', { ns: 'explore' })}</span>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
)
}
export default React.memo(Operation)