Progress Component Demo

A component that renders a Bootstrap Progress element using OverReact’s statically-typed React prop API.

Contents

Basic example

Tags scale to match the size of the immediate parent element by using relative font sizing and em units.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement progressBasicDemo() => Fragment()(
  (Progress()
    ..showCaption = true
    ..captionProps = (domProps()..className = 'text-xs-center')
    ..caption = 'Reticulating splines...'
  )(),
  (Progress()
    ..value = 25.0
    ..showCaption = true
    ..captionProps = (domProps()..className = 'text-xs-center')
    ..caption = 'Reticulating splines...'
  )(),
  (Progress()
    ..value = 50.0
    ..showCaption = true
    ..captionProps = (domProps()..className = 'text-xs-center')
    ..caption = 'Reticulating splines...'
  )(),
  (Progress()
    ..value = 75.0
    ..showCaption = true
    ..captionProps = (domProps()..className = 'text-xs-center')
    ..caption = 'Reticulating splines...'
  )(),
  (Progress()
    ..value = 100.0
    ..showCaption = true
    ..captionProps = (domProps()..className = 'text-xs-center')
    ..caption = 'Reticulating splines...'
  )()
);
import 'dart:math';

import 'package:over_react/over_react.dart';
part 'progress.over_react.g.dart';

UiFactory<ProgressProps> Progress = _$Progress;

mixin ProgressProps on UiProps {
  /// The current value of the [Progress] component.
  ///
  /// This value should be between the [min] and [max] values.
  ///
  /// Default: `0.0`
  double value;

  /// The min value of the [Progress] component.
  ///
  /// Default: `0.0`
  double min;

  /// The max value of the [Progress] component.
  ///
  /// Default: `100.0`
  double max;

  /// The skin / "context" for the [Progress] component.
  ///
  /// See: <https://getbootstrap.com/docs/4.4/components/progress/#contextual-alternatives>.
  ///
  /// Default: [ProgressSkin.DEFAULT]
  ProgressSkin skin;

  /// Whether to render a "Barber Pole" gradient stripe effect in the [Progress] component.
  ///
  /// Default: false
  bool isStriped;

  /// Whether to animate the "Barber Pole" gradient stripe effect in the [Progress] component.
  ///
  /// __Note:__ Has no effect if [isStriped] is `false`.
  ///
  /// Default: false
  bool isAnimated;

  /// Optionally add a caption that describes the context of the [Progress] component.
  ///
  /// See: <https://getbootstrap.com/docs/4.4/components/progress/#example>.
  ///
  /// Default: [ProgressComponent._getPercentComplete]%
  String caption;

  /// Additional props to be added to the [caption] element _(if specified)_.
  Map captionProps;

  /// Whether the [caption] should be visible.
  ///
  /// Default: false
  bool showCaption;

  /// Whether the [caption] should be appended with the value of [value].
  ///
  /// Default: true
  bool showPercentComplete;

  /// Additional props to be added to the [Dom.div] that wraps around the [caption] element and `<progress>` element.
  Map rootNodeProps;
}

mixin ProgressState on UiState {
  /// An autogenerated GUID, used as a fallback when [ProgressProps.id] is unspecified, and
  /// saved on the state so it will persist across remounts.
  ///
  /// HTML id attributes are needed on `<progress>` elements for proper accessibility support,
  /// so this state value ensures there's always a valid ID value to use.
  String id;
}

class ProgressComponent extends UiStatefulComponent2<ProgressProps, ProgressState> {
  @override
  get defaultProps => (newProps()
    ..value = 0.0
    ..min = 0.0
    ..max = 100.0
    ..skin = ProgressSkin.DEFAULT
    ..isStriped = false
    ..isAnimated = false
    ..showCaption = false
    ..showPercentComplete = true
  );

  @override
  get initialState => (newState()
    ..id = 'progress_' + generateGuid(4)
  );

  @override
  render() {
    return (Dom.div()..addProps(props.rootNodeProps))(
      renderCaptionNode(),
      renderProgressNode(),
      props.children
    );
  }

