mirror of
https://github.com/langgenius/dify.git
synced 2026-09-19 02:07:44 +08:00
refactor(web): migrate help menu icon trigger (#40650)
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user