/ libs / ui / src / components / progress.rs
progress.rs
  1  use dioxus::html::GlobalAttributesExtension;
  2  use dioxus::prelude::*;
  3  use dioxus_primitives::progress::Progress as ProgressPrimitive;
  4  
  5  /// Progress size variants
  6  #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
  7  pub enum ProgressSize {
  8      Small,
  9      #[default]
 10      Medium,
 11      Large,
 12  }
 13  
 14  /// Progress color variants
 15  #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
 16  pub enum ProgressVariant {
 17      #[default]
 18      Default,
 19      Destructive,
 20      Success,
 21      Warning,
 22  }
 23  
 24  /// Props for the Progress component
 25  #[derive(Props, Clone, PartialEq)]
 26  pub struct ProgressProps {
 27      /// The current progress value, between 0 and max
 28      value: ReadSignal<f64>,
 29  
 30      /// The maximum value. Defaults to 100
 31      #[props(default = 100.0)]
 32      max: f64,
 33  
 34      /// Size variant of the progress bar
 35      #[props(default)]
 36      pub size: ProgressSize,
 37  
 38      /// Color variant of the progress bar
 39      #[props(default)]
 40      pub variant: ProgressVariant,
 41  
 42      /// Optional ID for the progress element
 43      #[props(default)]
 44      pub id: Option<String>,
 45  
 46      /// Accessible label for the progress
 47      #[props(default)]
 48      pub aria_label: Option<String>,
 49  
 50      /// Whether to show the percentage text
 51      #[props(default = false)]
 52      pub show_percentage: bool,
 53  
 54      /// Custom class names for the progress container
 55      #[props(default)]
 56      pub class: Option<String>,
 57  }
 58  
 59  /// A styled progress component for showing completion progress
 60  #[component]
 61  pub fn Progress(props: ProgressProps) -> Element {
 62      // Calculate percentage
 63      let current: f64 = (props.value)();
 64      let max_value = props.max;
 65      let percentage = (current / max_value * 100.0).clamp(0.0, 100.0);
 66      // An adapter to convert signal type from `f64` to `Option<f64>`
 67      let value_optional = use_memo(move || Some((props.value)()));
 68  
 69      // Determine size-specific classes
 70      let height_class = match props.size {
 71          ProgressSize::Small => "h-2",
 72          ProgressSize::Medium => "h-3",
 73          ProgressSize::Large => "h-4",
 74      };
 75  
 76      // Determine color variant classes
 77      let indicator_color = match props.variant {
 78          ProgressVariant::Default => "bg-primary",
 79          ProgressVariant::Destructive => "bg-destructive",
 80          ProgressVariant::Success => "bg-green-500",
 81          ProgressVariant::Warning => "bg-yellow-500",
 82      };
 83  
 84      // Build container classes
 85      let container_class = format!(
 86          "relative w-full overflow-hidden rounded-full bg-secondary {}",
 87          height_class
 88      );
 89  
 90      let combined_class = if let Some(custom_class) = &props.class {
 91          format!("{} {}", container_class, custom_class)
 92      } else {
 93          container_class
 94      };
 95  
 96      // Build indicator classes
 97      let indicator_class = format!(
 98          "h-full transition-all duration-300 ease-in-out {}",
 99          indicator_color
100      );
101  
102      rsx! {
103          div { class: "w-full space-y-2",
104              if props.show_percentage {
105                  div { class: "flex justify-between text-sm text-muted-foreground",
106                      span {
107                          if let Some(label) = &props.aria_label {
108                              "{label}"
109                          } else {
110                              "Progress"
111                          }
112                      }
113                      span { "{percentage:.0}%" }
114                  }
115              }
116  
117              ProgressPrimitive {
118                  value: value_optional,
119                  max: props.max,
120                  class: combined_class,
121                  id: props.id.clone(),
122  
123                  div {
124                      class: indicator_class,
125                      style: format!("width: {}%", percentage),
126                  }
127              }
128          }
129      }
130  }