feat(web): Phase 11 UX improvements for approval card

- Change dashed border buttons to solid filled style for better clickability
- Add signature progress bar with visual indicator
- Add signed users list showing who has already signed
- Convert Blast Radius section to collapsible panel (auto-open for CRITICAL)
- Convert Dry-Run Checks to collapsible panel with pass/fail summary badge
- Add slide-in animations for expanded content

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-25 11:45:04 +08:00
parent bd6d7f5d0a
commit e87ac11f4f

View File

@@ -162,21 +162,23 @@ function LongPressButton({
}
}, [])
// Lab-White 虛線邊框按鈕風格 (Dashed Border Style)
// UX 優化: 實心邊框 + 填充色 (更明確的可點擊狀態)
const baseStyles = variant === 'danger'
? [
'bg-white',
'border-2 border-dashed border-status-critical',
'bg-status-critical/10',
'border-2 border-status-critical/50',
'text-status-critical',
'hover:bg-status-critical/5',
'hover:border-solid',
'hover:bg-status-critical/20',
'hover:border-status-critical',
'shadow-sm',
]
: [
'bg-white',
'border-2 border-dashed border-claw-blue',
'bg-claw-blue/10',
'border-2 border-claw-blue/50',
'text-claw-blue',
'hover:bg-claw-blue/5',
'hover:border-solid',
'hover:bg-claw-blue/20',
'hover:border-claw-blue',
'shadow-sm',
]
const progressBgColor = variant === 'danger' ? 'bg-status-critical' : 'bg-claw-blue'
@@ -423,7 +425,7 @@ export function ApprovalCard({
)}
</div>
{/* Multi-Sig Counter - Enhanced */}
{/* Multi-Sig Counter + Progress - Enhanced */}
<div className="text-right">
<div className="text-[10px] text-nothing-gray-500 font-mono uppercase tracking-wider mb-1">
{t('signatures')}
@@ -439,9 +441,37 @@ export function ApprovalCard({
<span className="text-nothing-gray-400">/</span>
{request.requiredSignatures}
</div>
{/* 簽核進度條 */}
<div className="mt-2 w-20 h-1.5 bg-nothing-gray-200 rounded-full overflow-hidden">
<div
className={cn(
'h-full rounded-full transition-all duration-500',
needsMoreSignatures ? 'bg-status-warning' : 'bg-status-healthy'
)}
style={{ width: `${(request.currentSignatures / request.requiredSignatures) * 100}%` }}
/>
</div>
</div>
</GlassCardHeader>
{/* 已簽核人員列表 (有簽名時顯示) */}
{signatures && signatures.length > 0 && (
<div className="px-6 -mt-2 mb-4">
<div className="flex items-center gap-2 text-[10px] text-nothing-gray-500 font-mono">
<Shield className="w-3 h-3" />
<span>:</span>
{signatures.map((sig, idx) => (
<span key={sig.id} className="inline-flex items-center gap-1">
<span className="px-1.5 py-0.5 bg-status-healthy/10 text-status-healthy rounded text-[10px] font-semibold">
{sig.signerName}
</span>
{idx < signatures.length - 1 && <span className="text-nothing-gray-300">·</span>}
</span>
))}
</div>
</div>
)}
{/* Title & Description - UX 優化: 分離標題與命令 */}
<div className="mb-5">
{/* 主標題: 只顯示 | 前的動作描述 */}
@@ -468,14 +498,21 @@ export function ApprovalCard({
</div>
<GlassCardContent>
{/* Blast Radius Grid - Enhanced */}
<div className="mb-5">
<h4 className="text-[10px] font-mono text-nothing-gray-500 uppercase tracking-widest mb-3 flex items-center gap-2">
<Zap className="w-3 h-3" />
{tBlast('title')}
</h4>
{/* Blast Radius - 可折疊面板 */}
<details className="mb-4 group" open={isCritical || isDestructive}>
<summary className="flex items-center justify-between cursor-pointer select-none py-2 px-3 -mx-3 rounded-lg hover:bg-nothing-gray-50 transition-colors">
<h4 className="text-[10px] font-mono text-nothing-gray-500 uppercase tracking-widest flex items-center gap-2">
<Zap className="w-3 h-3" />
{tBlast('title')}
{/* 摘要預覽 */}
<span className="text-nothing-gray-400 normal-case">
({request.blastRadius?.affectedPods ?? 0} pods)
</span>
</h4>
<ChevronDown className="w-4 h-4 text-nothing-gray-400 transition-transform group-open:rotate-180" />
</summary>
{request.blastRadius && (
<div className="grid grid-cols-2 gap-3">
<div className="grid grid-cols-2 gap-3 mt-3 animate-in slide-in-from-top-2 duration-200">
<MetricBox
label={tBlast('affectedPods')}
value={String(request.blastRadius.affectedPods ?? 0)}
@@ -505,7 +542,7 @@ export function ApprovalCard({
</div>
</div>
)}
</div>
</details>
{/* Data Impact */}
{isDestructive && (
@@ -519,13 +556,25 @@ export function ApprovalCard({
</div>
)}
{/* Dry-Run Checks - Enhanced */}
<div className="mb-5">
<h4 className="text-[10px] font-mono text-nothing-gray-500 uppercase tracking-widest mb-3 flex items-center gap-2">
<Clock className="w-3 h-3" />
{tDryRun('validation')}
</h4>
<div className="space-y-2">
{/* Dry-Run Checks - 可折疊面板 (預設收合) */}
<details className="mb-4 group">
<summary className="flex items-center justify-between cursor-pointer select-none py-2 px-3 -mx-3 rounded-lg hover:bg-nothing-gray-50 transition-colors">
<h4 className="text-[10px] font-mono text-nothing-gray-500 uppercase tracking-widest flex items-center gap-2">
<Clock className="w-3 h-3" />
{tDryRun('validation')}
{/* 通過/失敗摘要 */}
<span className={cn(
'text-[10px] font-mono px-1.5 py-0.5 rounded',
allChecksPassed
? 'bg-status-healthy/10 text-status-healthy'
: 'bg-status-critical/10 text-status-critical'
)}>
{(request.dryRunChecks ?? []).filter(c => c.passed).length}/{(request.dryRunChecks ?? []).length}
</span>
</h4>
<ChevronDown className="w-4 h-4 text-nothing-gray-400 transition-transform group-open:rotate-180" />
</summary>
<div className="space-y-2 mt-3 animate-in slide-in-from-top-2 duration-200">
{(request.dryRunChecks ?? []).map((check) => (
<div
key={check.name}
@@ -560,7 +609,7 @@ export function ApprovalCard({
</div>
))}
</div>
</div>
</details>
</GlassCardContent>
</div>
{/* 結束可滾動區域 */}