CUB-48: Add Agent Status Badge component with pulse animations
- Colored pill badge for Active, Idle, Thinking, Error, Offline statuses - Color mapping uses CSS custom properties (--status-active, etc.) - Pulse animations: Active 2s, Thinking 3s, Error 0.8s, Idle/Offline static - Respects prefers-reduced-motion for accessibility - Standalone component with OnPush change detection - Barrel export via index.ts
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
<!-- Agent Status Badge: colored pill with pulse animation -->
|
||||||
|
<span
|
||||||
|
class="status-badge"
|
||||||
|
[class]="statusClass"
|
||||||
|
[attr.aria-label]="statusLabels[status] + ' status'"
|
||||||
|
[attr.role]="'status'"
|
||||||
|
>
|
||||||
|
<span class="status-badge__dot"></span>
|
||||||
|
<span *ngIf="showLabel" class="status-badge__label">
|
||||||
|
{{ statusLabels[status] }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// Agent Status Badge — Pill Style with Pulse Animation
|
||||||
|
// Per CUB-48: Color mapping + animation durations:
|
||||||
|
// Active → --color-primary (#38BDF8) 2s pulse
|
||||||
|
// Idle → --color-secondary (#2DD4BF) no animation
|
||||||
|
// Thinking → --color-accent (#A78BFA) 3s pulse
|
||||||
|
// Error → --color-danger (#F87171) 0.8s pulse
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Badge Container
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
.status-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
// --- Status-specific backgrounds & text ---
|
||||||
|
&--active {
|
||||||
|
background-color: var(--status-active-bg);
|
||||||
|
color: var(--status-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--idle {
|
||||||
|
background-color: var(--status-idle-bg);
|
||||||
|
color: var(--status-idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--thinking {
|
||||||
|
background-color: var(--status-thinking-bg);
|
||||||
|
color: var(--status-thinking);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error {
|
||||||
|
background-color: var(--status-error-bg);
|
||||||
|
color: var(--status-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--offline {
|
||||||
|
background-color: rgba(100, 116, 139, 0.12);
|
||||||
|
color: var(--status-offline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Status Dot (inner indicator circle)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
.status-badge__dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.status-badge--active & {
|
||||||
|
background-color: var(--status-active);
|
||||||
|
animation: badge-pulse-active 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--idle & {
|
||||||
|
background-color: var(--status-idle);
|
||||||
|
// Idle: no animation — steady dot
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--thinking & {
|
||||||
|
background-color: var(--status-thinking);
|
||||||
|
animation: badge-pulse-thinking 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--error & {
|
||||||
|
background-color: var(--status-error);
|
||||||
|
animation: badge-pulse-error 0.8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge--offline & {
|
||||||
|
background-color: var(--status-offline);
|
||||||
|
// Offline: no animation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Label Text
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
.status-badge__label {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Badge Pulse Keyframes
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// These are scoped to the badge component rather than reusing the global
|
||||||
|
// .status-dot animations, because the badge pulse is subtler (scale + opacity
|
||||||
|
// blend for a pill context vs. standalone dot context).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@keyframes badge-pulse-active {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes badge-pulse-thinking {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes badge-pulse-error {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Accessibility: Reduced Motion
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.status-badge__dot {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// Agent Status Badge Component
|
||||||
|
// Per CUB-48: Colored pill badge with pulse animation for agent statuses.
|
||||||
|
// Displays Active, Idle, Thinking, or Error with correct color mapping.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { AgentStatus } from '../../models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reusable status badge that renders a colored pill with label text
|
||||||
|
* and a subtle pulse animation for Active, Thinking, and Error states.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ```html
|
||||||
|
* <app-agent-status-badge status="active" />
|
||||||
|
* <app-agent-status-badge status="error" />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'app-agent-status-badge',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './agent-status-badge.component.html',
|
||||||
|
styleUrl: './agent-status-badge.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class AgentStatusBadgeComponent {
|
||||||
|
/** Agent status to display. Maps to a color and animation. */
|
||||||
|
@Input({ required: true })
|
||||||
|
status!: AgentStatus;
|
||||||
|
|
||||||
|
/** Whether to show the status label text alongside the dot. Defaults to true. */
|
||||||
|
@Input()
|
||||||
|
showLabel = true;
|
||||||
|
|
||||||
|
/** Mapping from status to human-readable label. */
|
||||||
|
readonly statusLabels: Record<AgentStatus, string> = {
|
||||||
|
active: 'Active',
|
||||||
|
idle: 'Idle',
|
||||||
|
thinking: 'Thinking',
|
||||||
|
error: 'Error',
|
||||||
|
offline: 'Offline',
|
||||||
|
};
|
||||||
|
|
||||||
|
/** CSS class string for the badge container based on current status. */
|
||||||
|
get statusClass(): string {
|
||||||
|
return `status-badge--${this.status}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
frontend/src/app/components/agent-status-badge/index.ts
Normal file
6
frontend/src/app/components/agent-status-badge/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// Agent Status Badge — Barrel Export
|
||||||
|
// CUB-48
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export { AgentStatusBadgeComponent } from './agent-status-badge.component';
|
||||||
Reference in New Issue
Block a user