| 3084 | } |
| 3085 | |
| 3086 | function CopyableCodeBlock({ label, value }: { label: string; value: string }) { |
| 3087 | const { t } = useI18n(); |
| 3088 | const [copied, setCopied] = useState(false); |
| 3089 | |
| 3090 | async function copy() { |
| 3091 | try { |
| 3092 | await navigator.clipboard.writeText(value); |
| 3093 | setCopied(true); |
| 3094 | window.setTimeout(() => setCopied(false), 1200); |
| 3095 | } catch { |
| 3096 | setCopied(false); |
| 3097 | } |
| 3098 | } |
| 3099 | |
| 3100 | return ( |
| 3101 | <div className="min-w-0"> |
| 3102 | <div className="mb-1 flex items-center justify-between gap-2"> |
| 3103 | <div className="text-xs font-medium text-muted-foreground">{label}</div> |
| 3104 | <Button type="button" variant="outline" size="sm" onClick={() => void copy()}> |
| 3105 | {copied ? t("已复制", "Copied") : t("复制", "Copy")} |
| 3106 | </Button> |
| 3107 | </div> |
| 3108 | <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-md bg-muted p-3 text-xs leading-5"> |
| 3109 | {value} |
| 3110 | </pre> |
| 3111 | </div> |
| 3112 | ); |
| 3113 | } |
| 3114 | |
| 3115 | function DetailDrawer(props: { |
| 3116 | drawer: Exclude<DetailDrawer, null>; |