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:
@@ -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>
|
||||
{/* 結束可滾動區域 */}
|
||||
|
||||
Reference in New Issue
Block a user