1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
use *;
use BaseRadio;
use component_doc;
use inject_style;
use crateradio_group_styles;
/// Radio marks one choice within a [`RadioGroup`](crate::RadioGroup) where only a single
/// option can be selected — deployment tier, billing plan, or permission level.
///
/// **Radio + RadioGroup pair:** always bind selection on [`RadioGroup`](crate::RadioGroup) (`Option<String>`).
/// Each [`Radio`](crate::Radio) supplies a `value` and `label`. Preview slug `radio` documents both components.
///
/// Give each `Radio` a distinct `value` and `label`. Wrap the group in [`Field`](crate::Field) when the set requires a visible heading or validation.
///
/// # When to use
///
/// - Mutually exclusive choices (plan tier, shipping method, environment) - Short option lists where every choice should remain visible - Form fields where exactly one value is required
///
/// # API notes
///
/// - Bind selection on [`RadioGroup`](crate::RadioGroup) with `Option<String>` — `None` means no selection yet.
/// - Set [`RadioGroupLayout::Horizontal`] on [`RadioGroup`](crate::RadioGroup) for compact horizontal rows.
/// - Each [`Radio`](crate::Radio) supplies a distinct `value` and visible `label`.
///
/// # Usage
///
/// 1. Create an `Option<String>` signal and pass it to [`RadioGroup`](crate::RadioGroup) via `bind`.
/// 2. Add one [`Radio`](crate::Radio) per option with a unique `value` and visible `label`.
/// 3. Wrap in [`Field`](crate::Field) when the group needs a field label or validation messaging.
/// 4. Wrap preview examples in a native element with `data-testid` for E2E selectors.
///
/// # Best Practices
///
/// ## Do's
///
/// * Always use [`RadioGroup`](crate::RadioGroup) as the parent container * Provide a visible `label` on each option for accessible naming * Use `Field` + `RadioGroupRule::required` when a selection is mandatory
///
/// ## Don'ts
///
/// * Do not use for multi-select — prefer [`Checkbox`](crate::Checkbox) * Do not put `data-testid` on the component — wrap with a native element
///
/// # Examples
///
/// ## Default selected
/// One option starts checked when the bound signal holds its value.
/// <!-- preview -->
/// ```rust
/// use crate::{Radio, RadioGroup};
/// let choice = RwSignal::new(Some("a".to_string()));
/// view! {
/// <div data-testid="radio-preview">
/// <div data-testid="radio-group-preview">
/// <RadioGroup bind=choice>
/// <Radio value="a" label="Option A" />
/// <Radio value="b" label="Option B" />
/// </RadioGroup>
/// </div>
/// </div>
/// }
/// ```
///
/// ## Starts unselected
/// No option is checked when the bound signal is `None`.
/// <!-- preview -->
/// ```rust
/// use crate::{Radio, RadioGroup};
/// let choice = RwSignal::new(None::<String>);
/// view! {
/// <div data-testid="radio-group-unselected">
/// <RadioGroup bind=choice>
/// <Radio value="one" label="One" />
/// <Radio value="two" label="Two" />
/// </RadioGroup>
/// </div>
/// }
/// ```
///
/// ## Click changes selection
/// Clicking another option moves the checked state within the group.
/// <!-- preview -->
/// ```rust
/// use crate::{Radio, RadioGroup};
/// let choice = RwSignal::new(Some("cat".to_string()));
/// view! {
/// <div data-testid="radio-group-click">
/// <RadioGroup bind=choice>
/// <Radio value="cat" label="Cat" />
/// <Radio value="dog" label="Dog" />
/// </RadioGroup>
/// </div>
/// }
/// ```
///
/// ## Field wrapper integration
/// [`Field`](crate::Field) supplies the group label; each [`Radio`](crate::Radio) keeps its option label.
/// <!-- preview -->
/// ```rust
/// use crate::{Field, Radio, RadioGroup};
/// let choice = RwSignal::new(Some("daily".to_string()));
/// view! {
/// <div data-testid="radio-group-field">
/// <Field label="Digest frequency" name="frequency">
/// <RadioGroup bind=choice>
/// <Radio value="daily" label="Daily" />
/// <Radio value="weekly" label="Weekly" />
/// </RadioGroup>
/// </Field>
/// </div>
/// }
/// ```
///
/// ## Disabled fieldset
/// A disabled fieldset prevents changing the current selection.
/// <!-- preview -->
/// ```rust
/// use crate::{Radio, RadioGroup};
/// let choice = RwSignal::new(Some("stable".to_string()));
/// view! {
/// <div data-testid="radio-group-disabled">
/// <fieldset disabled>
/// <RadioGroup bind=choice>
/// <Radio value="stable" label="Stable" />
/// <Radio value="beta" label="Beta" />
/// </RadioGroup>
/// </fieldset>
/// </div>
/// }
/// ```
///
/// ## Horizontal layout
/// Compact row of options using [`RadioGroupLayout::Horizontal`].
/// <!-- preview -->
/// ```rust
/// use crate::{Radio, RadioGroup, RadioGroupLayout};
/// let choice = RwSignal::new(Some("a".to_string()));
/// view! {
/// <div data-testid="radio-group-horizontal">
/// <RadioGroup bind=choice layout=RadioGroupLayout::Horizontal>
/// <Radio value="a" label="Option A" />
/// <Radio value="b" label="Option B" />
/// <Radio value="c" label="Option C" />
/// </RadioGroup>
/// </div>
/// }
/// ```
///
/// ## Required validation
/// Required rule on the group; arrow keys move selection between options.
/// <!-- preview -->
/// ```rust
/// use crate::{Field, Radio, RadioGroup, RadioGroupBind, RadioGroupRule};
/// let choice = RwSignal::new(None::<String>);
/// let required = Signal::from(true);
/// view! {
/// <div data-testid="radio-group-required">
/// <Field label="Environment" name="environment" required=true>
/// <RadioGroup
/// bind=RadioGroupBind {
/// value: choice.into(),
/// rules: vec![RadioGroupRule::required(required)],
/// ..Default::default()
/// }
/// >
/// <Radio value="dev" label="Development" />
/// <Radio value="prod" label="Production" />
/// </RadioGroup>
/// </Field>
/// </div>
/// }
/// ```