<template> <div :aria-valuemax="max" :aria-valuemin="min" :aria-valuenow="progress !== 'indeterminate' ? progress : undefined" :aria-valuetext=" percentage ? `${percentage.toLocaleString(locale, { maximumFractionDigits: 2 })}%` : undefined " role="progressbar" > <svg class="tw-aspect-square tw-max-w-full tw-max-h-full" height="120" viewBox="0 0 120 120" width="120" xmlns="http://www.w3.org/2000/svg" > <g> <g class="circle" :class="{ indeterminate: progress === 'indeterminate' }"> <circle class="track" cx="60" cy="60" fill="none" r="54" stroke-width="12" /> <circle class="value" :stroke-dashoffset="dashOffset" cx="60" cy="60" fill="none" pathLength="100" r="54" stroke="currentColor" stroke-dasharray="100" stroke-width="12" /> </g> <text v-if="percentage" x="50%" y="52.5%" dominant-baseline="middle" text-anchor="middle" font-size="200%" > {{ `${Math.round(percentage)}%` }} </text> </g> </svg> </div> </template> <script lang="ts" setup> import { computed } from 'vue' import { useI18n } from '@/i18n' const progress = defineModel<number | 'indeterminate'>('progress', { required: true }) const props = withDefaults( defineProps<{ min?: number max?: number }>(), { min: 0, max: 100, }, ) const { locale } = useI18n() const percentage = computed(() => { if (progress.value === 'indeterminate') return null if (progress.value < props.min) { return 0 } else if (progress.value > props.max) { return 100 } return ((progress.value - props.min) / (props.max - props.min)) * 100 }) const dashOffset = computed(() => 100 - (percentage.value ?? 0)) </script> <style lang="postcss" scoped> .track { stroke: theme('colors.gray.100'); stroke: color-mix(in oklab, currentColor 15%, transparent 10%); } .value { transition: stroke-dashoffset 0.2s; } .circle { @apply tw-origin-center; &:not(.indeterminate) { @apply tw-translate-x-0 tw-translate-y-0 tw-skew-x-0 tw-skew-y-0 tw-scale-100 -tw-rotate-90; } &.indeterminate { animation: tw-spin 1s linear infinite; & > .value { animation: stretch 3s linear infinite; } } } @keyframes stretch { 0%, 50%, 100% { stroke-dashoffset: 90; } 25%, 75% { stroke-dashoffset: 65; } } </style>