  ReactElement renderProgressNode() {
    return (Dom.progress()
      ..modifyProps(addUnconsumedDomProps)
      ..aria.labelledby = captionId
      ..className = _getProgressNodeClasses().toClassName()
      ..id = id
      ..value = currentValue
      ..max = props.max
    )();
  }

  ReactElement renderCaptionNode() {
    var captionClasses = ClassNameBuilder.fromProps(props.captionProps)
      ..add('sr-only', !props.showCaption);

    var captionText = props.caption  ?? '';

    if (props.showPercentComplete) {
      captionText += ' ${_getPercentComplete()}%';
    }

    return (Dom.div()
      ..addProps(props.captionProps)
      ..id = captionId
      ..className = captionClasses.toClassName()
    )(captionText);
  }

  ClassNameBuilder _getProgressNodeClasses() {
    return ClassNameBuilder()
      ..add('progress')
      ..add('progress-striped', props.isStriped)
      ..add('progress-animated', props.isAnimated)
      ..add(props.skin.className);
  }

  /// Get the percentage complete based on [ProgressProps.min], [ProgressProps.max] and [ProgressProps.value].
  double _getPercentComplete() {
    return (props.value - props.min) / (props.max - props.min) * 100;
  }

  /// Returns the value that determines the width of the progress bar
  /// within the rendered [Progress] component via [DomPropsMixin.value].
  ///
  /// Note that the value of the HTML `<progress>` element's value
  /// attribute will never be less than the value of [ProgressProps.min].
  double get currentValue => max(props.min, props.value);

  String get id => props.id ?? state.id;

  String get captionId => '${id}_caption';
}

class ProgressSkin extends ClassNameConstant {
  const ProgressSkin._(String name, String className) : super(name, className);

  /// [className] value: ''
  static const ProgressSkin DEFAULT =
      ProgressSkin._('DEFAULT', '');

  /// [className] value: 'progress-danger'
  static const ProgressSkin DANGER =
      ProgressSkin._('DANGER', 'progress-danger');

  /// [className] value: 'progress-success'
  static const ProgressSkin SUCCESS =
      ProgressSkin._('SUCCESS', 'progress-success');

  /// [className] value: 'progress-warning'
  static const ProgressSkin WARNING =
      ProgressSkin._('WARNING', 'progress-warning');

  /// [className] value: 'progress-info'
  static const ProgressSkin INFO =
      ProgressSkin._('INFO', 'progress-info');
}

Contextual skins

Set props.skin to style a Progress using contextual colors.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement progressContextualDemo() => Dom.div()(
  (Progress()
    ..value = 25.0
    ..skin = ProgressSkin.SUCCESS
  )(),
  (Progress()
    ..value = 50.0
    ..skin = ProgressSkin.INFO
  )(),
  (Progress()
    ..value = 75.0
    ..skin = ProgressSkin.WARNING
  )(),
  (Progress()
    ..value = 100.0
    ..skin = ProgressSkin.DANGER
  )()
);

Striped

Set props.isStriped to render a "Barber Pole" gradient stripe effect in a Progress component.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement progressStripedDemo() => Dom.div()(
  (Progress()
    ..value = 10.0
    ..isStriped = true
  )(),
  (Progress()
    ..value = 25.0
    ..skin = ProgressSkin.SUCCESS
    ..isStriped = true
  )(),
  (Progress()
    ..value = 50.0
    ..skin = ProgressSkin.INFO
    ..isStriped = true
  )(),
  (Progress()
    ..value = 75.0
    ..skin = ProgressSkin.WARNING
    ..isStriped = true
  )(),
  (Progress()
    ..value = 100.0
    ..skin = ProgressSkin.DANGER
    ..isStriped = true
  )()
);

Animated stripes

Set props.isAnimated to animate the stripes.

Rendering OverReact Demo . . .
import 'package:over_react/over_react.dart';

import '../../demo_components.dart';

ReactElement progressAnimatedStripesDemo() =>
  (Progress()
    ..value = 25.0
    ..isStriped = true
    ..isAnimated = true
  )();