All files / components/screens/combat/components/indicators HealthBar.tsx

100% Statements 19/19
100% Branches 18/18
100% Functions 4/4
100% Lines 17/17

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                                                            4x 121x 11x 7x           4x             121x 119x       121x 121x 121x 121x     121x 121x 121x 121x   121x                                                                                           1210x                                                
/**
 * HealthBar Component - Segmented health display with Korean theming
 * 
 * Displays player health with:
 * - 10 segmented bars
 * - Color transitions: Green (>50%), Yellow (25-50%), Red (<25%)
 * - Pulse animation when health <20%
 * - Korean/English bilingual labels
 * - Numeric value display (e.g., "85/100")
 * - Responsive sizing for mobile/tablet/desktop
 */
 
import React, { useMemo } from "react";
import { KOREAN_COLORS, FONT_FAMILY } from "../../../../../types/constants";
import { hexToRgbaString } from "../../../../../utils/colorUtils";
 
export interface HealthBarProps {
  /** Current health value */
  readonly current: number;
  /** Maximum health capacity */
  readonly max: number;
  /** Player identifier for test ID */
  readonly playerId: string;
  /** Whether to use mobile-optimized sizing */
  readonly isMobile: boolean;
}
 
/**
 * Get health bar color based on health percentage
 */
const getHealthColor = (percentage: number): number => {
  if (percentage > 50) return KOREAN_COLORS.HEALTH_FULL; // Green
  if (percentage > 25) return KOREAN_COLORS.HEALTH_MEDIUM; // Yellow
  return KOREAN_COLORS.HEALTH_CRITICAL; // Red
};
 
/**
 * HealthBar - Segmented health display with Korean theming
 */
export const HealthBar: React.FC<HealthBarProps> = ({
  current,
  max,
  playerId,
  isMobile,
}) => {
  // Calculate health percentage and determine styling
  const healthPercent = useMemo(
    () => Math.max(0, Math.min(100, (current / max) * 100)),
    [current, max]
  );
 
  const segments = 10;
  const filledSegments = Math.ceil((healthPercent / 100) * segments);
  const healthColor = getHealthColor(healthPercent);
  const shouldPulse = healthPercent < 20;
 
  // Responsive sizing
  const barWidth = isMobile ? 180 : 250;
  const barHeight = isMobile ? 16 : 20;
  const fontSize = isMobile ? 11 : 13;
  const padding = isMobile ? "8px 12px" : "12px 16px";
 
  return (
    <div
      data-testid={`health-bar-${playerId}`}
      role="progressbar"
      aria-label="체력 | Health"
      aria-valuenow={Math.ceil(current)}
      aria-valuemin={0}
      aria-valuemax={max}
      aria-valuetext={`${Math.ceil(current)} out of ${max}`}
      style={{
        width: `${barWidth}px`,
        padding,
        backgroundColor: hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_DARK, 1),
        borderRadius: "8px",
        border: `2px solid ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1)}`,
        boxShadow: `0 0 10px ${hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 0.2)}`,
      }}
    >
      {/* Label and numeric display */}
      <div
        style={{
          fontSize: `${fontSize}px`,
          color: hexToRgbaString(KOREAN_COLORS.PRIMARY_CYAN, 1),
          fontFamily: FONT_FAMILY.KOREAN,
          marginBottom: "4px",
          display: "flex",
          justifyContent: "space-between",
          fontWeight: "bold",
        }}
      >
        <span>체력 | Health</span>
        <span data-testid={`health-value-${playerId}`}>
          {Math.ceil(current)}/{max}
        </span>
      </div>
 
      {/* Segmented health bar */}
      <div
        style={{
          display: "flex",
          gap: "3px",
          height: `${barHeight}px`,
          animation: shouldPulse ? "healthPulse 0.8s infinite" : "none",
        }}
      >
        {Array.from({ length: segments }).map((_, index) => (
          <div
            key={index}
            data-testid={`health-segment-${playerId}-${index}`}
            style={{
              flex: 1,
              backgroundColor:
                index < filledSegments
                  ? hexToRgbaString(healthColor, 1)
                  : hexToRgbaString(KOREAN_COLORS.UI_BACKGROUND_MEDIUM, 1),
              borderRadius: "2px",
              transition: "background-color 0.2s ease-in-out",
              boxShadow:
                index < filledSegments
                  ? `0 0 8px ${hexToRgbaString(healthColor, 0.4)}`
                  : "none",
            }}
          />
        ))}
      </div>
    </div>
  );
};
 
export default HealthBar;