yew_bs/components/
progress.rs

1use yew::prelude::*;
2use crate::components::common::Variant;
3#[derive(Clone, Copy, PartialEq, Debug)]
4pub enum ProgressAnimation {
5    Striped,
6    Animated,
7    Both,
8}
9impl ProgressAnimation {
10    pub fn as_str(&self) -> &'static str {
11        match self {
12            ProgressAnimation::Striped => "progress-bar-striped",
13            ProgressAnimation::Animated => "progress-bar-animated",
14            ProgressAnimation::Both => "progress-bar-striped progress-bar-animated",
15        }
16    }
17}
18/// Props for the ProgressBar component
19#[derive(Properties, PartialEq)]
20pub struct ProgressBarProps {
21    /// Progress value (0-100)
22    #[prop_or(0)]
23    pub value: u8,
24    /// Minimum value
25    #[prop_or(0)]
26    pub min: u8,
27    /// Maximum value
28    #[prop_or(100)]
29    pub max: u8,
30    /// Progress bar variant
31    #[prop_or_default]
32    pub variant: Option<Variant>,
33    /// Animation type
34    #[prop_or_default]
35    pub animation: Option<ProgressAnimation>,
36    /// Whether the progress bar is striped
37    #[prop_or_default]
38    pub striped: bool,
39    /// Whether the progress bar is animated
40    #[prop_or_default]
41    pub animated: bool,
42    /// Custom label text (overrides percentage)
43    #[prop_or_default]
44    pub label: Option<AttrValue>,
45    /// Whether to show percentage label
46    #[prop_or(true)]
47    pub show_label: bool,
48    /// Custom width (for multiple bars)
49    #[prop_or_default]
50    pub width: Option<AttrValue>,
51    /// Additional CSS classes
52    #[prop_or_default]
53    pub class: Option<AttrValue>,
54    /// ARIA label for accessibility
55    #[prop_or_default]
56    pub aria_label: Option<AttrValue>,
57}
58/// Bootstrap ProgressBar component
59#[function_component(ProgressBar)]
60pub fn progress_bar(props: &ProgressBarProps) -> Html {
61    let mut classes = "progress-bar".to_string();
62    if let Some(variant) = &props.variant {
63        classes.push_str(&format!(" bg-{}", variant.as_str()));
64    }
65    let final_animation = if let Some(animation) = &props.animation {
66        Some(*animation)
67    } else if props.striped && props.animated {
68        Some(ProgressAnimation::Both)
69    } else if props.striped {
70        Some(ProgressAnimation::Striped)
71    } else if props.animated {
72        Some(ProgressAnimation::Animated)
73    } else {
74        None
75    };
76    if let Some(animation) = final_animation {
77        let animation_str = animation.as_str();
78        for class in animation_str.split_whitespace() {
79            classes.push(' ');
80            classes.push_str(class);
81        }
82    }
83    if let Some(class) = &props.class {
84        classes.push(' ');
85        classes.push_str(class.as_str());
86    }
87    let percentage = if props.max > props.min {
88        ((props.value as f32 - props.min as f32) / (props.max as f32 - props.min as f32)
89            * 100.0) as u8
90    } else {
91        0
92    };
93    let label_text = if let Some(label) = &props.label {
94        label.clone()
95    } else if props.show_label {
96        format!("{}%", percentage).into()
97    } else {
98        "".into()
99    };
100    let width_style = if let Some(width) = &props.width {
101        width.clone()
102    } else {
103        format!("{}%", percentage).into()
104    };
105    html! {
106        < div class = { classes } role = "progressbar" style = { format!("width: {}",
107        width_style.as_str()) } aria - valuenow = { percentage.to_string() } aria -
108        valuemin = { props.min.to_string() } aria - valuemax = { props.max.to_string() }
109        aria - label = { props.aria_label.clone() } > { label_text } </ div >
110    }
111}
112/// Props for the Progress component
113#[derive(Properties, PartialEq)]
114pub struct ProgressProps {
115    /// Progress bars (can be multiple for stacked progress)
116    #[prop_or_default]
117    pub children: Children,
118    /// Progress value (0-100) - for single bar convenience
119    #[prop_or_default]
120    pub value: Option<u8>,
121    /// Progress variant - for single bar convenience
122    #[prop_or_default]
123    pub variant: Option<Variant>,
124    /// Animation type - for single bar convenience
125    #[prop_or_default]
126    pub animation: Option<ProgressAnimation>,
127    /// Whether progress bars are striped - for single bar convenience
128    #[prop_or_default]
129    pub striped: bool,
130    /// Whether progress bars are animated - for single bar convenience
131    #[prop_or_default]
132    pub animated: bool,
133    /// Progress height (e.g., "1rem", "20px")
134    #[prop_or_default]
135    pub height: Option<AttrValue>,
136    /// Additional CSS classes
137    #[prop_or_default]
138    pub class: Option<AttrValue>,
139    /// ARIA label for the progress container
140    #[prop_or_default]
141    pub aria_label: Option<AttrValue>,
142}
143/// Bootstrap Progress component
144#[function_component(Progress)]
145pub fn progress(props: &ProgressProps) -> Html {
146    let mut classes = "progress".to_string();
147    if let Some(class) = &props.class {
148        classes.push(' ');
149        classes.push_str(class.as_str());
150    }
151    let height_style = props.height.as_ref().map(|h| format!("height: {};", h.as_str()));
152    let progress_bars = if props.children.is_empty() && props.value.is_some() {
153        html! {
154            < ProgressBar value = { props.value.unwrap() } variant = { props.variant }
155            animation = { props.animation } striped = { props.striped } animated = {
156            props.animated } />
157        }
158    } else {
159        html! {
160            { for props.children.iter() }
161        }
162    };
163    html! {
164        < div class = { classes } style = { height_style } role = "progressbar" aria -
165        label = { props.aria_label.clone() } > { progress_bars } </ div >
166    }
167}