refactor(web): migrate help menu icon trigger (#40650)

This commit is contained in:
yyh
2026-08-13 07:04:15 +00:00
committed by GitHub
parent 146f402a8c
commit 204a906de8
4 changed files with 77 additions and 39 deletions
+2 -4
View File
@@ -31,10 +31,8 @@ function SecondarySidebarHelpMenu({ triggerClassName }: { triggerClassName?: str
return (
<HelpMenu
triggerIcon={secondarySidebarHelpTriggerIcon}
triggerClassName={cn(
'size-8 border-0 bg-transparent shadow-none hover:bg-state-base-hover hover:text-text-secondary',
triggerClassName,
)}
triggerSize="lg"
triggerClassName={triggerClassName}
/>
)
}
@@ -1313,7 +1313,12 @@ describe('MainNav', () => {
const top = typeof optionsOrX === 'object' ? optionsOrX.top : y
scrollViewport.scrollTop = Number(top ?? 0)
}
await user.click(await screen.findByRole('button', { name: 'common.operation.search' }))
const searchButton = await screen.findByRole('button', { name: 'common.operation.search' })
expect(searchButton).toHaveAttribute('aria-expanded', 'false')
await user.click(searchButton)
expect(searchButton).toHaveAttribute('aria-expanded', 'true')
const searchInput = screen.getByPlaceholderText('common.mainNav.webApps.searchPlaceholder')
await user.type(searchInput, 'beta')
@@ -1326,6 +1331,19 @@ describe('MainNav', () => {
expect(
screen.getByRole('link', { name: 'common.mainNav.webApps.openApp:{"name":"Beta Tool"}' }),
).toHaveAttribute('href', '/installed/installed-2')
const webAppsButton = screen.getByRole('button', { name: 'explore.sidebar.webApps' })
await user.click(webAppsButton)
expect(searchButton).toHaveAttribute('aria-expanded', 'false')
expect(
screen.queryByPlaceholderText('common.mainNav.webApps.searchPlaceholder'),
).not.toBeInTheDocument()
await user.click(webAppsButton)
expect(searchButton).toHaveAttribute('aria-expanded', 'true')
expect(screen.getByPlaceholderText('common.mainNav.webApps.searchPlaceholder')).toHaveValue(
'beta',
)
})
it('hides the installed web apps section while installed apps are loading', () => {
@@ -1,6 +1,7 @@
'use client'
import type { ReactNode } from 'react'
import type { IconButtonProps } from '@langgenius/dify-ui/icon-button'
import type { ReactElement } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
@@ -12,6 +13,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Switch } from '@langgenius/dify-ui/switch'
import { skipToken, useQuery, useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue, useSetAtom } from 'jotai'
@@ -52,8 +54,9 @@ import AccountAboutDialog from './help-menu/account-about-dialog'
import SupportMenu from './support-menu'
type HelpMenuProps = {
triggerIcon?: ReactNode
triggerIcon?: ReactElement
triggerClassName?: string
triggerSize?: IconButtonProps['size']
}
const defaultTriggerIcon = (
@@ -84,7 +87,7 @@ const MenuSwitchIndicator = ({ checked }: { checked: boolean }) => (
/>
)
const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMenuProps) => {
const HelpMenu = ({ triggerIcon, triggerClassName, triggerSize }: HelpMenuProps) => {
const { t } = useTranslation()
const docLink = useDocLink()
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
@@ -114,6 +117,7 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe
const disableStepByStepTour = useSetAtom(disableStepByStepTourForCurrentWorkspaceAtom)
const setStepByStepTourShellMode = useSetStepByStepTourShellMode()
const [aboutOpen, setAboutOpen] = useState(false)
const usesDefaultTrigger = !triggerIcon
const shouldShowLearnDifySwitch = systemFeatures.enable_learn_app
const shouldShowStepByStepTourSwitch = systemFeatures.enable_step_by_step_tour
const canToggleStepByStepTour =
@@ -148,17 +152,27 @@ const HelpMenu = ({ triggerIcon = defaultTriggerIcon, triggerClassName }: HelpMe
<>
<DropdownMenu onOpenChange={handleOpenChange}>
<DropdownMenuTrigger
aria-label={t(($) => $['mainNav.help.openMenu'], { ns: 'common' })}
data-learn-dify-help-target
className={cn(
'inline-flex size-7 shrink-0 cursor-pointer items-center justify-center rounded-full border border-components-card-border bg-components-card-bg p-0 text-text-tertiary shadow-xs transition-colors hover:bg-components-card-bg-alt hover:text-saas-dify-blue-inverted focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden',
triggerClassName,
'data-popup-open:bg-components-card-bg-alt data-popup-open:text-saas-dify-blue-inverted',
skipRecoveryVisible && styles.stepByStepTourRecoveryPulse,
)}
>
{triggerIcon}
</DropdownMenuTrigger>
render={
<IconButton
size={triggerSize ?? 'lg'}
aria-label={t(($) => $['mainNav.help.openMenu'], { ns: 'common' })}
className={cn(
usesDefaultTrigger && [
'rounded-full border border-components-card-border bg-components-card-bg text-text-tertiary shadow-xs transition-colors hover:bg-components-card-bg-alt hover:text-saas-dify-blue-inverted',
!triggerSize && 'size-7 p-0',
'data-popup-open:bg-components-card-bg-alt data-popup-open:text-saas-dify-blue-inverted',
],
!usesDefaultTrigger &&
'data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary',
triggerClassName,
skipRecoveryVisible && styles.stepByStepTourRecoveryPulse,
)}
>
{triggerIcon ?? defaultTriggerIcon}
</IconButton>
}
/>
<DropdownMenuContent
placement="top-end"
sideOffset={8}
@@ -16,6 +16,8 @@ import {
} from '@langgenius/dify-ui/alert-dialog'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import {
ScrollArea,
ScrollAreaContent,
@@ -89,6 +91,12 @@ const WebAppsSectionContent = () => {
setSearchText(value)
}
const handleSearchVisibleChange = (visible: boolean) => {
setAppsExpanded(true)
if (!visible) handleSearchTextChange('')
setSearchVisible(visible)
}
const handleDelete = () => {
if (!uninstallDialogAppId) return
@@ -136,7 +144,11 @@ const WebAppsSectionContent = () => {
/>
)
return (
<div className="flex min-h-0 flex-1 flex-col">
<Collapsible
open={appsExpanded && searchVisible}
onOpenChange={handleSearchVisibleChange}
className="flex min-h-0 flex-1 flex-col"
>
<div className="flex items-center justify-between py-1 pr-2 pl-2">
<button
type="button"
@@ -154,26 +166,22 @@ const WebAppsSectionContent = () => {
/>
</button>
<div className="flex items-center gap-0.5">
<button
type="button"
aria-label={t(($) => $['operation.search'], { ns: 'common' })}
className={cn(
'flex h-6 w-6 items-center justify-center rounded-md p-0.5 text-text-tertiary outline-hidden hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
searchVisible && 'bg-state-base-hover text-text-secondary',
)}
onClick={() => {
setAppsExpanded(true)
if (searchVisible) handleSearchTextChange('')
setSearchVisible(!searchVisible)
}}
>
<span className="flex size-5 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-search-line size-3.5" />
</span>
</button>
<CollapsibleTrigger
className="size-6 min-h-0 w-6 justify-center gap-0 rounded-md p-0.5 hover:not-data-disabled:bg-state-base-hover hover:not-data-disabled:text-text-secondary data-panel-open:bg-state-base-hover data-panel-open:text-text-secondary"
render={
<IconButton
aria-label={t(($) => $['operation.search'], { ns: 'common' })}
className="rounded-md"
>
<span className="flex size-5 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-search-line size-3.5" />
</span>
</IconButton>
}
/>
</div>
</div>
{appsExpanded && searchVisible && (
<CollapsiblePanel className="shrink-0">
<div className="px-2 pb-2">
<SearchInput
value={searchText}
@@ -183,7 +191,7 @@ const WebAppsSectionContent = () => {
autoFocus
/>
</div>
)}
</CollapsiblePanel>
{appsExpanded && (
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
<ScrollAreaViewport
@@ -298,7 +306,7 @@ const WebAppsSectionContent = () => {
</AlertDialogActions>
</AlertDialogContent>
</AlertDialog>
</div>
</Collapsible>
)
}