All files / src/components/ProgressBar ProgressBar.vue

96% Statements 24/25
92.59% Branches 25/27
100% Functions 9/9
100% Lines 16/16

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              36x                                         36x 112x 36x 36x 36x   36x   78x 36x 78x     36x 36x 38x 38x         78x                               2x                                                                                                  
<script setup lang="ts">
import { computed } from "vue";
import { resolveTrack } from "../Spinner/track";
import { clampProgress, progressA11y } from "../../utils/progress";
import { usePrefersReducedMotion } from "../../utils/usePrefersReducedMotion";
import { useLoadingConfig } from "../../config";
 
const props = withDefaults(
  defineProps<{
    /** 0–100. Without it the bar is indeterminate (a sliding segment). */
    value?: number;
    color?: string;
    /** The background rail. `true` derives it from `color`; a string sets it. */
    track?: boolean | string;
    /** Bar height in px. */
    thickness?: number;
    speed?: number;
    /** Announced to screen readers (progressbar or live status). */
    label?: string;
  }>(),
  {
    // explicit undefined opts out of Vue's Boolean casting, so an unset
    // `track` can fall through to the global config
    track: undefined,
  }
);
 
// explicit prop > global config > built-in default
const config = useLoadingConfig();
const conf = () => config.progressBar ?? {};
const color = computed(() => props.color ?? conf().color ?? "currentColor");
const thickness = computed(() => props.thickness ?? conf().thickness ?? 4);
const speed = computed(() => props.speed ?? conf().speed ?? 1);
// unlike the arc, a bar with no visible rail is hard to read as progress
const track = computed(() => props.track ?? conf().track ?? true);
 
const progress = computed(() => clampProgress(props.value));
const trackInfo = computed(() => resolveTrack(track.value));
const a11y = computed(() => progressA11y(props.label, progress.value));
 
// Same reduced-motion policy as Spinner: slow down rather than freeze.
const prefersReducedMotion = usePrefersReducedMotion();
const duration = computed(() => {
  const s = speed.value > 0 ? speed.value : 1;
  return `${(1.5 / s) * (prefersReducedMotion.value ? 2 : 1)}s`;
});
</script>
 
<template>
  <div
    class="vslk-progress"
    :class="{ 'vslk-progress--indeterminate': progress === null }"
    :data-vslk-track="trackInfo?.attr"
    :style="{
      height: `${thickness}px`,
      color: color,
      '--vslk-progress-track': trackInfo?.color ?? 'transparent',
      '--vslk-progress-duration': duration,
    }"
    v-bind="a11y"
  >
    <div
      class="vslk-progress__bar"
      :style="progress === null ? undefined : { width: `${progress}%` }"
    />
    <span v-if="Iprops.label && progress === null" class="vslk-sr-only">{{ props.label }}</span>
  </div>
</template>
 
<style scoped>
.vslk-progress {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 999px;
  background: var(--vslk-progress-track);
}
 
.vslk-progress__bar {
  height: 100%;
  border-radius: inherit;
  background: currentColor;
  transition: width 0.3s ease;
}
 
/* Indeterminate: a 40%-wide segment slides from fully off the left edge
   (-100% of its own width) to fully off the right (250% of 40% = 100%). */
.vslk-progress--indeterminate .vslk-progress__bar {
  width: 40%;
  transition: none;
  animation: vslk-progress-slide var(--vslk-progress-duration) ease-in-out infinite;
}
 
@keyframes vslk-progress-slide {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(250%);
  }
}
 
.vslk-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
</style>