dify/web/app/components/base/access-point/url.tsx
Joel 6208b33e08
chore: correct ARIA roles and preserve control interactions (#41738)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
2026-09-04 06:06:02 +00:00

189 lines
5.5 KiB
TypeScript

'use client'
import { Button, buttonVariants } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useTranslation } from 'react-i18next'
import { CopyFeedback } from '@/app/components/base/copy-feedback'
import ShareQRCode from '@/app/components/base/qrcode'
import { AccessPointEndpoint } from './card'
type AccessPointUrlProps = {
enabled: boolean
label: string
unavailableLabel: string
value: string
copiedLabel?: string
copyDisabled?: boolean
copyLabel?: string
loading?: boolean
unavailable?: boolean
showOpen?: boolean
showQrCode?: boolean
showRegenerate?: boolean
openUrl?: string
onCopyError?: () => void
onRegenerate?: () => void
openDisabledReason?: string
openLabel?: string
qrCodeDownloadLabel?: string
qrCodeLabel?: string
qrCodeScanLabel?: string
regenerateLabel?: string
regenerateDisabled?: boolean
regenerating?: boolean
}
export function AccessPointUrl({
copiedLabel,
copyDisabled = false,
copyLabel,
enabled,
label,
loading = false,
onCopyError,
onRegenerate,
openDisabledReason,
openLabel,
openUrl,
qrCodeDownloadLabel,
qrCodeLabel,
qrCodeScanLabel,
regenerateDisabled = false,
regenerateLabel,
regenerating = false,
showOpen = false,
showQrCode = false,
showRegenerate = false,
unavailable = false,
unavailableLabel,
value,
}: AccessPointUrlProps) {
const { t } = useTranslation()
const detailsAvailable = !loading && !unavailable
const disabledOpenButton = (
<Button
variant="secondary"
size="small"
className="h-6 gap-1 px-1.5"
disabled
focusableWhenDisabled={Boolean(openDisabledReason)}
>
<span aria-hidden className="i-ri-external-link-line size-3.5" />
{openLabel}
</Button>
)
const disabledOpenAction = openDisabledReason ? (
<Tooltip>
<TooltipTrigger render={disabledOpenButton} />
<TooltipContent>{openDisabledReason}</TooltipContent>
</Tooltip>
) : (
disabledOpenButton
)
const disabledActions = (
<div className="flex items-center gap-0.5">
<div className="flex cursor-not-allowed items-center justify-center p-0.5">
<span aria-hidden className="i-ri-file-copy-line size-4 text-text-disabled" />
</div>
{showQrCode && (
<div className="flex cursor-not-allowed items-center justify-center p-0.5">
<span aria-hidden className="i-ri-qr-code-line size-4 text-text-disabled" />
</div>
)}
{showRegenerate && (
<div className="flex cursor-not-allowed items-center justify-center p-0.5">
<span aria-hidden className="i-ri-loop-left-line size-4 text-text-disabled" />
</div>
)}
{showOpen && (
<>
<span className="mx-1 h-3.5 w-px bg-divider-subtle" />
<div className="flex h-6 cursor-not-allowed items-center gap-1 rounded-md border-[0.5px] border-components-button-secondary-border-disabled px-1.5 system-sm-medium text-components-button-secondary-text-disabled backdrop-blur-xs">
<span aria-hidden className="i-ri-external-link-line size-3.5" />
{openLabel}
</div>
</>
)}
</div>
)
const availableActions = (
<div className="flex items-center gap-0.5">
{copyDisabled ? (
<div className="flex cursor-not-allowed items-center justify-center p-0.5">
<span aria-hidden className="i-ri-file-copy-line size-4 text-text-disabled" />
</div>
) : (
<CopyFeedback
content={value}
className="size-6"
copyLabel={copyLabel}
copiedLabel={copiedLabel}
onCopyError={onCopyError}
/>
)}
{showQrCode &&
(copyDisabled ? (
<div className="flex cursor-not-allowed items-center justify-center p-0.5">
<span aria-hidden className="i-ri-qr-code-line size-4 text-text-disabled" />
</div>
) : (
<ShareQRCode
content={value}
triggerLabel={qrCodeLabel}
scanLabel={qrCodeScanLabel}
downloadLabel={qrCodeDownloadLabel}
/>
))}
{showRegenerate && (
<IconButton
aria-label={regenerateLabel || t(($) => $['operation.regenerate'], { ns: 'common' })}
disabled={regenerateDisabled || regenerating}
onClick={onRegenerate}
>
<span
aria-hidden
className={`i-ri-loop-left-line size-4 ${regenerating ? 'animate-spin' : ''}`}
/>
</IconButton>
)}
{showOpen && (
<>
<span className="mx-1 h-3.5 w-px bg-divider-regular" />
{enabled && openUrl ? (
<a
href={openUrl}
target="_blank"
rel="noopener noreferrer"
className={cn(
buttonVariants({ variant: 'secondary', size: 'small' }),
'h-6 gap-1 px-1.5',
)}
>
<span aria-hidden className="i-ri-external-link-line size-3.5" />
{openLabel}
</a>
) : (
disabledOpenAction
)}
</>
)}
</div>
)
return (
<AccessPointEndpoint
label={label}
value={value}
unavailableLabel={unavailableLabel}
unavailable={unavailable}
dimmed={!enabled}
loading={loading}
actions={detailsAvailable ? availableActions : disabledActions}
/>
)
}