Files
awoooi/apps/web/src/components/status-orb.tsx
OG T 4d46e6b9a7
Some checks failed
E2E Health Check / e2e-health (push) Successful in 17s
CD Pipeline / build-and-deploy (push) Has been cancelled
style(web): 全站 font-mono → font-body (DM Mono 設計系統套用)
45 個 component + 6 個 page 統一從舊 font-mono 遷移到
font-body (DM Mono),確保設計系統一致性。

font-body = DM Mono (等寬),視覺效果相同但走新設計 token。
保留: font-heading (Syne)、font-dot-matrix (VT323/DSEG7)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 09:37:03 +08:00

93 lines
2.1 KiB
TypeScript

'use client'
/**
* StatusOrb - Agent 狀態指示燈
* ============================
* i18n: 100% 使用 useTranslations
* 修復: 2026-03-29 Wave 3 i18n 清零
*/
import { useTranslations } from 'next-intl'
import { cn } from '@/lib/utils'
type AgentStatus = 'idle' | 'thinking' | 'executing' | 'waiting_approval'
interface StatusOrbProps {
status: AgentStatus
size?: 'sm' | 'md' | 'lg'
className?: string
}
// i18n key 對應表
const statusI18nKey: Record<AgentStatus, string> = {
idle: 'idle',
thinking: 'thinking',
executing: 'executing',
waiting_approval: 'waitingApproval',
}
const statusConfig: Record<AgentStatus, { color: string; animate: boolean }> = {
idle: {
color: 'bg-nothing-gray-600',
animate: false,
},
thinking: {
color: 'bg-status-warning',
animate: true,
},
executing: {
color: 'bg-status-healthy',
animate: true,
},
waiting_approval: {
color: 'bg-status-critical',
animate: true,
},
}
const sizeConfig = {
sm: 'w-8 h-8',
md: 'w-16 h-16',
lg: 'w-24 h-24',
}
export function StatusOrb({ status, size = 'md', className }: StatusOrbProps) {
const t = useTranslations('status')
const config = statusConfig[status]
const sizeClass = sizeConfig[size]
const label = t(statusI18nKey[status])
return (
<div className={cn('relative', className)}>
{/* Outer glow */}
<div
className={cn(
'absolute inset-0 rounded-full blur-xl opacity-30',
config.color,
config.animate && 'animate-pulse-slow'
)}
/>
{/* Main orb */}
<div
className={cn(
'relative rounded-full border border-nothing-gray-700',
sizeClass,
config.color,
config.animate && 'animate-breathe'
)}
>
{/* Inner highlight */}
<div className="absolute inset-2 rounded-full bg-gradient-to-br from-white/20 to-transparent" />
</div>
{/* Label */}
<div className="absolute -bottom-6 left-1/2 -translate-x-1/2 whitespace-nowrap">
<span className="font-body text-xs text-nothing-gray-500">
{label}
</span>
</div>
</div>
)
}