All files / src/components/common MetricCard.tsx

100% Statements 4/4
100% Branches 17/17
100% Functions 1/1
100% Lines 4/4

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146                                                                                            6x                       180x                                                                             180x   180x                                                                                            
import React, { ReactNode } from 'react';
import { TYPOGRAPHY, BORDER_RADIUS, SHADOWS } from '../../constants/designTokens';
 
interface MetricCardProps {
  /** Metric label */
  label: string;
  /** Metric value */
  value: string | number;
  /** Optional unit (e.g., '%', '$') */
  unit?: string;
  /** Optional icon */
  icon?: ReactNode;
  /** Optional description/subtitle */
  description?: string;
  /** Optional color variant */
  variant?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info';
  /** Test ID */
  testId?: string;
  /** Optional CSS class */
  className?: string;
  /** Optional aria-label for accessibility */
  ariaLabel?: string;
}
 
/**
 * Reusable card component for displaying key metrics
 *
 * ## Business Perspective
 *
 * This component presents key security metrics in a consistent,
 * easy-to-scan format. Standardized metric cards help stakeholders
 * quickly understand critical security indicators. 📊
 *
 * @example
 * ```tsx
 * <MetricCard
 *   label="Uptime Target"
 *   value="99.9"
 *   unit="%"
 *   icon="⏱️"
 *   description="Expected system availability"
 *   variant="success"
 *   testId="uptime-metric"
 * />
 * ```
 */
export const MetricCard: React.FC<MetricCardProps> = ({
  label,
  value,
  unit,
  icon,
  description,
  variant = 'default',
  testId = 'metric-card',
  className = '',
  ariaLabel,
}) => {
  // Variant color classes for borders and backgrounds
  const variantClasses = {
    default: {
      container: 'border-gray-300 dark:border-gray-700 bg-gray-50 dark:bg-gray-800',
      value: 'text-gray-800 dark:text-gray-200',
      label: 'text-gray-700 dark:text-gray-300',
      description: 'text-gray-600 dark:text-gray-400',
    },
    primary: {
      container: 'border-primary-light dark:border-primary-dark bg-primary-light/10 dark:bg-primary-dark/20',
      value: 'text-primary-dark dark:text-primary-light',
      label: 'text-primary-dark dark:text-primary-light',
      description: 'text-primary-dark/70 dark:text-primary-light/70',
    },
    success: {
      container: 'border-green-300 dark:border-green-700 bg-green-50 dark:bg-green-900/20',
      value: 'text-green-700 dark:text-green-300',
      label: 'text-green-700 dark:text-green-300',
      description: 'text-green-600 dark:text-green-400',
    },
    warning: {
      container: 'border-yellow-300 dark:border-yellow-700 bg-yellow-50 dark:bg-yellow-900/20',
      value: 'text-yellow-700 dark:text-yellow-300',
      label: 'text-yellow-700 dark:text-yellow-300',
      description: 'text-yellow-600 dark:text-yellow-400',
    },
    error: {
      container: 'border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-900/20',
      value: 'text-red-700 dark:text-red-300',
      label: 'text-red-700 dark:text-red-300',
      description: 'text-red-600 dark:text-red-400',
    },
    info: {
      container: 'border-blue-300 dark:border-blue-700 bg-blue-50 dark:bg-blue-900/20',
      value: 'text-blue-700 dark:text-blue-300',
      label: 'text-blue-700 dark:text-blue-300',
      description: 'text-blue-600 dark:text-blue-400',
    },
  };
 
  const colors = variantClasses[variant];
 
  return (
    <div
      className={`p-4 border ${colors.container} ${className}`}
      style={{
        borderRadius: BORDER_RADIUS.md,
        boxShadow: SHADOWS.sm,
      }}
      data-testid={testId}
      aria-label={ariaLabel || `${label}: ${value}${unit || ''}`}
    >
      <div className={`flex items-center ${icon ? 'justify-between' : 'justify-end'} mb-2`}>
        {icon && (
          <span className="text-xl" aria-hidden="true">
            {icon}
          </span>
        )}
        <span
          className={`text-2xl font-bold ${colors.value}`}
          style={{ fontSize: TYPOGRAPHY.title }}
          data-testid={`${testId}-value`}
        >
          {value}
          {unit && <span className={unit.startsWith(' ') ? '' : 'ml-1'}>{unit}</span>}
        </span>
      </div>
      <p
        className={`text-sm font-medium mb-1 ${colors.label}`}
        style={{ fontSize: TYPOGRAPHY.body }}
        data-testid={`${testId}-label`}
      >
        {label}
      </p>
      {description && (
        <p
          className={`text-xs ${colors.description}`}
          style={{ fontSize: TYPOGRAPHY.caption }}
          data-testid={`${testId}-description`}
        >
          {description}
        </p>
      )}
    </div>
  );
};
 
export default MetricCard;