CProgressBar
parent
f10a4c4de8
commit
5835a078bb
@ -0,0 +1,156 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import type { CProgressBarSize, CProgressBarVariant } from './types'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
value?: number
|
||||||
|
max?: number
|
||||||
|
indeterminate?: boolean
|
||||||
|
showValue?: boolean
|
||||||
|
size?: CProgressBarSize
|
||||||
|
variant?: CProgressBarVariant
|
||||||
|
ariaLabel?: string
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
max: 100,
|
||||||
|
indeterminate: false,
|
||||||
|
showValue: false,
|
||||||
|
size: 'medium',
|
||||||
|
variant: 'default',
|
||||||
|
ariaLabel: undefined,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
defineSlots<{
|
||||||
|
value(props: { value: number; max: number; percentage: number }): unknown
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const normalizedMax = computed(() =>
|
||||||
|
Number.isFinite(props.max) && props.max > 0 ? props.max : 100,
|
||||||
|
)
|
||||||
|
const normalizedValue = computed(() => {
|
||||||
|
if (!Number.isFinite(props.value)) return 0
|
||||||
|
return Math.min(Math.max(props.value, 0), normalizedMax.value)
|
||||||
|
})
|
||||||
|
const percentage = computed(() => (normalizedValue.value / normalizedMax.value) * 100)
|
||||||
|
const percentageLabel = computed(() => `${Math.round(percentage.value)}%`)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="c-progress-bar"
|
||||||
|
:class="[`is-${size}`, `is-${variant}`, { 'is-indeterminate': indeterminate }]"
|
||||||
|
role="progressbar"
|
||||||
|
:aria-label="ariaLabel"
|
||||||
|
:aria-valuemin="indeterminate ? undefined : 0"
|
||||||
|
:aria-valuemax="indeterminate ? undefined : normalizedMax"
|
||||||
|
:aria-valuenow="indeterminate ? undefined : normalizedValue"
|
||||||
|
>
|
||||||
|
<span class="track" aria-hidden="true">
|
||||||
|
<span
|
||||||
|
class="fill"
|
||||||
|
:style="indeterminate ? undefined : { width: `${percentage}%` }"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="showValue && !indeterminate" class="value">
|
||||||
|
<slot
|
||||||
|
name="value"
|
||||||
|
:value="normalizedValue"
|
||||||
|
:max="normalizedMax"
|
||||||
|
:percentage="percentage"
|
||||||
|
>
|
||||||
|
{{ percentageLabel }}
|
||||||
|
</slot>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.c-progress-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 7px;
|
||||||
|
|
||||||
|
.track {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--c-progress-track-color, #e5e8ec);
|
||||||
|
border: 1px solid var(--c-border-color, #cfd4da);
|
||||||
|
border-radius: var(--c-border-radius, 3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill {
|
||||||
|
position: absolute;
|
||||||
|
inset-block: 0;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
background: var(--c-primary-color, #286aa6);
|
||||||
|
transition: width 160ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
flex: none;
|
||||||
|
min-width: 3.5em;
|
||||||
|
color: var(--c-muted-text-color, #626a75);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: end;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-small .track {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-large .track {
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-success .fill {
|
||||||
|
background: var(--c-success-color, #2f7d32);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-warning .fill {
|
||||||
|
background: var(--c-warning-color, #f0ad32);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-danger .fill {
|
||||||
|
background: var(--c-danger-color, #b42318);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-indeterminate .fill {
|
||||||
|
width: 35%;
|
||||||
|
animation: c-progress-bar-indeterminate 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes c-progress-bar-indeterminate {
|
||||||
|
from {
|
||||||
|
transform: translateX(-110%);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(300%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.c-progress-bar {
|
||||||
|
.fill {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-indeterminate .fill {
|
||||||
|
animation-duration: 2.4s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export type CProgressBarSize = 'small' | 'medium' | 'large'
|
||||||
|
export type CProgressBarVariant = 'default' | 'success' | 'warning' | 'danger'
|
||||||
Loading…
Reference in New Issue