@ceed/ads 1.23.3 โ 1.23.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/data-display/Badge.md +71 -39
- package/dist/components/data-display/InfoSign.md +74 -98
- package/dist/components/data-display/Typography.md +310 -61
- package/dist/components/feedback/CircularProgress.md +257 -0
- package/dist/components/feedback/Skeleton.md +280 -0
- package/dist/components/feedback/llms.txt +2 -0
- package/dist/components/inputs/ButtonGroup.md +115 -106
- package/dist/components/inputs/Calendar.md +98 -459
- package/dist/components/inputs/CurrencyInput.md +181 -8
- package/dist/components/inputs/DatePicker.md +108 -436
- package/dist/components/inputs/DateRangePicker.md +130 -496
- package/dist/components/inputs/FilterMenu.md +169 -19
- package/dist/components/inputs/FilterableCheckboxGroup.md +119 -24
- package/dist/components/inputs/FormControl.md +361 -0
- package/dist/components/inputs/IconButton.md +137 -88
- package/dist/components/inputs/MonthPicker.md +95 -427
- package/dist/components/inputs/MonthRangePicker.md +89 -471
- package/dist/components/inputs/PercentageInput.md +183 -19
- package/dist/components/inputs/RadioButton.md +163 -35
- package/dist/components/inputs/RadioList.md +241 -0
- package/dist/components/inputs/RadioTileGroup.md +146 -62
- package/dist/components/inputs/Select.md +219 -328
- package/dist/components/inputs/Slider.md +334 -0
- package/dist/components/inputs/Switch.md +136 -376
- package/dist/components/inputs/Textarea.md +209 -11
- package/dist/components/inputs/Uploader/Uploader.md +145 -66
- package/dist/components/inputs/llms.txt +3 -0
- package/dist/components/navigation/Breadcrumbs.md +80 -322
- package/dist/components/navigation/Dropdown.md +92 -221
- package/dist/components/navigation/IconMenuButton.md +40 -502
- package/dist/components/navigation/InsetDrawer.md +68 -738
- package/dist/components/navigation/Link.md +39 -298
- package/dist/components/navigation/Menu.md +92 -285
- package/dist/components/navigation/MenuButton.md +55 -448
- package/dist/components/navigation/Pagination.md +47 -338
- package/dist/components/navigation/ProfileMenu.md +45 -268
- package/dist/components/navigation/Stepper.md +160 -28
- package/dist/components/navigation/Tabs.md +57 -316
- package/dist/components/surfaces/Sheet.md +150 -333
- package/dist/guides/ThemeProvider.md +116 -0
- package/dist/guides/llms.txt +9 -0
- package/dist/llms.txt +8 -0
- package/package.json +1 -1
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
PercentageInput is a specialized numeric input for entering percentage values. It automatically appends a `%` suffix, formats numbers with thousand separators, and supports min/max validation with built-in error states. Use it for any field where users need to enter a percentage, such as tax rates, discounts, or commission rates.
|
|
6
|
+
|
|
7
|
+
> **Tip: Use built-in form props**
|
|
6
8
|
>
|
|
7
|
-
>
|
|
8
|
-
> Form์ ๊ตฌ์ฑํ ๋ Typography๋ก ๋ณ๋์ label์ด๋ helperText๋ฅผ ๋ง๋๋ ๋์ , ์ปดํฌ๋ํธ์ ๋ด์ฅ prop์ ์ฌ์ฉํ์ธ์.
|
|
9
|
+
> This component supports `label` and `helperText` props directly. Use these instead of wrapping with FormControl + FormLabel + FormHelperText for simpler forms.
|
|
9
10
|
|
|
10
11
|
```tsx
|
|
11
12
|
<PercentageInput placeholder="Enter a percentage" />
|
|
@@ -27,7 +28,29 @@
|
|
|
27
28
|
| min | โ | โ |
|
|
28
29
|
| max | โ | โ |
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { PercentageInput } from '@ceed/ads';
|
|
35
|
+
|
|
36
|
+
function MyComponent() {
|
|
37
|
+
const [rate, setRate] = React.useState(0);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<PercentageInput
|
|
41
|
+
label="Tax Rate"
|
|
42
|
+
value={rate}
|
|
43
|
+
onChange={(e) => setRate(e.target.value)}
|
|
44
|
+
min={0}
|
|
45
|
+
max={100}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Sizes
|
|
52
|
+
|
|
53
|
+
PercentageInput supports three sizes: `sm`, `md` (default), and `lg`.
|
|
31
54
|
|
|
32
55
|
```tsx
|
|
33
56
|
<Stack gap={2}>
|
|
@@ -37,44 +60,45 @@
|
|
|
37
60
|
</Stack>
|
|
38
61
|
```
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
## Form Features
|
|
64
|
+
|
|
65
|
+
### With Label
|
|
41
66
|
|
|
42
67
|
```tsx
|
|
43
68
|
<PercentageInput
|
|
44
69
|
placeholder="Enter a percentage"
|
|
45
|
-
|
|
70
|
+
label="Percentage Input"
|
|
46
71
|
/>
|
|
47
72
|
```
|
|
48
73
|
|
|
49
|
-
### Error
|
|
50
|
-
|
|
51
74
|
```tsx
|
|
52
|
-
<PercentageInput
|
|
53
|
-
placeholder="Enter a percentage"
|
|
54
|
-
error
|
|
55
|
-
/>
|
|
75
|
+
<PercentageInput label="Discount Rate" />
|
|
56
76
|
```
|
|
57
77
|
|
|
58
|
-
###
|
|
78
|
+
### With Helper Text
|
|
59
79
|
|
|
60
80
|
```tsx
|
|
61
81
|
<PercentageInput
|
|
62
82
|
placeholder="Enter a percentage"
|
|
63
83
|
label="Percentage Input"
|
|
84
|
+
helperText="Please enter a percentage"
|
|
64
85
|
/>
|
|
65
86
|
```
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
```tsx
|
|
89
|
+
<PercentageInput label="Commission" helperText="Enter the commission percentage." />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Error State
|
|
68
93
|
|
|
69
94
|
```tsx
|
|
70
95
|
<PercentageInput
|
|
71
96
|
placeholder="Enter a percentage"
|
|
72
|
-
|
|
73
|
-
helperText="Please enter a percentage"
|
|
97
|
+
error
|
|
74
98
|
/>
|
|
75
99
|
```
|
|
76
100
|
|
|
77
|
-
###
|
|
101
|
+
### With Error Text
|
|
78
102
|
|
|
79
103
|
```tsx
|
|
80
104
|
<PercentageInput
|
|
@@ -96,7 +120,20 @@
|
|
|
96
120
|
/>
|
|
97
121
|
```
|
|
98
122
|
|
|
99
|
-
###
|
|
123
|
+
### Disabled
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<PercentageInput
|
|
127
|
+
placeholder="Enter a percentage"
|
|
128
|
+
disabled
|
|
129
|
+
/>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Min / Max Validation
|
|
133
|
+
|
|
134
|
+
Set `min` and `max` to enforce value boundaries. When the entered value is outside the range, the input automatically shows an error state.
|
|
135
|
+
|
|
136
|
+
### Minimum Value
|
|
100
137
|
|
|
101
138
|
```tsx
|
|
102
139
|
<PercentageInput
|
|
@@ -108,7 +145,12 @@
|
|
|
108
145
|
/>
|
|
109
146
|
```
|
|
110
147
|
|
|
111
|
-
|
|
148
|
+
```tsx
|
|
149
|
+
<PercentageInput min={10} defaultValue={5} />
|
|
150
|
+
{/* Shows error because 5 < 10 */}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Maximum Value
|
|
112
154
|
|
|
113
155
|
```tsx
|
|
114
156
|
<PercentageInput
|
|
@@ -119,3 +161,125 @@
|
|
|
119
161
|
defaultValue={5000}
|
|
120
162
|
/>
|
|
121
163
|
```
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<PercentageInput max={500} defaultValue={5000} />
|
|
167
|
+
{/* Shows error because 5000 > 500 */}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Decimal Scale
|
|
171
|
+
|
|
172
|
+
Use `maxDecimalScale` to control the number of decimal places allowed. The default is `0` (integers only).
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// Integer only (default)
|
|
176
|
+
<PercentageInput maxDecimalScale={0} />
|
|
177
|
+
|
|
178
|
+
// Up to 2 decimal places: 12.34%
|
|
179
|
+
<PercentageInput maxDecimalScale={2} />
|
|
180
|
+
|
|
181
|
+
// Up to 3 decimal places: 12.345%
|
|
182
|
+
<PercentageInput maxDecimalScale={3} />
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Minor Unit Mode
|
|
186
|
+
|
|
187
|
+
When `useMinorUnit` is `true`, the component converts between display values and minor units based on `maxDecimalScale`. This is useful when your API stores percentages in integer form.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
// Display shows "20.12%", onChange returns 20120
|
|
191
|
+
<PercentageInput useMinorUnit maxDecimalScale={3} value={20120} />
|
|
192
|
+
|
|
193
|
+
// Display shows "20.12%", onChange returns 20.12
|
|
194
|
+
<PercentageInput useMinorUnit={false} maxDecimalScale={2} value={20.12} />
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Common Use Cases
|
|
198
|
+
|
|
199
|
+
### Discount Configuration
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
function DiscountSetting() {
|
|
203
|
+
const [discount, setDiscount] = React.useState(0);
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<PercentageInput
|
|
207
|
+
label="Discount"
|
|
208
|
+
helperText="Enter 0-100%"
|
|
209
|
+
value={discount}
|
|
210
|
+
onChange={(e) => setDiscount(e.target.value)}
|
|
211
|
+
min={0}
|
|
212
|
+
max={100}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Tax Rate Settings
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
function TaxRateForm() {
|
|
222
|
+
return (
|
|
223
|
+
<Stack gap={2}>
|
|
224
|
+
<PercentageInput
|
|
225
|
+
label="VAT Rate"
|
|
226
|
+
defaultValue={10}
|
|
227
|
+
maxDecimalScale={2}
|
|
228
|
+
min={0}
|
|
229
|
+
max={100}
|
|
230
|
+
/>
|
|
231
|
+
<PercentageInput
|
|
232
|
+
label="Local Tax Rate"
|
|
233
|
+
defaultValue={2.5}
|
|
234
|
+
maxDecimalScale={2}
|
|
235
|
+
min={0}
|
|
236
|
+
max={50}
|
|
237
|
+
/>
|
|
238
|
+
</Stack>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Progress / Completion Tracker
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
function CompletionTracker({ value, onChange }) {
|
|
247
|
+
return (
|
|
248
|
+
<PercentageInput
|
|
249
|
+
label="Completion"
|
|
250
|
+
value={value}
|
|
251
|
+
onChange={(e) => onChange(e.target.value)}
|
|
252
|
+
min={0}
|
|
253
|
+
max={100}
|
|
254
|
+
helperText={value >= 100 ? 'Task is complete!' : `${100 - value}% remaining`}
|
|
255
|
+
/>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Best Practices
|
|
261
|
+
|
|
262
|
+
1. **Always set min and max**: Bound the valid range to prevent unreasonable values.
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// โ
Clear bounds
|
|
266
|
+
<PercentageInput min={0} max={100} />
|
|
267
|
+
|
|
268
|
+
// โ Unbounded โ user could enter 99999%
|
|
269
|
+
<PercentageInput />
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
2. **Use `maxDecimalScale` appropriately**: Set it to `0` for whole percentages (discounts, progress), or `2`โ`3` for precise rates (tax, interest).
|
|
273
|
+
|
|
274
|
+
3. **Use `useMinorUnit` for API compatibility**: When your backend stores percentages as integers (e.g., 1050 for 10.50%), enable `useMinorUnit` with the appropriate `maxDecimalScale`.
|
|
275
|
+
|
|
276
|
+
4. **Provide context via labels and helper text**: Clarify what the percentage represents and what range is valid.
|
|
277
|
+
|
|
278
|
+
5. **Handle edge cases**: Consider what happens at boundary values (0%, 100%) and negative percentages if allowed.
|
|
279
|
+
|
|
280
|
+
## Accessibility
|
|
281
|
+
|
|
282
|
+
- The input has proper labeling via the `label` prop, linked with `aria-labelledby`.
|
|
283
|
+
- Error states trigger `aria-invalid` with associated error messages via `aria-describedby`.
|
|
284
|
+
- The `%` suffix is a visual decoration โ ensure labels clarify that the field expects a percentage for screen readers.
|
|
285
|
+
- Min/max validation errors are communicated visually; add descriptive `helperText` to make the valid range clear.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Introduction
|
|
4
4
|
|
|
5
|
-
Radio
|
|
5
|
+
The Radio component provides a single-selection interface where users can choose exactly one option from a set of mutually exclusive choices. When one radio button is selected, any previously selected option in the same group is automatically deselected.
|
|
6
|
+
|
|
7
|
+
Radio buttons are built on Joy UI's Radio and RadioGroup components, supporting multiple visual variants, sizes, colors, and advanced customization such as custom icons, overlay interactions, and icon-free designs. They are ideal for settings forms, preference panels, and any UI where a user must commit to a single option from a predefined list.
|
|
6
8
|
|
|
7
9
|
```tsx
|
|
8
10
|
<Radio />
|
|
@@ -28,35 +30,28 @@ Radio ์ปดํฌ๋ํธ๋ ์ฌ์ฉ์๊ฐ ์ฌ๋ฌ ์ต์
์ค ํ๋๋ง ์ ํํ ์ ์
|
|
|
28
30
|
## Usage
|
|
29
31
|
|
|
30
32
|
```tsx
|
|
31
|
-
import { Radio } from '@ceed/ads';
|
|
33
|
+
import { Radio, RadioGroup } from '@ceed/ads';
|
|
32
34
|
|
|
33
35
|
function MyComponent() {
|
|
34
36
|
const [selectedValue, setSelectedValue] = useState('option1');
|
|
35
37
|
|
|
36
38
|
return (
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/>
|
|
45
|
-
|
|
46
|
-
name="example"
|
|
47
|
-
value="option2"
|
|
48
|
-
checked={selectedValue === 'option2'}
|
|
49
|
-
onChange={(e) => setSelectedValue(e.target.value)}
|
|
50
|
-
label="์ต์
2"
|
|
51
|
-
/>
|
|
52
|
-
</div>
|
|
39
|
+
<RadioGroup
|
|
40
|
+
name="example"
|
|
41
|
+
value={selectedValue}
|
|
42
|
+
onChange={(e) => setSelectedValue(e.target.value)}
|
|
43
|
+
>
|
|
44
|
+
<Radio value="option1" label="Option 1" />
|
|
45
|
+
<Radio value="option2" label="Option 2" />
|
|
46
|
+
<Radio value="option3" label="Option 3" />
|
|
47
|
+
</RadioGroup>
|
|
53
48
|
);
|
|
54
49
|
}
|
|
55
50
|
```
|
|
56
51
|
|
|
57
|
-
##
|
|
52
|
+
## Variants
|
|
58
53
|
|
|
59
|
-
|
|
54
|
+
Radio supports four visual variants: `outlined` (default), `soft`, `solid`, and `plain`. Choose a variant that fits the visual context of your form.
|
|
60
55
|
|
|
61
56
|
```tsx
|
|
62
57
|
<FormControl>
|
|
@@ -70,7 +65,9 @@ function MyComponent() {
|
|
|
70
65
|
</FormControl>
|
|
71
66
|
```
|
|
72
67
|
|
|
73
|
-
|
|
68
|
+
## Sizes
|
|
69
|
+
|
|
70
|
+
Three size presets are available: `sm`, `md` (default), and `lg`. Use smaller sizes for compact layouts and larger sizes for touch-friendly interfaces.
|
|
74
71
|
|
|
75
72
|
```tsx
|
|
76
73
|
<FormControl>
|
|
@@ -83,7 +80,9 @@ function MyComponent() {
|
|
|
83
80
|
</FormControl>
|
|
84
81
|
```
|
|
85
82
|
|
|
86
|
-
|
|
83
|
+
## Colors
|
|
84
|
+
|
|
85
|
+
Radio supports five semantic colors: `primary`, `neutral`, `danger`, `success`, and `warning`. Use semantic colors to convey meaning -- for example, `danger` for destructive options or `success` for confirmed selections.
|
|
87
86
|
|
|
88
87
|
```tsx
|
|
89
88
|
<FormControl>
|
|
@@ -98,9 +97,9 @@ function MyComponent() {
|
|
|
98
97
|
</FormControl>
|
|
99
98
|
```
|
|
100
99
|
|
|
101
|
-
|
|
100
|
+
## Custom Icons
|
|
102
101
|
|
|
103
|
-
|
|
102
|
+
Replace the default radio indicator with custom icons using the `checkedIcon` prop. This is useful for card-style selectors where the selection state needs a distinctive visual treatment.
|
|
104
103
|
|
|
105
104
|
```tsx
|
|
106
105
|
<RadioGroup aria-label="platform" defaultValue="Website" overlay name="platform" sx={{
|
|
@@ -142,9 +141,9 @@ function MyComponent() {
|
|
|
142
141
|
</RadioGroup>
|
|
143
142
|
```
|
|
144
143
|
|
|
145
|
-
|
|
144
|
+
## No Icon
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
Use the `disableIcon` prop to hide the radio indicator entirely. This pattern is commonly used for card-style selectors where the entire card acts as the selectable target, and the selection state is conveyed through border or background styling.
|
|
148
147
|
|
|
149
148
|
```tsx
|
|
150
149
|
<Box sx={{
|
|
@@ -195,9 +194,9 @@ Storage
|
|
|
195
194
|
</Box>
|
|
196
195
|
```
|
|
197
196
|
|
|
198
|
-
|
|
197
|
+
## Segmented Controls
|
|
199
198
|
|
|
200
|
-
|
|
199
|
+
Radio buttons can be styled as segmented controls for compact, inline option switching. This pattern works well for toolbar-style controls where horizontal space is limited.
|
|
201
200
|
|
|
202
201
|
```tsx
|
|
203
202
|
<Box sx={{
|
|
@@ -238,9 +237,9 @@ Storage
|
|
|
238
237
|
</Box>
|
|
239
238
|
```
|
|
240
239
|
|
|
241
|
-
|
|
240
|
+
## Product Attributes
|
|
242
241
|
|
|
243
|
-
|
|
242
|
+
Radio groups can be customized to represent product attributes such as colors and sizes. Using `overlay`, `disableIcon`, and custom `Sheet` containers, you can create rich visual selectors.
|
|
244
243
|
|
|
245
244
|
```tsx
|
|
246
245
|
<Box sx={{
|
|
@@ -337,14 +336,143 @@ Size
|
|
|
337
336
|
</Box>
|
|
338
337
|
```
|
|
339
338
|
|
|
339
|
+
## Common Use Cases
|
|
340
|
+
|
|
341
|
+
### Survey / Questionnaire Form
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
function SurveyQuestion() {
|
|
345
|
+
const [answer, setAnswer] = useState('');
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<FormControl>
|
|
349
|
+
<FormLabel>How satisfied are you with our service?</FormLabel>
|
|
350
|
+
<RadioGroup
|
|
351
|
+
name="satisfaction"
|
|
352
|
+
value={answer}
|
|
353
|
+
onChange={(e) => setAnswer(e.target.value)}
|
|
354
|
+
>
|
|
355
|
+
<Radio value="very-satisfied" label="Very Satisfied" color="success" />
|
|
356
|
+
<Radio value="satisfied" label="Satisfied" />
|
|
357
|
+
<Radio value="neutral" label="Neutral" />
|
|
358
|
+
<Radio value="dissatisfied" label="Dissatisfied" color="warning" />
|
|
359
|
+
<Radio value="very-dissatisfied" label="Very Dissatisfied" color="danger" />
|
|
360
|
+
</RadioGroup>
|
|
361
|
+
</FormControl>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Payment Method Selection
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
function PaymentMethodSelector() {
|
|
370
|
+
const [method, setMethod] = useState('credit-card');
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<FormControl>
|
|
374
|
+
<FormLabel>Payment Method</FormLabel>
|
|
375
|
+
<RadioGroup
|
|
376
|
+
name="payment"
|
|
377
|
+
value={method}
|
|
378
|
+
onChange={(e) => setMethod(e.target.value)}
|
|
379
|
+
sx={{ gap: 1.5 }}
|
|
380
|
+
>
|
|
381
|
+
{[
|
|
382
|
+
{ value: 'credit-card', label: 'Credit Card' },
|
|
383
|
+
{ value: 'bank-transfer', label: 'Bank Transfer' },
|
|
384
|
+
{ value: 'paypal', label: 'PayPal' },
|
|
385
|
+
].map((option) => (
|
|
386
|
+
<Sheet key={option.value} variant="outlined" sx={{ p: 2, borderRadius: 'md' }}>
|
|
387
|
+
<Radio
|
|
388
|
+
overlay
|
|
389
|
+
value={option.value}
|
|
390
|
+
label={option.label}
|
|
391
|
+
slotProps={{
|
|
392
|
+
action: ({ checked }) => ({
|
|
393
|
+
sx: {
|
|
394
|
+
...(checked && {
|
|
395
|
+
'--variant-borderWidth': '2px',
|
|
396
|
+
borderColor: 'primary.500',
|
|
397
|
+
}),
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
400
|
+
}}
|
|
401
|
+
/>
|
|
402
|
+
</Sheet>
|
|
403
|
+
))}
|
|
404
|
+
</RadioGroup>
|
|
405
|
+
</FormControl>
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Shipping Speed Selection
|
|
411
|
+
|
|
412
|
+
```tsx
|
|
413
|
+
function ShippingOptions() {
|
|
414
|
+
const [shipping, setShipping] = useState('standard');
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<FormControl>
|
|
418
|
+
<FormLabel>Shipping Speed</FormLabel>
|
|
419
|
+
<RadioGroup
|
|
420
|
+
name="shipping"
|
|
421
|
+
value={shipping}
|
|
422
|
+
onChange={(e) => setShipping(e.target.value)}
|
|
423
|
+
>
|
|
424
|
+
<Radio value="standard" label="Standard (5-7 business days) - Free" />
|
|
425
|
+
<Radio value="express" label="Express (2-3 business days) - $9.99" />
|
|
426
|
+
<Radio value="overnight" label="Overnight (next day) - $24.99" />
|
|
427
|
+
</RadioGroup>
|
|
428
|
+
</FormControl>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
340
433
|
## Best Practices
|
|
341
434
|
|
|
342
|
-
1.
|
|
435
|
+
1. **Always use RadioGroup**: Wrap Radio buttons in a `RadioGroup` to ensure proper grouping, keyboard navigation, and mutual exclusivity.
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
// โ
Wrapped in RadioGroup
|
|
439
|
+
<RadioGroup name="options" value={value} onChange={handleChange}>
|
|
440
|
+
<Radio value="a" label="Option A" />
|
|
441
|
+
<Radio value="b" label="Option B" />
|
|
442
|
+
</RadioGroup>
|
|
443
|
+
|
|
444
|
+
// โ Standalone radios without a group
|
|
445
|
+
<Radio name="options" value="a" label="Option A" />
|
|
446
|
+
<Radio name="options" value="b" label="Option B" />
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
2. **Provide clear labels**: Every radio button must have a visible label or an `aria-label` to describe its purpose.
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
// โ
Descriptive label
|
|
453
|
+
<Radio value="monthly" label="Monthly billing ($10/month)" />
|
|
454
|
+
|
|
455
|
+
// โ Vague label
|
|
456
|
+
<Radio value="monthly" label="Option 1" />
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
3. **Set a default selection**: Pre-select the most common or recommended option using `defaultValue` or a controlled `value` to reduce user effort.
|
|
460
|
+
|
|
461
|
+
```tsx
|
|
462
|
+
// โ
Sensible default
|
|
463
|
+
<RadioGroup defaultValue="standard" name="shipping">
|
|
464
|
+
<Radio value="standard" label="Standard Shipping" />
|
|
465
|
+
<Radio value="express" label="Express Shipping" />
|
|
466
|
+
</RadioGroup>
|
|
467
|
+
```
|
|
343
468
|
|
|
344
|
-
|
|
469
|
+
4. **Limit options to 2-7 items**: If more than 7 options exist, consider using a Select or Autocomplete component instead.
|
|
345
470
|
|
|
346
|
-
|
|
471
|
+
5. **Use vertical layout by default**: Vertical lists are easier to scan. Only use horizontal (`orientation="horizontal"`) when options are very short (e.g., segmented controls).
|
|
347
472
|
|
|
348
|
-
|
|
473
|
+
## Accessibility
|
|
349
474
|
|
|
350
|
-
|
|
475
|
+
- **Keyboard navigation**: Users can move between options using `Arrow Up` / `Arrow Down` (vertical) or `Arrow Left` / `Arrow Right` (horizontal). `Space` selects the focused option.
|
|
476
|
+
- **ARIA roles**: `RadioGroup` renders with `role="radiogroup"`, and each `Radio` renders with `role="radio"` and `aria-checked` reflecting its state.
|
|
477
|
+
- **Labeling**: Always connect a `FormLabel` to the `RadioGroup` via `aria-labelledby`, or provide an `aria-label` directly on the group so screen readers can announce the group purpose.
|
|
478
|
+
- **Focus management**: When using `overlay` or `disableIcon` patterns, ensure the interactive area is clearly communicated -- the `overlay` prop extends the clickable area to the parent container, which is also reflected in focus styling.
|