All files FzProgressBar.vue

98.68% Statements 150/152
94.44% Branches 17/18
100% Functions 1/1
98.68% Lines 150/152

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 147 148 149 150 151 152 1531x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 52x 52x 52x 45x 43x 52x 12x 12x 40x 40x 52x 2x 2x 38x 38x 38x 1x 1x 1x 52x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 52x 52x 52x     52x 52x 1x 1x 1x 1x 1x 1x 1x 1x 1x 156x 14x 14x 142x 156x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
<script lang="ts" setup>
/**
 * FzProgressBar Component
 *
 * Visual progress bar indicator displaying completion percentage.
 * Supports custom min/max ranges and calculates percentage position
 * automatically with smooth transitions.
 *
 * Note: We use div elements instead of HTML progress element for better
 * CSS cross-browser compatibility. The progress element requires browser-specific
 * pseudo-elements (::-webkit-progress-bar, ::-moz-progress-bar) which can be
 * inconsistent across browsers and harder to style with Tailwind.
 *
 * @component
 * @example
 * <FzProgressBar :current="50" />
 * <FzProgressBar :current="30" :min="-15" :max="50" />
 */
import { computed } from "vue";
import type { FzProgressBarProps } from "./types";
 
/**
 * Disables automatic attribute inheritance to prevent props like 'color' from being
 * passed as HTML attributes to the root div element. The 'color' prop is only used
 * internally for computing the CSS class.
 */
defineOptions({
  inheritAttrs: false,
});
 
const props = withDefaults(defineProps<FzProgressBarProps>(), {
  max: 100,
  min: 0,
  name: "progress-bar",
  size: "md",
  color: "purple",
});
 
/**
 * Computes progress percentage based on current value within min-max range
 *
 * Calculates percentage position of current value in the range [min, max],
 * clamping the result between 0 and 100 for CSS width percentage.
 * Handles edge cases: NaN, Infinity, and zero range.
 */
const percentageProgress = computed(() => {
  // Validate inputs to prevent NaN/Infinity
  if (
    !Number.isFinite(props.current) ||
    !Number.isFinite(props.max) ||
    !Number.isFinite(props.min)
  ) {
    return 0;
  }
 
  const range = props.max - props.min;
  if (range === 0) {
    return 0;
  }
 
  const progress = ((props.current - props.min) / range) * 100;
  return Math.max(0, Math.min(100, Math.round(progress)));
});
 
const progressBarSize = computed(() => {
  return props.size === "sm" ? "h-[8px]" : "h-[20px]";
});
 
/**
 * Color to Tailwind CSS class mapping
 *
 * Maps color prop values to complete Tailwind CSS background color classes.
 * Using explicit mapping ensures Tailwind can detect and include these classes
 * during compilation (dynamic template literals are not detected by Tailwind).
 */
const colorClassMap = {
  purple: "bg-purple-500",
  blue: "bg-blue-500",
  orange: "bg-orange-500",
  pink: "bg-pink-500",
  yellow: "bg-yellow-500",
  grey: "bg-grey-500",
} as const;
 
/**
 * Computes background color class based on color prop
 *
 * Returns the corresponding Tailwind CSS background color class from the mapping.
 * Falls back to 'purple' if color is undefined or not in the mapping.
 */
const progressBarColor = computed(() => {
  const color = props.color;
 
  if (!color || !(color in colorClassMap)) {
    return colorClassMap.purple;
  }
 
  return colorClassMap[color as keyof typeof colorClassMap];
});
 
/**
 * Sanitizes value for ARIA attributes
 *
 * Converts NaN and Infinity to 0 to ensure valid ARIA attribute values.
 * ARIA attributes must be valid numbers per WCAG 2.1 AA standards.
 */
const sanitizeAriaValue = (value: number): number => {
  if (!Number.isFinite(value)) {
    return 0;
  }
  return value;
};
 
/**
 * Sanitized current value for aria-valuenow
 *
 * Returns 0 if current is NaN or Infinity to ensure valid ARIA attribute.
 */
const ariaValuenow = computed(() => sanitizeAriaValue(props.current));
 
/**
 * Sanitized min value for aria-valuemin
 *
 * Returns 0 if min is NaN or Infinity to ensure valid ARIA attribute.
 */
const ariaValuemin = computed(() => sanitizeAriaValue(props.min));
 
/**
 * Sanitized max value for aria-valuemax
 *
 * Returns 0 if max is NaN or Infinity to ensure valid ARIA attribute.
 */
const ariaValuemax = computed(() => sanitizeAriaValue(props.max));
</script>
 
<template>
  <div
    class="fz-progress-bar w-full rounded-[4px] bg-grey-100"
    :class="progressBarSize"
    role="progressbar"
    :aria-valuenow="ariaValuenow"
    :aria-valuemin="ariaValuemin"
    :aria-valuemax="ariaValuemax"
    :aria-label="props.name"
  >
    <div
      class="fz-progress-bar__progress-indicator h-full rounded-[4px] transition-all duration-300"
      :class="progressBarColor"
      :style="{ width: `${percentageProgress}%` }"
    ></div>
  </div>
</template>