PlProgressCircular
A ring that fills. The one to use where there is no room for a bar, inside a table row, beside a field, at the end of a line of text.
import { PlProgressCircular } from 'plass-ui';
<PlProgressCircular label="Syncing" value={68} showValue />;import 'package:plass_ui/plass_ui.dart';
PlProgressCircular(label: const Text('Syncing'), value: 68, showValue: true);Props
| Prop | Type | Default | Description |
|---|---|---|---|
| value | number | null | null | How far along, between min and max. null is the indeterminate case: something is happening and nobody knows how much of it is left |
| min | number | 0 | The bottom of the range |
| max | number | 100 | The top of the range |
| label | ReactNode | — | A name for what is loading. Read out with the value by a screen reader |
| showValue | boolean | false | Shows the value as text beside the shape. A percentage of the range unless format says otherwise |
| format | Intl.NumberFormatOptions | — | How to format the value when it is shown — Intl.NumberFormat options, so bytes and currencies work as well as plain numbers |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | Diameter of the ring. Sits just under the control ladder at every step |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | Semantic colour role. It becomes the gradient of the filled part |
| Prop | Type | Default | Description |
|---|---|---|---|
| value | double? | null | How far along, between min and max. null is the indeterminate case: something is happening and nobody knows how much of it is left |
| min | double | 0 | The bottom of the range |
| max | double | 100 | The top of the range |
| label | Widget? | — | A name for what is loading. Read out with the value by a screen reader |
| showValue | bool | false | Shows the value as text beside the shape. A percentage of the range unless format says otherwise |
| formatValue | String Function(double value)? | — | How to write the value, as a function rather than React's options object: there is no Intl.NumberFormat in the framework, and pulling package:intl in to provide one would be a dependency decision made on the consumer's behalf |
| sizeshared | PlassSize | PlassSize.md | Diameter of the ring. Sits just under the control ladder at every step |
| colorshared | PlassColor | PlassColor.primary | Semantic colour role. It becomes the gradient of the filled part |
The table is PlProgressLinear's, and only size means something different: on a bar it is thickness, on a ring it is diameter. That is the claim the indicators make, one component in three shapes, and it is why they share a props table rather than three that would drift.
Every native <div> attribute passes straight through. color is excluded because it collides with the color in the table above, and children because a ring holds nothing.
formatValue is a function where React takes an options object, for the reason PlProgressLinear gives.
The arc
An SVG stroke cannot be given a CSS gradient, so the ring builds a <linearGradient> of its own out of the same two stops the bar's fill is made of, at the same 135°.
A stroke takes a Shader rather than a decoration, so the sweep the rest of the package gets from PlassCssGradient is asked for directly. The one place in the library a shader is built by hand.
Either way it is worth the extra work: a flat ring beside a swept bar is two materials for one idea.
The track under it is --plass-track, the same neutral ink the bar's groove is, so a ring and a bar on one screen are cut into the same surface.
The value label
Not inside it. A number in the middle of a dial is the picture everyone has of this component, and it works at two of the five sizes: at xs the ring is fourteen pixels across and there is nowhere for "40%" to go. Beside it, every size reads.
Examples
value
null, the default, is the indeterminate case. The ring then draws a fixed quarter-arc and turns, which is the one place the library moves something on its own, and the exception is the same one the button's spinner already has: an indeterminate indicator that holds still is a decoration.
With a value the ring holds still and the gap closes instead. Both are one dash pattern on one circle.
size
Diameter, on a ladder that sits just under the control ladder at every step, a md ring is 20px inside a 40px control, so a ring dropped into a button, a field or a table row never makes the row taller than it already was.
color
In a row
The size ladder is what this is for: an xs ring in a table cell is fourteen pixels, and the row is the height it was already going to be.
Accessibility
- Base UI renders a
role="progressbar"and keepsaria-valuenow,aria-valueminandaria-valuemaxin step with the props. - An indeterminate ring reports no value at all rather than zero, which is what tells a screen reader to announce indeterminate progress.
- The
<svg>isaria-hidden: it is the drawing, and everything it says is already in the role and the value. aria-valuetextis the same stringshowValuedraws. Withoutformatthat is a percentage of the range, not of 100.- Under
prefers-reduced-motionthe ring is slowed to where it stops reading as motion rather than stopped, for the reason it turns at all.
- The ring is one merged semantics node carrying
SemanticsRole.progressBarand its value, so the label and the ring are read together. - With no value the role is
SemanticsRole.loadingSpinnerand there is no value at all, which is what tells the platform to announce indeterminate progress. - The drawn percentage is behind
ExcludeSemantics: the same string is already the node's value. - With
MediaQuery.disableAnimationsthe ring is slowed rather than stopped.
Differences from the React build
| React | Flutter | Why |
|---|---|---|
format: Intl.NumberFormatOptions | formatValue: String Function(double) | There is no Intl.NumberFormat in the framework, and pulling package:intl in to provide one would be a dependency decision made on the consumer's behalf. |
an <svg> with a <linearGradient> | a CustomPainter with a ui.Gradient shader | Same two stops, same 135°; a stroke takes a shader rather than a decoration. |
className, style, native attributes | — | There is no class list and no style attribute to pass through. |