Un anneau qui se remplit à mesure que quelque chose avance, ou tourne en continu quand il n'y a aucun chiffre à rapporter. Sa géométrie est en CSS pur : changer sa taille ne recalcule rien en JavaScript.
value est l'avancement, face à un max qui dit ce qui compte comme terminé. Tout ce qui sort de la plage y est ramené.
vue
40 %58 %
value = 40, max = 100
<script setup lang="ts">
import { ref } from 'vue'
import { VButton, VProgressCircular, VTypography } from 'vectis-ui'
const value = ref(40)
const step = (delta: number) => (value.value += delta)
</script>
<template>
<div class="column">
<div class="row">
<VProgressCircular :value="value" :size="96" show-value label="Upload" />
<!-- `max` says what counts as finished. The other end is always zero. -->
<VProgressCircular :value="7" :max="12" :size="96" show-value label="Files" />
</div>
<div class="row">
<VButton variant="outline" tone="neutral" size="sm" @click="step(-25)">-25</VButton>
<VButton variant="outline" tone="neutral" size="sm" @click="step(25)">+25</VButton>
<!-- Anything outside the range is brought back into it, so a value of 130 or -10
needs no clamping of your own. -->
<VTypography variant="body-sm" tone="muted">value = {{ value }}, max = 100</VTypography>
</div>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-5);
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--vectis-space-6);
}
</style>
Indéterminé
indeterminate sert à une attente qui ne se mesure pas : l'anneau tourne et la valeur est ignorée. Là où un indicateur tient lieu d'icône plutôt que de rendre compte d'une tâche, VSpinner est le plus petit objet à prendre.
vue
For a server that reports no percentage. Where a spinner would stand in for an icon, VSpinner is the smaller thing to reach for.
<script setup lang="ts">
import { VProgressCircular, VTypography } from 'vectis-ui'
</script>
<template>
<div class="column">
<div class="row">
<!-- The ring turns and its arc grows and shrinks on a period of its own, the two
offset so the movement never settles into a rhythm. The value is ignored:
there is nothing to report. -->
<VProgressCircular indeterminate label="Waiting for the server" />
<VProgressCircular indeterminate :size="64" tone="neutral" label="Loading" />
<VProgressCircular indeterminate :size="64" :thickness="10" label="Working" />
</div>
<VTypography variant="body-sm" tone="muted">
For a server that reports no percentage. Where a spinner would stand in for an icon, VSpinner
is the smaller thing to reach for.
</VTypography>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-5);
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--vectis-space-6);
}
</style>
Tonalités
tone dit ce que signifie la progression, sous forme de couleur. Il y en a cinq plutôt que les trois d'un bouton, un anneau rendant compte d'un état plutôt que d'amorcer une action.
color remplace le ton, en hexadécimal, en nom CSS ou en oklch(). La nuance de l'anneau non rempli en est dérivée face au thème.
vue
A hex value
A CSS colour name
An oklch() colour
<script setup lang="ts">
import { VProgressCircular, VTypography } from 'vectis-ui'
const COLORS = [
{ value: '#7c3aed', caption: 'A hex value' },
{ value: 'teal', caption: 'A CSS colour name' },
{ value: 'oklch(0.72 0.19 45)', caption: 'An oklch() colour' },
]
</script>
<template>
<div class="row">
<!-- The unfilled ring's shade is derived from the colour against the theme, so one
value is enough and it stays right in dark mode. -->
<div v-for="colour in COLORS" :key="colour.value" class="cell">
<VProgressCircular :value="65" :color="colour.value" :label="`Upload, ${colour.caption}`" />
<VTypography variant="caption" tone="muted">{{ colour.caption }}</VTypography>
</div>
</div>
</template>
<style scoped>
.row {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-6);
}
.cell {
display: grid;
justify-items: center;
gap: var(--vectis-space-2);
}
</style>
Diamètre et épaisseur
size et thickness sont le diamètre et l'anneau, toujours en pixels qu'ils soient donnés en nombre ou en chaîne numérique, et indépendants l'un de l'autre.
vue
24px
40px
64px
96px
2px ring
4px ring
8px ring
14px ring
<script setup lang="ts">
import { VProgressCircular, VTypography } from 'vectis-ui'
const SIZES = [24, 40, 64, 96]
const THICKNESSES = [2, 4, 8, 14]
</script>
<template>
<div class="column">
<!-- The geometry is entirely CSS: the radius is derived from the diameter and the
thickness, so changing either recomputes nothing in JavaScript. -->
<div class="row">
<div v-for="size in SIZES" :key="size" class="cell">
<VProgressCircular :value="65" :size="size" :label="`Upload, ${size}px`" />
<VTypography variant="caption" tone="muted">{{ size }}px</VTypography>
</div>
</div>
<!-- Thickness is independent of the diameter, and both are always pixels whether
given as a number or as a numeric string. -->
<div class="row">
<div v-for="thickness in THICKNESSES" :key="thickness" class="cell">
<VProgressCircular
:value="65"
:size="64"
:thickness="thickness"
:label="`Upload, ${thickness}px ring`"
/>
<VTypography variant="caption" tone="muted">{{ thickness }}px ring</VTypography>
</div>
</div>
</div>
</template>
<style scoped>
.column {
display: grid;
gap: var(--vectis-space-6);
}
.row {
display: flex;
flex-wrap: wrap;
align-items: end;
gap: var(--vectis-space-6);
}
.cell {
display: grid;
justify-items: center;
gap: var(--vectis-space-2);
}
</style>
Forme
shape dit si les extrémités de l'arc dessiné sont arrondies ou coupées net. Cela se voit sur un anneau épais et presque pas sur un anneau fin.
vue
rounded, the default
square
<script setup lang="ts">
import { VProgressCircular, VTypography } from 'vectis-ui'
</script>
<template>
<div class="row">
<!-- The shape is the end of the drawn arc, rounded or cut square. It shows on a
thick ring and is all but invisible on a thin one. -->
<div class="cell">
<VProgressCircular :value="65" :size="96" :thickness="14" label="Upload, rounded" />
<VTypography variant="caption" tone="muted">rounded, the default</VTypography>
</div>
<div class="cell">
<VProgressCircular
:value="65"
:size="96"
:thickness="14"
shape="square"
label="Upload, square"
/>
<VTypography variant="caption" tone="muted">square</VTypography>
</div>
</div>
</template>
<style scoped>
.row {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-6);
}
.cell {
display: grid;
justify-items: center;
gap: var(--vectis-space-2);
}
</style>
Du contenu au centre
showValue écrit le pourcentage dans le creux de l'anneau, dimensionné en proportion du diamètre. Le slot par défaut le remplace par un contenu à vous et reçoit la valeur, le max et le pourcentage qui en découle.
vue
65 %
showValue
7/12
A count
An icon
<script setup lang="ts">
import { VIcon, VProgressCircular, VTypography } from 'vectis-ui'
import { check } from 'vectis-ui/icons'
</script>
<template>
<div class="row">
<div class="cell">
<VProgressCircular :value="65" :size="96" show-value label="Upload" />
<VTypography variant="caption" tone="muted">showValue</VTypography>
</div>
<!-- The slot replaces the percentage and receives the value, the max and the
percentage worked out from them. It sits in the hole of the ring, on the page
background, so it takes the page's own text colour. -->
<div class="cell">
<VProgressCircular :value="7" :max="12" :size="96" label="Files uploaded">
<template #default="{ value, max }">
<span class="count">{{ value }}/{{ max }}</span>
</template>
</VProgressCircular>
<VTypography variant="caption" tone="muted">A count</VTypography>
</div>
<div class="cell">
<VProgressCircular :value="100" :size="96" tone="success" label="Upload finished">
<VIcon :name="check" :size="32" />
</VProgressCircular>
<VTypography variant="caption" tone="muted">An icon</VTypography>
</div>
</div>
</template>
<style scoped>
.row {
display: flex;
flex-wrap: wrap;
gap: var(--vectis-space-6);
}
.cell {
display: grid;
justify-items: center;
gap: var(--vectis-space-2);
}
.count {
font-variant-numeric: tabular-nums;
}
</style>
API
Props
Prop
Type
Défaut
value
number
0
Où en est la progression. Tout ce qui sort de l'intervalle y est ramené.
label
string
aucune
Ce qui progresse, en mots, pour les lecteurs d'écran. Rien ne s'affiche à l'écran, et le dictionnaire du design system sert de repli ; un aria-label ou un aria-labelledby à vous l'emporte sur lui.
max
number
100
Ce qui compte comme terminé. L'autre extrémité est toujours zéro.
indeterminate
boolean
false
Dit que la progression ne peut pas être mesurée : l'anneau tourne en continu et la valeur est ignorée.
Ce que la progression signifie, exprimé en couleur.
color
string
aucune
Une couleur à vous, en hexadécimal, en nom CSS ou en oklch(), qui remplace le ton. La nuance de l'anneau non rempli en est dérivée face au thème, elle suit donc les deux.
size
number | string
aucune
Le diamètre, toujours EN PIXELS : 96 et '96' donnent tous deux 96px.
thickness
number | string
aucune
L'épaisseur de l'anneau, toujours EN PIXELS : 8 et '8' donnent tous deux 8px.
shape
ProgressCircularShape'rounded' | 'square'
'rounded'
Si les extrémités de l'arc dessiné sont arrondies ou coupées net.
showValue
boolean
false
Écrit le pourcentage au milieu de l'anneau. C'est ignoré tant que la progression n'est pas mesurable, faute de chiffre à écrire.
Ce qu'il faut mettre au milieu de l'anneau à la place du pourcentage : un nombre de fichiers, une icône, un chiffre abrégé.
Types
Les types que les tables ci-dessus nomment, écrits comme la librairie les déclare. Ceux qui portent export s'importent depuis vectis-ui pour typer votre propre code ; les autres décrivent la forme de ce qu'un slot fournit.
export interface ProgressCircularSlotProps {
value: number
max: number
percent: number
}