patternfly_yew/components/
helper_text.rs1use crate::prelude::{AsClasses, ExtendClasses, Icon};
7use log::warn;
8use std::rc::Rc;
9use yew::prelude::*;
10
11#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
12pub enum HelperTextComponent {
13 #[default]
14 Div,
15 Ul,
16}
17
18impl ToString for HelperTextComponent {
19 fn to_string(&self) -> String {
20 match self {
21 Self::Div => "div",
22 Self::Ul => "ul",
23 }
24 .to_string()
25 }
26}
27
28#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
29pub enum HelperTextItemVariant {
30 #[default]
31 Default,
32 Intermediate,
33 Warning,
34 Success,
35 Error,
36}
37
38impl AsClasses for HelperTextItemVariant {
39 fn extend_classes(&self, classes: &mut Classes) {
40 match self {
41 Self::Default => {}
42 Self::Intermediate => classes.push(classes!("pf-m-indeterminate")),
43 Self::Warning => classes.push(classes!("pf-m-warning")),
44 Self::Success => classes.push(classes!("pf-m-success")),
45 Self::Error => classes.push(classes!("pf-m-error")),
46 }
47 }
48}
49
50impl HelperTextItemVariant {
51 pub fn icon(&self) -> Icon {
52 match self {
53 Self::Default => Icon::Minus,
54 Self::Intermediate => Icon::Minus,
55 Self::Warning => Icon::ExclamationTriangle,
56 Self::Success => Icon::CheckCircle,
57 Self::Error => Icon::ExclamationCircle,
58 }
59 }
60}
61
62#[derive(Clone, Debug, PartialEq, Properties)]
63pub struct HelperTextProperties {
64 #[prop_or_default]
66 pub aria_label: Option<AttrValue>,
67 #[prop_or_default]
69 pub children: ChildrenWithProps<HelperTextItem>,
70 #[prop_or_default]
72 pub class: Classes,
73 #[prop_or_default]
75 pub component: HelperTextComponent,
76 #[prop_or_default]
80 pub id: Option<AttrValue>,
81 #[prop_or_default]
84 pub live_region: bool,
85
86 #[prop_or_default]
89 pub hidden: bool,
90}
91
92#[function_component(HelperText)]
122pub fn helper_text(props: &HelperTextProperties) -> Html {
123 let mut class = classes!("pf-v5-c-helper-text", props.class.clone());
124 if props.hidden {
125 class.push("pf-m-hidden")
126 }
127 let aria_live = props.live_region.then_some("polite");
128 let component = props.component.to_string();
129 let item_component = match props.component {
130 HelperTextComponent::Div => HelperTextItemComponent::Div,
131 HelperTextComponent::Ul => HelperTextItemComponent::Li,
132 };
133 let role = (props.component == HelperTextComponent::Ul).then_some("list");
134 let _ = use_memo(
135 (props.component, props.aria_label.clone()),
136 |(component, label)| {
138 if component == &HelperTextComponent::Ul && label.is_none() {
139 warn!(
140 "The aria_label property should be set on the HelperText component when the \
141 component attribute is set to HelperTextComponent::Ul"
142 );
143 }
144 },
145 );
146
147 html!(
148 <@{component}
149 id={ &props.id }
150 { class }
151 aria-label={ &props.aria_label }
152 aria-live={ aria_live }
153 { role }
154 >
155 {
156 for props.children.iter().map(|mut c|{
157 let props = Rc::make_mut(&mut c.props);
158 props.component = item_component;
159 c
160 })
161 }
162 </@>
163 )
164}
165
166#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
167pub enum HelperTextItemComponent {
168 #[default]
169 Div,
170 Li,
171}
172
173impl ToString for HelperTextItemComponent {
174 fn to_string(&self) -> String {
175 match self {
176 Self::Div => "div",
177 Self::Li => "li",
178 }
179 .to_string()
180 }
181}
182
183#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
184pub enum HelperTextItemIcon {
185 #[default]
186 Default,
187 Hidden,
188 Visible,
189 Custom(Icon),
190}
191
192#[derive(Clone, Debug, PartialEq, Properties)]
193pub struct HelperTextItemProperties {
194 #[prop_or_default]
196 pub children: Html,
197 #[prop_or_default]
199 pub class: Classes,
200 #[prop_or_default]
203 pub component: HelperTextItemComponent,
204 #[prop_or_default]
207 pub dynamic: bool,
208 #[prop_or_default]
211 pub icon: HelperTextItemIcon,
212 #[prop_or_default]
216 pub id: Option<AttrValue>,
217 #[prop_or_default]
220 pub screen_reader_text: AttrValue,
221 #[prop_or_default]
223 pub variant: HelperTextItemVariant,
224}
225
226#[function_component(HelperTextItem)]
236pub fn helper_text_item(props: &HelperTextItemProperties) -> Html {
237 let mut class = classes!("pf-v5-c-helper-text__item", props.class.clone());
238 if props.dynamic {
239 class.push(classes!("pf-m-dynamic"));
240 }
241 class.extend_from(&props.variant);
242 let component = props.component.to_string();
243 let icon = match (props.icon, &props.dynamic) {
244 (HelperTextItemIcon::Default, false) | (HelperTextItemIcon::Hidden, ..) => None,
245 (HelperTextItemIcon::Default, true) | (HelperTextItemIcon::Visible, ..) => {
246 Some(props.variant.icon())
247 }
248 (HelperTextItemIcon::Custom(icon), ..) => Some(icon),
249 };
250
251 let item_icon = icon.map(|icon| {
252 html!(
253 <span class="pf-v5-c-helper-text__item-icon">
254 { icon }
255 </span>
256 )
257 });
258
259 let screen_reader = use_memo(
260 (props.screen_reader_text.clone(), props.dynamic),
261 |(text, _)| {
263 if !text.is_empty() {
264 if props.dynamic {
265 Some(html!(
266 <span class="pf-v5-u-screen-reader">
267 { ": " }{ text }{ ";" }
268 </span>
269 ))
270 } else {
271 warn!(
272 "The screen_reader_text attribute was set but has not been used as the \
273 dynamic attribute was not set to true."
274 );
275 None
276 }
277 } else {
278 None
279 }
280 },
281 );
282
283 html!(
284 <@{component} id={ &props.id } { class }>
285 { item_icon }
286 <div class="pf-v5-c-helper-text__item-text">
287 { props.children.clone() }
288 { (*screen_reader).clone() }
289 </div>
290 </@>
291 )
292}