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 { for props.children.iter().map(|mut c|{
158 let props = Rc::make_mut(&mut c.props);
159 props.component = item_component;
160 c
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 Display for HelperTextItemComponent {
174 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
175 let value = match self {
176 Self::Div => "div",
177 Self::Li => "li",
178 };
179
180 f.write_str(value)
181 }
182}
183
184#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
185pub enum HelperTextItemIcon {
186 #[default]
187 Default,
188 Hidden,
189 Visible,
190 Custom(Icon),
191}
192
193#[derive(Clone, Debug, PartialEq, Properties)]
194pub struct HelperTextItemProperties {
195 #[prop_or_default]
197 pub children: Html,
198 #[prop_or_default]
200 pub class: Classes,
201 #[prop_or_default]
204 pub component: HelperTextItemComponent,
205 #[prop_or_default]
208 pub dynamic: bool,
209 #[prop_or_default]
212 pub icon: HelperTextItemIcon,
213 #[prop_or_default]
217 pub id: Option<AttrValue>,
218 #[prop_or_default]
221 pub screen_reader_text: AttrValue,
222 #[prop_or_default]
224 pub variant: HelperTextItemVariant,
225}
226
227#[function_component(HelperTextItem)]
237pub fn helper_text_item(props: &HelperTextItemProperties) -> Html {
238 let mut class = classes!("pf-v6-c-helper-text__item", props.class.clone());
239 if props.dynamic {
240 class.push(classes!("pf-m-dynamic"));
241 }
242 class.extend_from(&props.variant);
243 let component = props.component.to_string();
244 let icon = match (props.icon, &props.dynamic) {
245 (HelperTextItemIcon::Default, false) | (HelperTextItemIcon::Hidden, ..) => None,
246 (HelperTextItemIcon::Default, true) | (HelperTextItemIcon::Visible, ..) => {
247 Some(props.variant.icon())
248 }
249 (HelperTextItemIcon::Custom(icon), ..) => Some(icon),
250 };
251
252 let item_icon =
253 icon.map(|icon| html!(<span class="pf-v6-c-helper-text__item-icon">{ icon }</span>));
254
255 let screen_reader = use_memo(
256 (props.screen_reader_text.clone(), props.dynamic),
257 |(text, _)| {
259 if !text.is_empty() {
260 if props.dynamic {
261 Some(html!(<span class="pf-v6-u-screen-reader">{ ": " }{ text }{ ";" }</span>))
262 } else {
263 warn!(
264 "The screen_reader_text attribute was set but has not been used as the \
265 dynamic attribute was not set to true."
266 );
267 None
268 }
269 } else {
270 None
271 }
272 },
273 );
274
275 html!(
276 <@{component} id={&props.id} {class}>
277 { item_icon }
278 <div class="pf-v6-c-helper-text__item-text">
279 { props.children.clone() }
280 { (*screen_reader).clone() }
281 </div>
282 </@>
283 )
284}