mirror of
https://github.com/langgenius/dify.git
synced 2026-07-15 09:27:36 +08:00
fix(web): preserve folded app nav compact state
This commit is contained in:
@ -28,11 +28,19 @@ describe('AppNavItem', () => {
|
||||
expect(screen.getByTestId('item-operation-trigger')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should use responsive selectors for compact sidebar content', () => {
|
||||
it('should use responsive selectors for mobile content', () => {
|
||||
render(<AppNavItem {...baseProps} />)
|
||||
|
||||
expect(screen.getByText('My App')).toHaveClass('hidden', 'pc:block', 'group-data-folded/explore-sidebar:hidden')
|
||||
expect(screen.getByTestId('item-operation-trigger').parentElement).toHaveClass('hidden', 'pc:block', 'group-data-folded/explore-sidebar:hidden')
|
||||
expect(screen.getByText('My App')).toHaveClass('hidden', 'pc:block')
|
||||
expect(screen.getByTestId('item-operation-trigger').parentElement).toHaveClass('hidden', 'pc:block')
|
||||
})
|
||||
|
||||
it('should render icon-only content when folded', () => {
|
||||
render(<AppNavItem {...baseProps} isFolded />)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'My App' })).toBeInTheDocument()
|
||||
expect(screen.queryByText('My App')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('item-operation-trigger')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import ItemOperation from '@/app/components/explore/item-operation'
|
||||
import Link from '@/next/link'
|
||||
|
||||
type IAppNavItemProps = {
|
||||
isFolded?: boolean
|
||||
variant?: 'default' | 'mainNav'
|
||||
name: string
|
||||
id: string
|
||||
@ -24,6 +25,7 @@ type IAppNavItemProps = {
|
||||
}
|
||||
|
||||
export default function AppNavItem({
|
||||
isFolded = false,
|
||||
variant = 'default',
|
||||
name,
|
||||
id,
|
||||
@ -39,6 +41,7 @@ export default function AppNavItem({
|
||||
}: IAppNavItemProps) {
|
||||
const url = buildInstalledAppPath(id)
|
||||
const isMainNav = variant === 'mainNav'
|
||||
const shouldRenderFolded = !isMainNav && isFolded
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -46,31 +49,45 @@ export default function AppNavItem({
|
||||
className={cn(
|
||||
isMainNav
|
||||
? 'group flex h-8 items-center justify-between gap-2 rounded-lg py-0.5 pr-0.5 pl-2 transition-colors not-has-[>a[aria-current=page]]:hover:bg-state-base-hover has-[>a:focus-visible]:inset-ring-2 has-[>a:focus-visible]:inset-ring-state-accent-solid has-[>a[aria-current=page]]:bg-state-base-active'
|
||||
: 'group flex h-8 items-center justify-between rounded-lg px-2 system-sm-medium text-sm font-normal text-components-menu-item-text group-data-folded/explore-sidebar:justify-center group-data-folded/explore-sidebar:px-1 not-has-[>a[aria-current=page]]:hover:bg-state-base-hover not-has-[>a[aria-current=page]]:hover:text-components-menu-item-text-hover has-[>a:focus-visible]:inset-ring-2 has-[>a:focus-visible]:inset-ring-state-accent-solid has-[>a[aria-current=page]]:bg-state-base-active has-[>a[aria-current=page]]:text-components-menu-item-text-active mobile:justify-center mobile:px-1 pc:w-full pc:justify-start',
|
||||
: cn(
|
||||
'group flex h-8 items-center rounded-lg system-sm-medium text-sm font-normal text-components-menu-item-text transition-colors not-has-[>a[aria-current=page]]:hover:bg-state-base-hover not-has-[>a[aria-current=page]]:hover:text-components-menu-item-text-hover has-[>a:focus-visible]:inset-ring-2 has-[>a:focus-visible]:inset-ring-state-accent-solid has-[>a[aria-current=page]]:bg-state-base-active has-[>a[aria-current=page]]:text-components-menu-item-text-active',
|
||||
shouldRenderFolded ? 'justify-center px-1' : 'justify-between px-2 mobile:justify-center mobile:px-1 pc:w-full pc:justify-start',
|
||||
),
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
href={url}
|
||||
aria-current={isSelected ? 'page' : undefined}
|
||||
aria-label={name}
|
||||
title={name}
|
||||
className={cn(
|
||||
isMainNav
|
||||
? 'flex min-w-0 flex-1 items-center gap-2 outline-hidden'
|
||||
: 'flex min-w-0 flex-1 items-center justify-center outline-hidden group-data-folded/explore-sidebar:w-auto group-data-folded/explore-sidebar:flex-none group-data-folded/explore-sidebar:justify-center pc:w-0 pc:grow pc:justify-start pc:space-x-2',
|
||||
)}
|
||||
>
|
||||
<AppIcon size="tiny" className={cn(isMainNav && 'size-5 rounded-md text-sm')} iconType={icon_type} icon={icon} background={icon_background} imageUrl={icon_url} />
|
||||
<div className={cn(isMainNav ? 'min-w-0 flex-1 truncate py-1 pr-1 system-sm-regular' : 'hidden truncate system-sm-regular text-components-menu-item-text group-data-folded/explore-sidebar:hidden pc:block')} title={name}>{name}</div>
|
||||
</Link>
|
||||
<div className={cn(isMainNav ? 'h-6 shrink-0' : 'hidden h-6 shrink-0 group-data-folded/explore-sidebar:hidden pc:block')}>
|
||||
<ItemOperation
|
||||
isPinned={isPinned}
|
||||
togglePin={togglePin}
|
||||
isShowDelete={!uninstallable && !isSelected}
|
||||
onDelete={() => onDelete(id)}
|
||||
/>
|
||||
</div>
|
||||
{shouldRenderFolded
|
||||
? (
|
||||
<Link
|
||||
href={url}
|
||||
aria-current={isSelected ? 'page' : undefined}
|
||||
aria-label={name}
|
||||
title={name}
|
||||
className="flex min-w-0 flex-1 items-center justify-center outline-hidden"
|
||||
>
|
||||
<AppIcon size="tiny" iconType={icon_type} icon={icon} background={icon_background} imageUrl={icon_url} />
|
||||
</Link>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<Link
|
||||
href={url}
|
||||
aria-current={isSelected ? 'page' : undefined}
|
||||
title={name}
|
||||
className={cn(isMainNav ? 'flex min-w-0 flex-1 items-center gap-2 outline-hidden' : 'flex min-w-0 flex-1 items-center justify-center outline-hidden pc:w-0 pc:grow pc:justify-start pc:space-x-2')}
|
||||
>
|
||||
<AppIcon size="tiny" className={cn(isMainNav && 'size-5 rounded-md text-sm')} iconType={icon_type} icon={icon} background={icon_background} imageUrl={icon_url} />
|
||||
<div className={cn(isMainNav ? 'min-w-0 flex-1 truncate py-1 pr-1 system-sm-regular' : 'hidden truncate system-sm-regular text-components-menu-item-text pc:block')} title={name}>{name}</div>
|
||||
</Link>
|
||||
<div className={cn(isMainNav ? 'h-6 shrink-0' : 'hidden h-6 shrink-0 pc:block')}>
|
||||
<ItemOperation
|
||||
isPinned={isPinned}
|
||||
togglePin={togglePin}
|
||||
isShowDelete={!uninstallable && !isSelected}
|
||||
onDelete={() => onDelete(id)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -60,6 +60,7 @@ const SideBar = () => {
|
||||
const installedAppItems = installedApps.map(({ id, is_pinned, uninstallable, app: { name, icon_type, icon, icon_url, icon_background } }, index) => (
|
||||
<React.Fragment key={id}>
|
||||
<Item
|
||||
isFolded={isFold}
|
||||
name={name}
|
||||
icon_type={icon_type}
|
||||
icon={icon}
|
||||
@ -80,7 +81,7 @@ const SideBar = () => {
|
||||
))
|
||||
|
||||
return (
|
||||
<div data-folded={isFold || undefined} className={cn('group/explore-sidebar flex h-full w-fit shrink-0 cursor-pointer flex-col px-3 pt-6 sm:w-[240px]', isFold && 'sm:w-[56px]')}>
|
||||
<div className={cn('flex h-full w-fit shrink-0 cursor-pointer flex-col px-3 pt-6 sm:w-[240px]', isFold && 'sm:w-[56px]')}>
|
||||
<div className={cn(isDiscoverySelected ? 'text-text-accent' : 'text-text-tertiary')}>
|
||||
<Link
|
||||
href="/"
|
||||
|
||||
Reference in New Issue
Block a user