patternfly_yew/components/
progress.rs1use crate::core::{AsClasses, ExtendClasses};
4use crate::icon::Icon;
5use crate::prelude::*;
6use std::ops::Range;
7use yew::prelude::*;
8use yew::virtual_dom::VChild;
9
10#[derive(Copy, Clone, Eq, PartialEq, Default)]
11pub enum ProgressValueFormat {
12 #[default]
13 Percentage,
14 Decimal(usize),
15 Integer,
16 Raw,
17}
18
19impl ProgressValueFormat {
20 pub fn format(&self, value: f64) -> String {
21 match self {
22 Self::Percentage => format!("{:.0}%", value * 100f64),
23 Self::Decimal(precision) => format!("{value:.precision$}"),
24 Self::Raw => format!("{value}"),
25 Self::Integer => format!("{:.0}", value),
26 }
27 }
28}
29
30#[derive(Copy, Clone, Eq, PartialEq, Default)]
31pub enum ProgressSize {
32 #[default]
33 Default,
34 Small,
35 Large,
36}
37
38impl AsClasses for ProgressSize {
39 fn extend_classes(&self, classes: &mut Classes) {
40 match self {
41 Self::Default => {}
42 Self::Small => classes.push(classes!("pf-m-sm")),
43 Self::Large => classes.push(classes!("pf-m-lg")),
44 }
45 }
46}
47
48#[derive(Copy, Clone, Eq, PartialEq, Default)]
49pub enum ProgressMeasureLocation {
50 #[default]
51 Default,
52 Inside,
53 Outside,
54 None,
55}
56
57impl AsClasses for ProgressMeasureLocation {
58 fn extend_classes(&self, classes: &mut Classes) {
59 match self {
60 Self::Default | Self::None => {}
61 Self::Outside => classes.push(classes!("pf-m-outside")),
62 Self::Inside => classes.push(classes!("pf-m-inside")),
63 }
64 }
65}
66
67#[derive(Copy, Clone, Eq, PartialEq, Default)]
68pub enum ProgressVariant {
69 #[default]
70 Default,
71 Success,
72 Warning,
73 Danger,
74}
75
76impl AsClasses for ProgressVariant {
77 fn extend_classes(&self, classes: &mut Classes) {
78 match self {
79 Self::Default => {}
80 Self::Success => classes.push(classes!("pf-m-success")),
81 Self::Warning => classes.push(classes!("pf-m-warning")),
82 Self::Danger => classes.push(classes!("pf-m-danger")),
83 }
84 }
85}
86
87impl ProgressVariant {
88 pub fn icon(&self) -> Option<Icon> {
89 match self {
90 Self::Default => None,
91 Self::Success => Some(Icon::CheckCircle),
92 Self::Warning => Some(Icon::ExclamationTriangle),
93 Self::Danger => Some(Icon::TimesCircle),
94 }
95 }
96}
97
98#[derive(PartialEq, Properties)]
100pub struct ProgressProperties {
101 pub value: f64,
103
104 #[prop_or_default]
105 pub format: ProgressValueFormat,
106
107 #[prop_or(0f64..1f64)]
109 pub range: Range<f64>,
110
111 #[prop_or_default]
112 pub description: OptionalHtml,
113
114 #[prop_or_default]
116 pub id: Option<AttrValue>,
117
118 #[prop_or_default]
119 pub size: ProgressSize,
120
121 #[prop_or_default]
123 pub location: ProgressMeasureLocation,
124
125 #[prop_or_default]
127 pub truncate: bool,
128
129 #[prop_or_default]
130 pub variant: ProgressVariant,
131
132 #[prop_or_default]
134 pub no_icon: bool,
135
136 #[prop_or_default]
138 pub helper_text: Option<VChild<HelperText>>,
139
140 #[prop_or_default]
142 pub value_text: Option<String>,
143
144 #[prop_or_default]
146 pub class: Classes,
147
148 #[prop_or_default]
150 pub measure_class: Classes,
151
152 #[prop_or_default]
154 pub style: Option<AttrValue>,
155}
156
157#[function_component(Progress)]
173pub fn progress(props: &ProgressProperties) -> Html {
174 let id = use_id(props.id.clone());
175 let desc_id = use_suffixed_id(&id, "-description");
176
177 let percentage =
178 ((props.value / (props.range.end - props.range.start)) * 100f64).clamp(0f64, 100f64);
179 let style = format!("width: {percentage:.0}%;");
180
181 let mut class = classes!("pf-v5-c-progress");
182 class.extend_from(&props.size);
183 class.extend_from(&props.location);
184 class.extend_from(&props.variant);
185 if props.description.is_none() {
186 class.extend(classes!("pf-m-singleline"));
187 }
188 class.extend(&props.class);
189
190 let measure_class = classes!("pf-v5-c-progress__measure", props.measure_class.clone());
191 let mut measure = match props.location {
192 ProgressMeasureLocation::None => None,
193 _ => {
194 let measure = props
195 .value_text
196 .clone()
197 .unwrap_or_else(|| props.format.format(props.value));
198 Some(html!(<span class={measure_class}> { measure } </span>))
199 }
200 };
201
202 let mut description_class = classes!("pf-v5-c-progress__description");
203 if props.truncate {
204 description_class.push(classes!("pf-m-truncate"));
205 }
206
207 let icon = match props.no_icon {
208 true => None,
209 false => props.variant.icon(),
210 }
211 .map(|icon| {
212 html!(
213 <span class="pf-v5-c-progress__status-icon">
214 { icon }
215 </span>
216 )
217 });
218
219 html!(
220 <div {class} {id} style={props.style.clone()}>
221 if let Some(description) = &props.description.0 {
222 <div
223 class={description_class}
224 id={desc_id.clone()}
225 >
226 { description.clone() }
227 </div>
228 }
229 <div class="pf-v5-c-progress__status" aria-hidden="true">
230 if matches!(props.location, ProgressMeasureLocation::Default | ProgressMeasureLocation::Outside) {
231 { measure.take() }
232 }
233 { icon }
234 </div>
235 <div
236 class="pf-v5-c-progress__bar"
237 role="progressbar"
238 aria-valuemin={ props.range.start.to_string() }
239 aria-valuemax={ props.range.end.to_string() }
240 aria-valuenow={ props.value.to_string() }
241 aria-labelledby={desc_id}
242 >
243 <div class="pf-v5-c-progress__indicator" {style}>
244 if matches!(props.location, ProgressMeasureLocation::Inside) {
245 { measure }
246 }
247 </div>
248 </div>
249 if let Some(helper_text) = &props.helper_text {
250 <div class="pf-v5-c-progress__helper-text">
251 { helper_text.clone() }
252 </div>
253 }
254 </div>
255 )
256}