main
import { useT } from '@app/i18n'
import { IconCheck as Check, IconCopy as Copy } from '@tabler/icons-react'
import { useState } from 'react'
/** Shared empty / error / copy affordances for the repo views. */
export function PanelNote({ text }: { text: string }) {
return <p className="px-2 py-6 text-center text-[13px] text-shell-ink-muted">{text}</p>
}
export function ErrorPanel({
status,
what,
onRetry,
note,
}: {
status: number
what: string
onRetry: () => void
note?: string
}) {
const { t } = useT()
const text =
status === 404
? t('ui.notFoundOnRevision', { what })
: status === 401 || status === 403
? t('ui.forbidden', { what })
: status === 0
? t('ui.loadFailedWithNote', { what, note: note ?? t('error.status.0') })
: t('ui.loadFailedWithStatus', { what, status })
return (
<div className="flex flex-col items-center gap-3 py-10 text-center">
<p className="text-[13.5px]">{text}</p>
<button
type="button"
onClick={onRetry}
className="rounded-md border-2 border-shell-hairline bg-shell-field px-4 py-1.5 text-[13px] transition-colors hover:border-shell-ink"
>
{t('common.action.retry')}
</button>
</div>
)
}
/** Copy-to-clipboard chip used for object ids; falls back silently when the
* clipboard API is unavailable (insecure context / permission denied). */
export function CopyChip({ text, label }: { text: string; label?: string }) {
const { t } = useT()
const [copied, setCopied] = useState(false)
return (
<button
type="button"
aria-label={`${t('common.action.copy')} ${label ?? text}`}
title={text}
onClick={async () => {
try {
await navigator.clipboard.writeText(text)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
} catch {
/* clipboard unavailable */
}
}}
className="flex shrink-0 items-center gap-1 rounded bg-shell-active/70 px-1.5 py-0.5 font-mono text-[11px] text-shell-ink-muted transition-colors hover:text-shell-ink"
>
{copied ? <Check className="h-3 w-3" aria-hidden /> : <Copy className="h-3 w-3" aria-hidden />}
{copied ? t('common.action.copied') : (label ?? text)}
</button>
)
}