mirror of
https://github.com/langgenius/dify.git
synced 2026-05-10 05:56:31 +08:00
tweak dropdown menuy
This commit is contained in:
parent
1cada0c49c
commit
b07b68b531
@ -59,6 +59,8 @@ Do not copy existing code patterns blindly. Existing implementations are referen
|
||||
- Split deeper components by the data and state each layer actually needs. Each component should access only necessary data, and state should be pushed down to the lowest owner.
|
||||
- Keep cohesive forms, menu bodies, and one-off helpers local unless they need their own state, reuse, or semantic boundary.
|
||||
- Separate hidden secondary surfaces from the trigger's main flow. For dialogs, dropdowns, popovers, and similar branches, extract a small local component that owns the trigger, open state, and hidden content when that branch obscures the parent flow.
|
||||
- Preserve composability by separating behavior ownership from layout ownership. A dropdown action may own its trigger, open state, and menu content; the caller should own placement such as slots, offsets, and alignment.
|
||||
- Avoid unnecessary DOM hierarchy. Do not add wrapper elements unless they provide layout, semantics, accessibility, state ownership, or integration with a library API; prefer fragments or styling an existing element when possible.
|
||||
- Avoid shallow wrappers and prop renaming. Call the original function directly unless the wrapper adds validation, orchestration, error handling, state ownership, or a real semantic boundary.
|
||||
|
||||
## Navigation
|
||||
|
||||
@ -1,26 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import type { AppInstanceCard } from '@dify/contracts/enterprise/types.gen'
|
||||
import type { InstanceDetailTabKey } from '../detail/tabs'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLinkItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import Link from '@/next/link'
|
||||
import { openDeployDrawerAtom } from '../store'
|
||||
import { toAppMode } from '../utils'
|
||||
|
||||
const INSTANCE_CARD_MENU_TAB_KEYS = ['deploy', 'versions', 'access', 'settings'] satisfies InstanceDetailTabKey[]
|
||||
|
||||
function getInstanceTabHref(appInstanceId: string, tabKey: InstanceDetailTabKey) {
|
||||
return `/deployments/${appInstanceId}/${tabKey}`
|
||||
}
|
||||
|
||||
export function InstanceCard({ app }: {
|
||||
app: AppInstanceCard
|
||||
}) {
|
||||
@ -184,72 +186,43 @@ export function InstanceCard({ app }: {
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
<InstanceCardActions appInstanceId={appInstanceId} detailHref={detailHref} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InstanceCardActions({ appInstanceId, detailHref }: {
|
||||
appInstanceId: string
|
||||
detailHref: string
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
const [menuOpen, setMenuOpen] = useState(false)
|
||||
const openDeployDrawer = useSetAtom(openDeployDrawerAtom)
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none absolute right-0 bottom-1 left-0 flex h-[42px] shrink-0 items-center pt-1 pr-[6px] pb-[6px] pl-[14px]">
|
||||
<div
|
||||
className={cn(
|
||||
'absolute top-1/2 right-[6px] flex -translate-y-1/2 items-center transition-opacity',
|
||||
menuOpen
|
||||
? 'pointer-events-auto opacity-100'
|
||||
: 'pointer-events-none opacity-0 group-hover:pointer-events-auto group-hover:opacity-100',
|
||||
)}
|
||||
>
|
||||
<DropdownMenu modal={false} open={menuOpen} onOpenChange={setMenuOpen}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t('card.moreActions')}
|
||||
className={cn(
|
||||
menuOpen ? 'bg-state-base-hover shadow-none' : 'bg-transparent',
|
||||
'flex h-8 w-8 items-center justify-center rounded-md border-none p-2 hover:bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
|
||||
</DropdownMenuTrigger>
|
||||
{menuOpen && (
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-[216px]">
|
||||
<DropdownMenuItem
|
||||
className="gap-2 px-3"
|
||||
onClick={() => {
|
||||
setMenuOpen(false)
|
||||
openDeployDrawer({ appInstanceId })
|
||||
}}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">{t('card.menu.deploy')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuLinkItem
|
||||
className="gap-2 px-3"
|
||||
render={<Link href={detailHref} />}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">{t('card.menu.viewDetail')}</span>
|
||||
</DropdownMenuLinkItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
aria-disabled
|
||||
title={t('card.menu.deleteDisabled')}
|
||||
className="cursor-not-allowed gap-2 px-3 opacity-50"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<span className="system-sm-regular text-text-destructive">{t('card.menu.delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
)}
|
||||
</DropdownMenu>
|
||||
<div className="pointer-events-auto absolute right-[6px] bottom-1 flex h-[42px] items-center">
|
||||
<InstanceCardActions appInstanceId={appInstanceId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function InstanceCardActions({ appInstanceId }: {
|
||||
appInstanceId: string
|
||||
}) {
|
||||
const { t } = useTranslation('deployments')
|
||||
|
||||
return (
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t('card.moreActions')}
|
||||
className={cn(
|
||||
'flex h-8 w-8 items-center justify-center rounded-md border-none bg-transparent p-2 hover:bg-state-base-hover data-popup-open:bg-state-base-hover data-popup-open:shadow-none',
|
||||
)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill h-4 w-4 text-text-tertiary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-[216px]">
|
||||
{INSTANCE_CARD_MENU_TAB_KEYS.map((tabKey) => {
|
||||
const href = getInstanceTabHref(appInstanceId, tabKey)
|
||||
|
||||
return (
|
||||
<DropdownMenuLinkItem
|
||||
key={tabKey}
|
||||
className="gap-2 px-3"
|
||||
render={<Link href={href} />}
|
||||
>
|
||||
<span className="system-sm-regular text-text-secondary">{t(`tabs.${tabKey}.name`)}</span>
|
||||
</DropdownMenuLinkItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user