circularProgress function

BloomNode circularProgress({
  1. required double value,
  2. double max = 100,
  3. double size = 40,
  4. double strokeWidth = 3.5,
  5. String? color,
  6. String? label,
  7. bool showValue = false,
  8. String extraClassName = '',
})

Circular determinate progress indicator primitive.

Pure SVG implementation with smooth stroke animations.

Implementation

BloomNode circularProgress({
  required double value,
  double max = 100,
  double size = 40,
  double strokeWidth = 3.5,
  String? color,
  String? label,
  bool showValue = false,
  String extraClassName = '',
}) {
  final clampedMax = max <= 0 ? 100.0 : max;
  final clampedValue = value.clamp(0.0, clampedMax);
  final percentage = (clampedValue / clampedMax).clamp(0.0, 1.0);

  final radius = (size - strokeWidth) / 2.0;
  final circumference = 2 * math.pi * radius;
  final strokeDashoffset = circumference - (percentage * circumference);

  final progressColor = color ?? 'var(--primary)';
  final center = size / 2.0;

  return Div(
    attrs: {
      'role': 'progressbar',
      'aria-valuenow': '$clampedValue',
      'aria-valuemin': '0',
      'aria-valuemax': '$clampedMax',
      'data-slot': 'circular-progress',
    },
    className: cn(['inline-flex flex-col items-center gap-1.5 select-none', extraClassName]),
    children: [
      Div(
        className: 'relative inline-flex items-center justify-center',
        children: [
          El(
            'svg',
            attrs: {
              'width': '$size',
              'height': '$size',
              'viewBox': '0 0 $size $size',
              'aria-hidden': 'true',
            },
            className: 'shrink-0 -rotate-90',
            children: [
              // Background track
              El(
                'circle',
                attrs: {
                  'cx': '$center',
                  'cy': '$center',
                  'r': '$radius',
                  'fill': 'none',
                  'stroke': 'var(--muted)',
                  'stroke-width': '$strokeWidth',
                },
              ),
              // Animated progress arc
              El(
                'circle',
                attrs: {
                  'cx': '$center',
                  'cy': '$center',
                  'r': '$radius',
                  'fill': 'none',
                  'stroke': progressColor,
                  'stroke-width': '$strokeWidth',
                  'stroke-dasharray': '$circumference',
                  'stroke-dashoffset': '$strokeDashoffset',
                  'stroke-linecap': 'round',
                },
                className: 'transition-all duration-300 ease-out',
              ),
            ],
          ),
          if (showValue)
            Span(
              className: 'absolute text-[10px] font-semibold text-[var(--text)]',
              text: '${(percentage * 100).round()}%',
            ),
        ],
      ),
      if (label != null && label.isNotEmpty)
        Span(
          className: 'text-xs text-[var(--text-muted)]',
          text: label,
        ),
    ],
  );
}