Skip to main content

ui/components/
radio.rs

1use gpui::{ClickEvent, ElementId};
2
3use crate::prelude::*;
4
5/// A single radio button. Group exclusivity is the caller's responsibility:
6/// the parent holds the selected value and passes `checked` to each button.
7#[derive(IntoElement, RegisterComponent)]
8pub struct RadioButton {
9    id: ElementId,
10    label: Option<SharedString>,
11    checked: bool,
12    disabled: bool,
13    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
14}
15
16impl RadioButton {
17    pub fn new(id: impl Into<ElementId>) -> Self {
18        Self {
19            id: id.into(),
20            label: None,
21            checked: false,
22            disabled: false,
23            on_click: None,
24        }
25    }
26
27    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
28        self.label = Some(label.into());
29        self
30    }
31
32    pub fn checked(mut self, checked: bool) -> Self {
33        self.checked = checked;
34        self
35    }
36
37    pub fn disabled(mut self, disabled: bool) -> Self {
38        self.disabled = disabled;
39        self
40    }
41
42    pub fn on_click(
43        mut self,
44        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
45    ) -> Self {
46        self.on_click = Some(Box::new(handler));
47        self
48    }
49}
50
51impl RenderOnce for RadioButton {
52    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
53        let ring = if self.checked {
54            palette::primary(600)
55        } else {
56            semantic::border(cx)
57        };
58
59        let control = div()
60            .size_4()
61            .rounded_full()
62            .border_1()
63            .border_color(ring)
64            .flex()
65            .items_center()
66            .justify_center()
67            .when(self.checked, |this| {
68                this.child(div().size_1p5().rounded_full().bg(palette::primary(600)))
69            });
70
71        h_flex()
72            .id(self.id)
73            .items_center()
74            .gap_2()
75            .when(!self.disabled, |this| this.cursor_pointer())
76            .when(self.disabled, |this| this.opacity(0.5))
77            .child(control)
78            .when_some(self.label, |this, label| {
79                this.child(Label::new(label).size(LabelSize::Small))
80            })
81            .when_some(self.on_click.filter(|_| !self.disabled), |this, handler| {
82                this.on_click(handler)
83            })
84    }
85}
86
87impl Component for RadioButton {
88    fn scope() -> ComponentScope {
89        ComponentScope::Input
90    }
91
92    fn description() -> Option<&'static str> {
93        Some("A single radio button; group exclusivity is caller-managed.")
94    }
95
96    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
97        Some(
98            v_flex()
99                .gap_6()
100                .child(
101                    v_flex()
102                        .gap_2()
103                        .child(
104                            Label::new("States")
105                                .size(LabelSize::Small)
106                                .color(Color::Muted),
107                        )
108                        .child(RadioButton::new("radio-a").label("Selected").checked(true))
109                        .child(RadioButton::new("radio-b").label("Unselected"))
110                        .child(
111                            RadioButton::new("radio-c")
112                                .label("Disabled, unselected")
113                                .disabled(true),
114                        )
115                        .child(
116                            RadioButton::new("radio-d")
117                                .label("Disabled, selected")
118                                .checked(true)
119                                .disabled(true),
120                        ),
121                )
122                .child(
123                    v_flex()
124                        .gap_2()
125                        .child(
126                            Label::new("Shipping method")
127                                .size(LabelSize::Small)
128                                .color(Color::Muted),
129                        )
130                        .child(
131                            RadioButton::new("ship-standard")
132                                .label("Standard (5-7 days)")
133                                .checked(true),
134                        )
135                        .child(RadioButton::new("ship-express").label("Express (2-3 days)"))
136                        .child(RadioButton::new("ship-overnight").label("Overnight")),
137                )
138                .into_any_element(),
139        )
140    }
141}