chore: templates item ui and learn dify

This commit is contained in:
Joel 2026-05-11 14:29:48 +08:00
parent d9b34fc885
commit 155b7496c5
6 changed files with 52 additions and 43 deletions

View File

@ -90,7 +90,14 @@ describe('AppCard', () => {
renderComponent({ app: createApp({ description: 'Very long description text' }) })
const descWrapper = screen.getByText('Very long description text')
expect(descWrapper).toHaveClass('line-clamp-4')
expect(descWrapper).toHaveClass('line-clamp-2')
})
it('should render category badges', () => {
renderComponent({ app: createApp({ categories: ['Search', 'Productivity'] }) })
expect(screen.getByText('Search')).toBeInTheDocument()
expect(screen.getByText('Productivity')).toBeInTheDocument()
})
})

View File

@ -1,10 +1,8 @@
'use client'
import type { App } from '@/models/explore'
import type { TryAppSelection } from '@/types/try-app'
import { PlusIcon } from '@heroicons/react/20/solid'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { RiInformation2Line } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { trackEvent } from '@/app/components/base/amplitude'
@ -44,8 +42,8 @@ const AppCard = ({
}
return (
<div className={cn('group relative col-span-1 flex cursor-pointer flex-col overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg pb-2 shadow-sm transition-all duration-200 ease-in-out hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-lg')}>
<div className="flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pt-[14px] pb-3">
<div className="group relative col-span-1 flex h-[142px] cursor-pointer flex-col overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg pb-3 shadow-xs shadow-shadow-shadow-3">
<div className="flex shrink-0 items-center gap-3 px-4 pt-4 pb-2">
<div className="relative shrink-0">
<AppIcon
size="large"
@ -55,16 +53,16 @@ const AppCard = ({
imageUrl={appBasicInfo.icon_url}
/>
<AppTypeIcon
wrapperClassName="absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm"
className="h-3 w-3"
wrapperClassName="absolute -right-0.5 -bottom-0.5 size-4 rounded border-components-panel-on-panel-item-bg shadow-sm"
className="size-3"
type={appBasicInfo.mode}
/>
</div>
<div className="w-0 grow py-px">
<div className="flex items-center text-sm leading-5 font-semibold text-text-secondary">
<div className="flex w-0 grow flex-col gap-1 py-px">
<div className="flex items-center system-md-semibold text-text-secondary">
<div className="truncate" title={appBasicInfo.name}>{appBasicInfo.name}</div>
</div>
<div className="flex items-center text-[10px] leading-[18px] font-medium text-text-tertiary">
<div className="flex items-center system-2xs-medium-uppercase text-text-tertiary">
{appBasicInfo.mode === AppModeEnum.ADVANCED_CHAT && <div className="truncate">{t('types.advanced', { ns: 'app' }).toUpperCase()}</div>}
{appBasicInfo.mode === AppModeEnum.CHAT && <div className="truncate">{t('types.chatbot', { ns: 'app' }).toUpperCase()}</div>}
{appBasicInfo.mode === AppModeEnum.AGENT_CHAT && <div className="truncate">{t('types.agent', { ns: 'app' }).toUpperCase()}</div>}
@ -73,24 +71,35 @@ const AppCard = ({
</div>
</div>
</div>
<div className="description-wrapper h-[90px] px-[14px] system-xs-regular text-text-tertiary">
<div className="line-clamp-4 group-hover:line-clamp-2">
<div className="flex shrink-0 items-start px-4 py-1">
<div className="line-clamp-2 min-h-8 flex-1 system-xs-regular text-text-tertiary">
{app.description}
</div>
</div>
<div className="relative flex h-[26px] w-full shrink-0 flex-col gap-2 overflow-hidden px-3">
<div className="flex w-full shrink-0 items-center gap-1 rounded-lg p-1">
{app.categories.slice(0, 2).map(category => (
<div key={category} className="flex min-w-[18px] shrink-0 items-center justify-center gap-0.5 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-[5px] py-[3px] system-2xs-medium-uppercase text-text-tertiary">
<span className="i-custom-vender-line-financeAndECommerce-tag-01 size-3 shrink-0" />
<span className="whitespace-nowrap">{category}</span>
</div>
))}
</div>
<div className="pointer-events-none absolute top-0 right-0 bottom-0 w-20 bg-linear-to-r from-components-card-bg-alt-transparent to-components-card-bg-alt" />
</div>
{isExplore && (canCreate || isTrialApp) && (
<div className={cn('absolute right-0 bottom-0 left-0 hidden bg-linear-to-t from-components-panel-gradient-2 from-[60.27%] to-transparent p-4 pt-8 group-hover:flex')}>
<div className={cn('grid h-8 w-full grid-cols-1 space-x-2', canCreate && 'grid-cols-2')}>
{
canCreate && (
<Button variant="primary" className="h-7" onClick={() => onCreate()}>
<PlusIcon className="mr-1 h-4 w-4" />
<span className="mr-1 i-heroicons-plus-20-solid h-4 w-4" />
<span className="text-xs">{t('appCard.addToWorkspace', { ns: 'explore' })}</span>
</Button>
)
}
<Button className="h-7" onClick={handleTryApp}>
<RiInformation2Line className="mr-1 size-4" />
<span className="mr-1 i-ri-information-2-line size-4" />
<span>{t('appCard.try', { ns: 'explore' })}</span>
</Button>
</div>

View File

@ -239,7 +239,7 @@ const Apps = ({
<nav
className={cn(
s.appList,
'grid shrink-0 content-start gap-4 px-6 sm:px-12',
'grid shrink-0 content-start gap-3 px-6 sm:px-12',
)}
>
{searchFilteredList.map(app => (

View File

@ -10,19 +10,13 @@
grid-template-columns: repeat(1, minmax(0, 1fr))
}
@media (min-width: 1624px) {
@media (min-width: 1280px) {
.appList {
grid-template-columns: repeat(4, minmax(0, 1fr))
}
}
@media (min-width: 1300px) and (max-width: 1624px) {
.appList {
grid-template-columns: repeat(3, minmax(0, 1fr))
}
}
@media (min-width: 1025px) and (max-width: 1300px) {
@media (min-width: 640px) and (max-width: 1279px) {
.appList {
grid-template-columns: repeat(2, minmax(0, 1fr))
}

View File

@ -341,18 +341,20 @@ describe('MainNav', () => {
window.removeEventListener(GOTO_ANYTHING_OPEN_EVENT, handleOpen)
})
it('shows hidden Learn Dify in help menu and restores it from localStorage', async () => {
it('shows Learn Dify switch in help menu and restores it from localStorage', async () => {
localStorage.setItem(LEARN_DIFY_HIDDEN_STORAGE_KEY, 'true')
renderMainNav()
fireEvent.click(screen.getByRole('button', { name: 'common.mainNav.help.openMenu' }))
fireEvent.click(await screen.findByText('common.mainNav.help.learnDify'))
expect(await screen.findByText('common.mainNav.help.learnDify')).toBeInTheDocument()
fireEvent.click(screen.getByRole('switch', { name: 'common.mainNav.help.learnDify' }))
await waitFor(() => {
expect(localStorage.getItem(LEARN_DIFY_HIDDEN_STORAGE_KEY)).toBe('false')
})
expect(mockPush).toHaveBeenCalledWith('/explore/apps')
expect(mockPush).not.toHaveBeenCalled()
})
it('opens workspace settings, members, provider credits, upgrade, and workspace switching actions', async () => {

View File

@ -10,6 +10,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { Switch } from '@langgenius/dify-ui/switch'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -24,7 +25,6 @@ import { IS_CLOUD_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useDocLink } from '@/context/i18n'
import { env } from '@/env'
import { useRouter } from '@/next/navigation'
import { systemFeaturesQueryOptions } from '@/service/system-features'
const HelpMenu = () => {
@ -32,7 +32,6 @@ const HelpMenu = () => {
const docLink = useDocLink()
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())
const { langGeniusVersionInfo, isCurrentWorkspaceOwner } = useAppContext()
const router = useRouter()
const [learnDifyHidden, setLearnDifyHidden] = useLearnDifyHiddenState()
const [aboutVisible, setAboutVisible] = useState(false)
const [open, setOpen] = useState(false)
@ -66,21 +65,19 @@ const HelpMenu = () => {
/>
</DropdownMenuLinkItem>
<Support closeAccountDropdown={() => setOpen(false)} />
{learnDifyHidden && (
<DropdownMenuItem
className="mx-0 h-8 gap-1 px-3 py-1.5"
onClick={() => {
setLearnDifyHidden(false)
setOpen(false)
router.push('/explore/apps')
}}
>
<MenuItemContent
iconClassName="i-ri-graduation-cap-line"
label={t('mainNav.help.learnDify', { ns: 'common' })}
/>
</DropdownMenuItem>
)}
<div className="mx-0 flex h-8 items-center gap-1 rounded-lg py-1 pr-2 pl-3">
<span aria-hidden className="i-custom-vender-workflow-docs-extractor size-4 shrink-0 text-text-tertiary" />
<span className="min-w-0 flex-1 truncate px-1 py-0.5 system-md-regular text-text-secondary">
{t('mainNav.help.learnDify', { ns: 'common' })}
</span>
<Switch
size="md"
checked={!learnDifyHidden}
aria-label={t('mainNav.help.learnDify', { ns: 'common' })}
onClick={event => event.stopPropagation()}
onCheckedChange={checked => setLearnDifyHidden(!checked)}
/>
</div>
{IS_CLOUD_EDITION && isCurrentWorkspaceOwner && <Compliance />}
</DropdownMenuGroup>
<DropdownMenuSeparator className="my-0!" />