Skip to main content

dioxus_element_plug/components/
radio.rs

1use dioxus::prelude::*;
2
3/// Radio size variants
4#[derive(Clone, PartialEq)]
5pub enum RadioSize {
6    Large,
7    Default,
8    Small,
9}
10
11impl RadioSize {
12    pub fn as_class(&self) -> &'static str {
13        match self {
14            RadioSize::Large => "el-radio--large",
15            RadioSize::Default => "",
16            RadioSize::Small => "el-radio--small",
17        }
18    }
19}
20
21/// Radio props
22#[derive(Props, Clone, PartialEq)]
23pub struct RadioProps {
24    /// Radio content
25    #[props(default)]
26    pub children: Option<Element>,
27
28    /// Label text
29    #[props(default)]
30    pub label: Option<String>,
31
32    /// Radio value
33    pub value: String,
34
35    /// Whether this radio is checked
36    #[props(default = false)]
37    pub model_value: bool,
38
39    /// Whether the radio is disabled
40    #[props(default = false)]
41    pub disabled: bool,
42
43    /// Whether to show a border
44    #[props(default = false)]
45    pub border: bool,
46
47    /// Radio size
48    #[props(default = RadioSize::Default)]
49    pub size: RadioSize,
50
51    /// Radio name attribute
52    #[props(default)]
53    pub name: Option<String>,
54
55    /// Change event handler
56    #[props(default)]
57    pub on_change: Option<EventHandler<String>>,
58
59    /// Additional CSS classes
60    #[props(default)]
61    pub class: Option<String>,
62
63    /// Inline styles
64    #[props(default)]
65    pub style: Option<String>,
66}
67
68/// Radio component for selecting a single option
69///
70/// ## Example
71///
72/// ```rust,ignore
73/// rsx! {
74///     Radio { value: "1".to_string(), label: Some("Option 1".to_string()), model_value: true }
75/// }
76/// ```
77#[component]
78pub fn Radio(props: RadioProps) -> Element {
79    let mut class_names = vec!["el-radio".to_string()];
80
81    let size_class = props.size.as_class();
82    if !size_class.is_empty() {
83        class_names.push(size_class.to_string());
84    }
85
86    if props.model_value {
87        class_names.push("is-checked".to_string());
88    }
89
90    if props.disabled {
91        class_names.push("is-disabled".to_string());
92    }
93
94    if props.border {
95        class_names.push("is-bordered".to_string());
96    }
97
98    if let Some(ref custom_class) = props.class {
99        class_names.push(custom_class.clone());
100    }
101
102    let class_string = class_names.join(" ");
103    let style_string = props.style.clone().unwrap_or_default();
104    let value_clone1 = props.value.clone();
105    let value_clone2 = props.value.clone();
106
107    rsx! {
108        label {
109            class: "{class_string}",
110            style: "{style_string}",
111            role: "radio",
112            aria_checked: "{props.model_value}",
113            span {
114                class: "el-radio__input",
115                span {
116                    class: "el-radio__inner",
117                }
118                input {
119                    r#type: "radio",
120                    class: "el-radio__original",
121                    name: props.name.clone().unwrap_or_default(),
122                    value: "{props.value}",
123                    checked: props.model_value,
124                    onchange: move |_| {
125                        if !props.disabled {
126                            if let Some(handler) = props.on_change {
127                                handler.call(value_clone1.clone());
128                            }
129                        }
130                    },
131                }
132            }
133            span {
134                class: "el-radio__label",
135                onclick: move |_| {
136                    if !props.disabled {
137                        if let Some(handler) = props.on_change {
138                            handler.call(value_clone2.clone());
139                        }
140                    }
141                },
142                if let Some(ref label) = props.label {
143                    "{label}"
144                }
145                {props.children}
146            }
147        }
148    }
149}
150
151/// RadioGroup props
152#[derive(Props, Clone, PartialEq)]
153pub struct RadioGroupProps {
154    /// Radio group content
155    #[props(default)]
156    pub children: Element,
157
158    /// Whether the group is disabled
159    #[props(default = false)]
160    pub disabled: bool,
161
162    /// Group size
163    #[props(default = RadioSize::Default)]
164    pub size: RadioSize,
165
166    /// Text color for checked radio
167    #[props(default)]
168    pub text_color: Option<String>,
169
170    /// Fill color for checked radio
171    #[props(default)]
172    pub fill: Option<String>,
173
174    /// Group name attribute
175    #[props(default)]
176    pub name: Option<String>,
177
178    /// Change event handler
179    #[props(default)]
180    pub on_change: Option<EventHandler<String>>,
181
182    /// Additional CSS classes
183    #[props(default)]
184    pub class: Option<String>,
185
186    /// Inline styles
187    #[props(default)]
188    pub style: Option<String>,
189}
190
191/// RadioGroup component for grouping radio buttons
192#[component]
193pub fn RadioGroup(props: RadioGroupProps) -> Element {
194    let mut class_names = vec!["el-radio-group".to_string()];
195
196    if props.disabled {
197        class_names.push("is-disabled".to_string());
198    }
199
200    if let Some(ref custom_class) = props.class {
201        class_names.push(custom_class.clone());
202    }
203
204    let class_string = class_names.join(" ");
205    let style_string = props.style.clone().unwrap_or_default();
206
207    rsx! {
208        div {
209            class: "{class_string}",
210            style: "{style_string}",
211            role: "radiogroup",
212            {props.children}
213        }
214    }
215}
216
217/// RadioButton props (radio styled as a button)
218#[derive(Props, Clone, PartialEq)]
219pub struct RadioButtonProps {
220    /// Radio button content
221    #[props(default)]
222    pub children: Option<Element>,
223
224    /// Label text
225    #[props(default)]
226    pub label: Option<String>,
227
228    /// Radio value
229    pub value: String,
230
231    /// Whether this radio is checked
232    #[props(default = false)]
233    pub model_value: bool,
234
235    /// Whether disabled
236    #[props(default = false)]
237    pub disabled: bool,
238
239    /// Change event handler
240    #[props(default)]
241    pub on_change: Option<EventHandler<String>>,
242
243    /// Additional CSS classes
244    #[props(default)]
245    pub class: Option<String>,
246
247    /// Inline styles
248    #[props(default)]
249    pub style: Option<String>,
250}
251
252/// RadioButton component - radio styled as a button
253#[component]
254pub fn RadioButton(props: RadioButtonProps) -> Element {
255    let mut class_names = vec!["el-radio-button".to_string()];
256
257    if props.model_value {
258        class_names.push("is-checked".to_string());
259    }
260
261    if props.disabled {
262        class_names.push("is-disabled".to_string());
263    }
264
265    if let Some(ref custom_class) = props.class {
266        class_names.push(custom_class.clone());
267    }
268
269    let class_string = class_names.join(" ");
270    let style_string = props.style.clone().unwrap_or_default();
271    let value_clone = props.value.clone();
272
273    rsx! {
274        label {
275            class: "{class_string}",
276            style: "{style_string}",
277            span {
278                class: "el-radio-button__inner",
279                role: "radio",
280                aria_checked: "{props.model_value}",
281                onclick: move |_| {
282                    if !props.disabled {
283                        if let Some(handler) = props.on_change {
284                            handler.call(value_clone.clone());
285                        }
286                    }
287                },
288                if let Some(ref label) = props.label {
289                    "{label}"
290                }
291                {props.children}
292            }
293        }
294    }
295}