Raccourci clavier : Ctrl + K
Commencer

Progression circulaire

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.

Utilisation

vue
65 %
<script setup lang="ts">
import { VProgressCircular } from 'vectis-ui'
</script>

<template>
  <VProgressCircular :value="65" label="Upload" show-value />
</template>

Exemples

Valeur

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.

vue

accent

success

warning

danger

neutral

<script setup lang="ts">
import { VProgressCircular, VTypography, type ProgressCircularTone } from 'vectis-ui'

const TONES: ProgressCircularTone[] = ['accent', 'success', 'warning', 'danger', 'neutral']
</script>

<template>
  <div class="row">
    <div v-for="tone in TONES" :key="tone" class="cell">
      <VProgressCircular :value="65" :tone="tone" :label="`Upload, ${tone}`" />
      <VTypography variant="caption" tone="muted">{{ tone }}</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>

Couleurs personnalisées

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

PropTypeDéfaut
valuenumber0
Où en est la progression. Tout ce qui sort de l'intervalle y est ramené.
labelstringaucune
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.
maxnumber100
Ce qui compte comme terminé. L'autre extrémité est toujours zéro.
indeterminatebooleanfalse
Dit que la progression ne peut pas être mesurée : l'anneau tourne en continu et la valeur est ignorée.
toneProgressCircularTone'neutral' | 'accent' | 'danger' | 'success' | 'warning''accent'
Ce que la progression signifie, exprimé en couleur.
colorstringaucune
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.
sizenumber | stringaucune
Le diamètre, toujours EN PIXELS : 96 et '96' donnent tous deux 96px.
thicknessnumber | stringaucune
L'épaisseur de l'anneau, toujours EN PIXELS : 8 et '8' donnent tous deux 8px.
shapeProgressCircularShape'rounded' | 'square''rounded'
Si les extrémités de l'arc dessiné sont arrondies ou coupées net.
showValuebooleanfalse
Écrit le pourcentage au milieu de l'anneau. C'est ignoré tant que la progression n'est pas mesurable, faute de chiffre à écrire.

Slots

SlotType
defaultProgressCircularSlotProps
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
}

Variables CSS

TokenValeur
--vectis-control-size-progress-circular-diameter3rem
--vectis-control-size-progress-circular-thickness0.25rem