Align featured tool hover layout and widen action dropdown

This commit is contained in:
lyzno1 2025-10-22 14:19:22 +08:00
parent 37805184d9
commit 0d7dde0639
No known key found for this signature in database
2 changed files with 92 additions and 30 deletions

View File

@ -5,8 +5,8 @@ import { BlockEnum, type ToolWithProvider } from '../types'
import type { ToolDefaultValue, ToolValue } from './types'
import type { Plugin } from '@/app/components/plugins/types'
import { useGetLanguage } from '@/context/i18n'
import Button from '@/app/components/base/button'
import BlockIcon from '../block-icon'
import Tooltip from '@/app/components/base/tooltip'
import { RiArrowDownSLine, RiArrowRightSLine, RiLoader2Line, RiMoreLine } from '@remixicon/react'
import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
import Loading from '@/app/components/base/loading'
@ -15,6 +15,8 @@ import { getMarketplaceUrl } from '@/utils/var'
import { ToolTypeEnum } from './types'
import { ViewType } from './view-type-select'
import Tools from './tools'
import { formatNumber } from '@/utils/format'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
const MAX_RECOMMENDED_COUNT = 15
const INITIAL_VISIBLE_COUNT = 5
@ -44,11 +46,16 @@ const FeaturedTools = ({
const language = useGetLanguage()
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
const [installingIdentifier, setInstallingIdentifier] = useState<string | null>(null)
const [isCollapsed, setIsCollapsed] = useState<boolean>(false)
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (typeof window === 'undefined')
return false
const stored = window.localStorage.getItem(STORAGE_KEY)
return stored === 'true'
})
const installMutation = useInstallPackageFromMarketPlace({
onSuccess: () => {
onInstallSuccess?.()
onSuccess: async () => {
await onInstallSuccess?.()
},
onSettled: () => {
setInstallingIdentifier(null)
@ -99,11 +106,11 @@ const FeaturedTools = ({
<div className='px-3 pb-3 pt-2'>
<button
type='button'
className='flex w-full items-center justify-between rounded-md px-0 py-1 text-left text-text-tertiary'
className='flex w-full items-center justify-between rounded-md px-0 py-1 text-left text-text-primary'
onClick={() => setIsCollapsed(prev => !prev)}
>
<span className='system-xs-medium'>{t('workflow.tabs.featuredTools')}</span>
{isCollapsed ? <RiArrowRightSLine className='size-3.5' /> : <RiArrowDownSLine className='size-3.5' />}
{isCollapsed ? <RiArrowRightSLine className='size-3.5 text-text-tertiary' /> : <RiArrowDownSLine className='size-3.5 text-text-tertiary' />}
</button>
{!isCollapsed && (
@ -198,33 +205,88 @@ function FeaturedToolUninstalledItem({
}: FeaturedToolUninstalledItemProps) {
const label = plugin.label?.[language] || plugin.name
const description = typeof plugin.brief === 'object' ? plugin.brief[language] : plugin.brief
const installCountLabel = t('plugin.install', { num: plugin.install_count?.toLocaleString() ?? 0 })
const installCountLabel = t('plugin.install', { num: formatNumber(plugin.install_count || 0) })
const [actionOpen, setActionOpen] = useState(false)
const [isActionHovered, setIsActionHovered] = useState(false)
useEffect(() => {
if (!actionOpen)
return
const handleScroll = () => {
setActionOpen(false)
setIsActionHovered(false)
}
window.addEventListener('scroll', handleScroll, true)
return () => {
window.removeEventListener('scroll', handleScroll, true)
}
}, [actionOpen])
return (
<div className='group flex items-center justify-between rounded-lg px-3 py-2 hover:bg-state-base-hover'>
<div className='flex min-w-0 items-center gap-3'>
<BlockIcon type={BlockEnum.Tool} toolIcon={plugin.icon} />
<div className='min-w-0'>
<div className='system-sm-medium truncate text-text-primary'>{label}</div>
{description && (
<div className='system-xs-regular truncate text-text-tertiary'>{description}</div>
)}
<Tooltip
position='right'
needsDelay={false}
popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg'
popupContent={(
<div>
<BlockIcon size='md' className='mb-2' type={BlockEnum.Tool} toolIcon={plugin.icon} />
<div className='mb-1 text-sm leading-5 text-text-primary'>{label}</div>
<div className='text-xs leading-[18px] text-text-secondary'>{description}</div>
</div>
)}
disabled={!description || isActionHovered || actionOpen}
>
<div
className='group flex h-8 w-full items-center rounded-lg pl-3 pr-1 hover:bg-state-base-hover'
onMouseLeave={() => {
if (!actionOpen)
setIsActionHovered(false)
}}
>
<div className='flex h-full min-w-0 items-center'>
<BlockIcon type={BlockEnum.Tool} toolIcon={plugin.icon} />
<div className='ml-2 min-w-0'>
<div className='system-sm-medium truncate text-text-secondary'>{label}</div>
</div>
</div>
<div className='ml-auto flex h-full items-center gap-1 pl-1'>
<span className='system-xs-regular text-text-tertiary group-hover:hidden'>{installCountLabel}</span>
<div
className={`h-full items-center gap-1 [&_.action-btn]:h-6 [&_.action-btn]:min-h-0 [&_.action-btn]:w-6 [&_.action-btn]:rounded-lg [&_.action-btn]:p-0 ${actionOpen ? 'flex' : 'hidden group-hover:flex'}`}
onMouseEnter={() => setIsActionHovered(true)}
onMouseLeave={() => {
if (!actionOpen)
setIsActionHovered(false)
}}
>
<button
type='button'
className='system-xs-medium flex h-6 cursor-pointer items-center gap-1 rounded px-1.5 text-components-button-secondary-accent-text hover:bg-state-base-hover'
disabled={installing}
onClick={onInstall}
>
{installing ? t('workflow.nodes.agent.pluginInstaller.installing') : t('workflow.nodes.agent.pluginInstaller.install')}
{installing && <RiLoader2Line className='size-3 animate-spin' />}
</button>
<div className='flex items-center'>
<Action
open={actionOpen}
onOpenChange={(value) => {
setActionOpen(value)
setIsActionHovered(value)
}}
author={plugin.org}
name={plugin.name}
version={plugin.latest_version}
/>
</div>
</div>
</div>
</div>
<div className='ml-3 flex items-center'>
<span className='system-xs-regular text-text-tertiary group-hover:hidden'>{installCountLabel}</span>
<Button
size='small'
variant='secondary'
className='hidden items-center gap-1 group-hover:flex'
disabled={installing}
onClick={onInstall}
>
{installing ? t('workflow.nodes.agent.pluginInstaller.installing') : t('workflow.nodes.agent.pluginInstaller.install')}
{installing && <RiLoader2Line className='size-3 animate-spin' />}
</Button>
</div>
</div>
</Tooltip>
)
}

View File

@ -77,7 +77,7 @@ const OperationDropdown: FC<Props> = ({
</ActionButton>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[9999]'>
<div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<div className='min-w-[176px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
<div onClick={handleDownload} className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.download')}</div>
<a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
</div>