patternfly_yew/components/form/
radio.rs

1use crate::ouia;
2use crate::prelude::OuiaComponentType;
3use crate::utils::OuiaSafe;
4use crate::{prelude::use_prop_id, utils::Ouia};
5use std::mem::swap;
6use yew::prelude::*;
7
8const OUIA: Ouia = ouia!("Radio");
9
10/// Properties for [`Radio`].
11#[derive(PartialEq, Properties)]
12pub struct RadioProperties {
13    /// Additional classes added to the radio button.
14    #[prop_or_default]
15    pub class: Classes,
16
17    /// Additional classes added to the underlying input tag.
18    #[prop_or_default]
19    pub input_class: Classes,
20
21    #[prop_or_default]
22    pub id: Option<String>,
23
24    /// The input control name, used to group radio buttons together.
25    #[prop_or_default]
26    pub name: Option<AttrValue>,
27
28    #[prop_or_default]
29    pub value: Option<AttrValue>,
30
31    /// The radio button label content.
32    #[prop_or_default]
33    pub children: Children,
34
35    #[prop_or_default]
36    pub checked: bool,
37
38    #[prop_or_default]
39    pub reversed: bool,
40
41    #[prop_or_default]
42    pub disabled: bool,
43
44    /// A longer description text.
45    #[prop_or_default]
46    pub description: Option<Html>,
47
48    /// Additional content aligned with the label.
49    #[prop_or_default]
50    pub body: Option<Html>,
51
52    /// Event fired when the radio button is checked (but not when unchecked).
53    #[prop_or_default]
54    pub onchange: Callback<()>,
55
56    /// Event fired when any part of the input is clicked. If you only want something to happen
57    /// when the radio button itself is clicked then use `onchange`.
58    #[prop_or_default]
59    pub input_onclick: Option<Callback<MouseEvent>>,
60
61    /// Creates a non-standalone input with a label, even if there are no children to this radio.
62    #[prop_or_default]
63    pub force_label: bool,
64
65    /// OUIA Component id
66    #[prop_or_default]
67    pub ouia_id: Option<String>,
68    /// OUIA Component Type
69    #[prop_or(OUIA.component_type())]
70    pub ouia_type: OuiaComponentType,
71    /// OUIA Component Safe
72    #[prop_or(OuiaSafe::TRUE)]
73    pub ouia_safe: OuiaSafe,
74}
75
76/// Radio button component
77///
78/// > A **radio** button is used to present the user with mutually exclusive choices. Always present radio buttons in groups of 2 or more.
79///
80/// See: <https://www.patternfly.org/components/forms/radio>
81///
82/// ## Properties
83///
84/// Defined by [`RadioProperties`].
85#[function_component(Radio)]
86pub fn radio(props: &RadioProperties) -> Html {
87    let ouia_id = use_memo(props.ouia_id.clone(), |id| {
88        id.clone().unwrap_or(OUIA.generated_id())
89    });
90    let class = classes!("pf-v5-c-radio", props.class.clone());
91
92    let id = use_prop_id(props.id.clone());
93
94    let mut input_class = classes!(props.input_class.clone(), "pf-v5-c-radio__input");
95
96    if props.children.is_empty() && !props.force_label {
97        input_class.extend(classes!("pf-m-standalone"));
98    }
99
100    let onchange = use_callback(props.onchange.clone(), |_, onchange| {
101        onchange.emit(());
102    });
103
104    let mut first = html!(
105        <input
106            class={input_class}
107            type="radio"
108            id={(*id).clone()}
109            name={&props.name}
110            checked={props.checked}
111            disabled={props.disabled}
112            value={&props.value}
113            {onchange}
114            onclick={props.input_onclick.clone()}
115            data-ouia-component-id={(*ouia_id).clone()}
116            data-ouia-component-type={props.ouia_type}
117            data-ouia-safe={props.ouia_safe}
118        />
119    );
120
121    let mut label_class = classes!("pf-v5-c-radio__label");
122    if props.disabled {
123        label_class.extend(classes!("pf-m-disabled"));
124    }
125
126    let mut second = html!(
127        <>
128            if !props.children.is_empty() || props.force_label {
129                <label
130                    class={label_class}
131                    for={(*id).clone()}
132                >
133                    { props.children.clone() }
134                </label>
135            }
136        </>
137    );
138
139    if props.reversed {
140        swap(&mut first, &mut second);
141    }
142
143    html!(
144        <div {class}>
145            {first} {second}
146
147            if let Some(description) = &props.description {
148                <span class="pf-v5-c-radio__description">
149                    { description.clone() }
150                </span>
151            }
152
153            if  let Some(body) = &props.body {
154                <span class="pf-v5-c-radio__body">
155                    { body.clone() }
156                </span>
157            }
158        </div>
159    )
160}