1use gpui::{ClickEvent, ElementId};
2
3use crate::prelude::*;
4
5#[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}