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