refactor(web): migrate app card icon controls (#40652)

This commit is contained in:
yyh
2026-08-13 07:04:17 +00:00
committed by GitHub
parent a0ca3d2e81
commit fb30dd2d58
2 changed files with 61 additions and 50 deletions
@@ -682,9 +682,13 @@ describe('AppCard', () => {
})
it('should star the app from the card action without navigating', async () => {
const user = userEvent.setup()
render(<AppCard app={mockApp} />)
fireEvent.click(screen.getByRole('button', { name: 'app.studio.starApp' }))
const starToggle = screen.getByRole('button', { name: 'app.studio.starApp' })
expect(starToggle).toHaveAttribute('aria-pressed', 'false')
await user.click(starToggle)
await waitFor(() => {
expect(mockStarAppMutation).toHaveBeenCalledWith({
@@ -695,10 +699,14 @@ describe('AppCard', () => {
})
it('should unstar the app from the filled star action', async () => {
const user = userEvent.setup()
const starredApp = createMockApp({ is_starred: true })
render(<AppCard app={starredApp} />)
fireEvent.click(screen.getByRole('button', { name: 'app.studio.unstarApp' }))
const starToggle = screen.getByRole('button', { name: 'app.studio.starApp' })
expect(starToggle).toHaveAttribute('aria-pressed', 'true')
await user.click(starToggle)
await waitFor(() => {
expect(mockUnstarAppMutation).toHaveBeenCalledWith({
@@ -724,13 +732,15 @@ describe('AppCard', () => {
})
it('should show edit option when dropdown menu is opened', async () => {
const user = userEvent.setup()
render(<AppCard app={mockApp} />)
fireEvent.click(getOperationsTrigger())
await user.click(getOperationsTrigger())
await waitFor(() => {
expect(screen.getByText('app.editApp')).toBeInTheDocument()
})
expect(mockPush).not.toHaveBeenCalled()
})
it('should show duplicate option when dropdown menu is opened', async () => {
@@ -4,7 +4,7 @@ import type {
AppPartial,
EnvironmentVariableItemResponse,
} from '@dify/contracts/api/console/apps/types.gen'
import type { FormEventHandler, MouseEvent } from 'react'
import type { FormEventHandler } from 'react'
import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import { zIconType } from '@dify/contracts/api/console/apps/zod.gen'
@@ -24,7 +24,9 @@ import {
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { Field, FieldControl, FieldLabel } from '@langgenius/dify-ui/field'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { toast } from '@langgenius/dify-ui/toast'
import { Toggle } from '@langgenius/dify-ui/toggle'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useMutation, useSuspenseQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
@@ -311,13 +313,10 @@ export const AppCardActionBar = memo(
}
const handleToggleStar = useCallback(
(e: MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.preventDefault()
(pressed: boolean) => {
if (isTogglingStar) return
const mutateStar = app.is_starred ? unstarApp : starApp
const mutateStar = pressed ? starApp : unstarApp
try {
mutateStar(
{ params: { app_id: app.id } },
@@ -338,7 +337,7 @@ export const AppCardActionBar = memo(
)
}
},
[app.id, app.is_starred, isTogglingStar, starApp, t, unstarApp],
[app.id, isTogglingStar, starApp, t, unstarApp],
)
const shouldShowEditOption = appACLCapabilities.canEdit
@@ -359,6 +358,7 @@ export const AppCardActionBar = memo(
const starActionLabel = app.is_starred
? t(($) => $['studio.unstarApp'], { ns: 'app' })
: t(($) => $['studio.starApp'], { ns: 'app' })
const starToggleLabel = t(($) => $['studio.starApp'], { ns: 'app' })
return (
<>
@@ -374,21 +374,23 @@ export const AppCardActionBar = memo(
<Tooltip>
<TooltipTrigger
render={
<button
type="button"
aria-label={starActionLabel}
<Toggle
pressed={app.is_starred}
disabled={isTogglingStar}
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-70"
onClick={handleToggleStar}
>
<StarIcon
aria-hidden
className={cn(
app.is_starred ? 'text-text-warning-secondary' : 'text-text-tertiary',
'size-4.5',
)}
/>
</button>
onPressedChange={handleToggleStar}
render={
<IconButton
size="lg"
aria-label={starToggleLabel}
className="group disabled:opacity-70"
>
<StarIcon
aria-hidden
className="size-4.5 text-text-tertiary group-data-pressed:text-text-warning-secondary"
/>
</IconButton>
}
/>
}
/>
<TooltipContent>{starActionLabel}</TooltipContent>
@@ -400,33 +402,32 @@ export const AppCardActionBar = memo(
onOpenChange={setIsOperationsMenuOpen}
>
<DropdownMenuTrigger
aria-label={
isExporting
? t(($) => $['operation.exporting'], { ns: 'common' })
: t(($) => $['operation.moreActionsFor'], {
ns: 'common',
name: app.name,
})
render={
<IconButton
size="lg"
aria-label={
isExporting
? t(($) => $['operation.exporting'], { ns: 'common' })
: t(($) => $['operation.moreActionsFor'], {
ns: 'common',
name: app.name,
})
}
disabled={isExporting}
className="data-popup-open:bg-state-base-hover"
>
<span
aria-hidden
className={cn(
'size-4.5 text-text-tertiary',
isExporting
? 'i-ri-loader-2-line animate-spin motion-reduce:animate-none'
: 'i-ri-more-fill',
)}
/>
</IconButton>
}
disabled={isExporting}
className={cn(
'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden disabled:cursor-not-allowed data-popup-open:bg-state-base-hover',
)}
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
}}
>
<span
aria-hidden
className={cn(
'h-4.5 w-4.5 text-text-tertiary',
isExporting
? 'i-ri-loader-2-line animate-spin motion-reduce:animate-none'
: 'i-ri-more-fill',
)}
/>
</DropdownMenuTrigger>
/>
<DropdownMenuContent
placement="bottom-end"
sideOffset={4}