fix: improve workflow checklist semantics (#36006)

This commit is contained in:
yyh 2026-05-11 12:22:46 +08:00 committed by GitHub
parent 153064bbd4
commit 2162ea6a68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 62 additions and 30 deletions

View File

@ -54,6 +54,8 @@ vi.mock('@langgenius/dify-ui/popover', () => ({
},
PopoverTrigger: ({ render }: { render: ReactNode }) => <>{render}</>,
PopoverContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
PopoverTitle: ({ children, className }: { children: ReactNode, className?: string }) => <h2 className={className}>{children}</h2>,
PopoverDescription: ({ children, className }: { children: ReactNode, className?: string }) => <p className={className}>{children}</p>,
PopoverClose: ({ children, className }: { children: ReactNode, className?: string }) => <button className={className}>{children}</button>,
}))

View File

@ -36,6 +36,7 @@ describe('ChecklistNodeGroup', () => {
expect(screen.getByText('Needs configuration')).toBeInTheDocument()
expect(screen.getByText(/needConnectTip/i)).toBeInTheDocument()
expect(screen.getAllByText(/goToFix/i)).toHaveLength(2)
expect(screen.getByRole('button', { name: /Needs configuration/i })).toHaveAttribute('title', 'Needs configuration')
fireEvent.click(screen.getByText('Needs configuration'))
@ -57,5 +58,7 @@ describe('ChecklistNodeGroup', () => {
expect(onItemClick).not.toHaveBeenCalled()
expect(screen.queryByText(/goToFix/i)).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: /Needs configuration/i })).not.toBeInTheDocument()
expect(screen.getByText('Needs configuration').parentElement).toHaveAttribute('title', 'Needs configuration')
})
})

View File

@ -7,6 +7,8 @@ import {
Popover,
PopoverClose,
PopoverContent,
PopoverDescription,
PopoverTitle,
PopoverTrigger,
} from '@langgenius/dify-ui/popover'
import {
@ -43,6 +45,7 @@ const WorkflowChecklist = ({
const nodes = useNodes()
const needWarningNodes = useChecklist(nodes, edges)
const { handleNodeSelect } = useNodesInteractions()
const checklistLabel = t('panel.checklist', { ns: 'workflow' })
const { pluginItems, nodeItems } = useMemo(() => {
const plugins: ChecklistItem[] = []
@ -75,12 +78,14 @@ const WorkflowChecklist = ({
disabled && 'cursor-not-allowed opacity-50',
)}
disabled={disabled || undefined}
aria-label={checklistLabel}
>
<span
className={cn('group flex h-full w-full items-center justify-center rounded-md hover:bg-state-accent-hover', open && 'bg-state-accent-hover')}
>
<span
className={cn('i-ri-list-check-3 h-4 w-4 group-hover:text-components-button-secondary-accent-text', open ? 'text-components-button-secondary-accent-text' : 'text-components-button-ghost-text')}
aria-hidden="true"
/>
</span>
{!!needWarningNodes.length && (
@ -104,19 +109,22 @@ const WorkflowChecklist = ({
<div className="flex flex-col gap-0.5 px-3 pt-3.5 pb-1">
<div className="flex items-start px-1">
<div className="min-w-0 grow pr-8">
<h2 className="text-base leading-6 font-semibold text-text-primary">
{t('panel.checklist', { ns: 'workflow' })}
<PopoverTitle className="text-base leading-6 font-semibold text-text-primary">
{checklistLabel}
{needWarningNodes.length > 0 && `(${needWarningNodes.length})`}
</h2>
</PopoverTitle>
</div>
<PopoverClose className="-mt-0.5 -mr-0.5 flex size-7 shrink-0 items-center justify-center rounded-lg">
<span className="i-ri-close-line size-4 text-text-tertiary" />
<PopoverClose
className="-mt-0.5 -mr-0.5 flex size-7 shrink-0 items-center justify-center rounded-lg"
aria-label={t('operation.close', { ns: 'common' })}
>
<span className="i-ri-close-line size-4 text-text-tertiary" aria-hidden="true" />
</PopoverClose>
</div>
{needWarningNodes.length > 0 && (
<p className="px-1 text-xs leading-4 text-text-tertiary">
<PopoverDescription className="px-1 text-xs leading-4 text-text-tertiary">
{t('panel.checklistDescription', { ns: 'workflow' })}
</p>
</PopoverDescription>
)}
</div>

View File

@ -45,29 +45,48 @@ export const ChecklistNodeGroup = memo(({
</span>
</div>
<div className="p-1">
{subItems.map(sub => (
<div
key={sub.key}
className={cn(
'group/item flex items-start gap-2 rounded-lg px-1',
goToEnabled && 'cursor-pointer hover:bg-state-base-hover',
)}
onClick={() => goToEnabled && onItemClick(item)}
>
<ItemIndicator />
<span className="min-w-0 grow py-1 text-xs leading-4 text-text-warning">
{sub.message}
</span>
{goToEnabled && (
<div className="flex shrink-0 items-center gap-0.5 pt-1 pr-0.5 opacity-0 transition-opacity duration-150 group-hover/item:opacity-100">
<span className="text-xs leading-4 font-medium whitespace-nowrap text-text-accent">
{t('panel.goToFix', { ns: 'workflow' })}
</span>
<span className="i-ri-arrow-right-line size-3.5 text-text-accent" />
</div>
)}
</div>
))}
{subItems.map((sub) => {
const content = (
<>
<ItemIndicator />
<span className="min-w-0 grow truncate text-xs leading-4 text-text-warning">
{sub.message}
</span>
{goToEnabled && (
<div className="flex shrink-0 items-center gap-0.5 pr-0.5 opacity-0 transition-opacity duration-150 group-hover/item:opacity-100">
<span className="text-xs leading-4 font-medium whitespace-nowrap text-text-accent">
{t('panel.goToFix', { ns: 'workflow' })}
</span>
<span className="i-ri-arrow-right-line size-3.5 text-text-accent" aria-hidden="true" />
</div>
)}
</>
)
const className = cn(
'group/item flex w-full items-center gap-2 rounded-lg px-1 text-left',
goToEnabled && 'cursor-pointer hover:bg-state-base-hover',
)
if (goToEnabled) {
return (
<button
key={sub.key}
type="button"
className={cn(className, 'border-none bg-transparent')}
title={sub.message}
onClick={() => onItemClick(item)}
>
{content}
</button>
)
}
return (
<div key={sub.key} className={className} title={sub.message}>
{content}
</div>
)
})}
</div>
</div>
)