Skip to content

Commit 18e26ac

Browse files
feat(radio-group): add helperText and errorText properties (#30222)
Issue number: N/A --------- ## What is the current behavior? Radio group does not support helper and error text. ## What is the new behavior? Adds support for helper and error text. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information - [Supporting Text: Preview](https://ionic-framework-git-rou-11554-ionic1.vercel.app/src/components/radio-group/test/supporting-text) --------- Co-authored-by: Brandy Smith <[email protected]>
1 parent bbdaec0 commit 18e26ac

File tree

34 files changed

+625
-4
lines changed

34 files changed

+625
-4
lines changed

core/api.txt

+2
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,8 @@ ion-radio,part,mark
13521352
ion-radio-group,none
13531353
ion-radio-group,prop,allowEmptySelection,boolean,false,false,false
13541354
ion-radio-group,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false
1355+
ion-radio-group,prop,errorText,string | undefined,undefined,false,false
1356+
ion-radio-group,prop,helperText,string | undefined,undefined,false,false
13551357
ion-radio-group,prop,name,string,this.inputId,false,false
13561358
ion-radio-group,prop,value,any,undefined,false,false
13571359
ion-radio-group,event,ionChange,RadioGroupChangeEventDetail<any>,true

core/src/components.d.ts

+16
Original file line numberDiff line numberDiff line change
@@ -2315,6 +2315,14 @@ export namespace Components {
23152315
* This property allows developers to specify a custom function or property name for comparing objects when determining the selected option in the ion-radio-group. When not specified, the default behavior will use strict equality (===) for comparison.
23162316
*/
23172317
"compareWith"?: string | RadioGroupCompareFn | null;
2318+
/**
2319+
* The error text to display at the top of the radio group.
2320+
*/
2321+
"errorText"?: string;
2322+
/**
2323+
* The helper text to display at the top of the radio group.
2324+
*/
2325+
"helperText"?: string;
23182326
/**
23192327
* The name of the control, which is submitted with the form data.
23202328
*/
@@ -7111,6 +7119,14 @@ declare namespace LocalJSX {
71117119
* This property allows developers to specify a custom function or property name for comparing objects when determining the selected option in the ion-radio-group. When not specified, the default behavior will use strict equality (===) for comparison.
71127120
*/
71137121
"compareWith"?: string | RadioGroupCompareFn | null;
7122+
/**
7123+
* The error text to display at the top of the radio group.
7124+
*/
7125+
"errorText"?: string;
7126+
/**
7127+
* The helper text to display at the top of the radio group.
7128+
*/
7129+
"helperText"?: string;
71147130
/**
71157131
* The name of the control, which is submitted with the form data.
71167132
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@import "../../themes/ionic.globals.ios";
2+
@import "./radio-group";
3+
@import "../item/item.ios.vars";
4+
5+
// iOS Radio Group Top in List
6+
// --------------------------------------------------
7+
8+
// Add padding to the error and helper text when used in a
9+
// list to align them with the list header and item text.
10+
ion-list .radio-group-top {
11+
@include padding-horizontal($item-ios-padding-start, $item-ios-padding-end);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@import "../../themes/ionic.globals.md";
2+
@import "./radio-group";
3+
@import "../item/item.md.vars";
4+
5+
// Material Design Radio Group Top in List
6+
// --------------------------------------------------
7+
8+
// Add padding to the error and helper text when used in a
9+
// list to align them with the list header and item text.
10+
ion-list .radio-group-top {
11+
@include padding-horizontal($item-md-padding-start, $item-md-padding-end);
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@import "../../themes/ionic.globals";
2+
3+
// Radio Group
4+
// --------------------------------------------------
5+
6+
ion-radio-group {
7+
// Prevents additional pixels from being rendered on top
8+
vertical-align: top;
9+
}
10+
11+
.radio-group-wrapper {
12+
display: inline;
13+
}
14+
15+
// Radio Group: Top
16+
// --------------------------------------------------
17+
18+
.radio-group-top {
19+
line-height: 1.5;
20+
}
21+
22+
/**
23+
* Error text should only be shown when .ion-invalid is present
24+
* on the radio group. Otherwise the helper text should be shown.
25+
*/
26+
.radio-group-top .error-text {
27+
display: none;
28+
29+
color: ion-color(danger, base);
30+
}
31+
32+
.radio-group-top .helper-text {
33+
display: block;
34+
35+
color: $text-color-step-300;
36+
}
37+
38+
.ion-touched.ion-invalid .radio-group-top .error-text {
39+
display: block;
40+
}
41+
42+
.ion-touched.ion-invalid .radio-group-top .helper-text {
43+
display: none;
44+
}

core/src/components/radio-group/radio-group.tsx

+73-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ import type { RadioGroupChangeEventDetail, RadioGroupCompareFn } from './radio-g
88

99
@Component({
1010
tag: 'ion-radio-group',
11+
styleUrls: {
12+
ios: 'radio-group.ios.scss',
13+
md: 'radio-group.md.scss',
14+
},
1115
})
1216
export class RadioGroup implements ComponentInterface {
1317
private inputId = `ion-rg-${radioGroupIds++}`;
18+
private helperTextId = `${this.inputId}-helper-text`;
19+
private errorTextId = `${this.inputId}-error-text`;
1420
private labelId = `${this.inputId}-lbl`;
1521
private label?: HTMLIonLabelElement | null;
1622

@@ -39,6 +45,16 @@ export class RadioGroup implements ComponentInterface {
3945
*/
4046
@Prop({ mutable: true }) value?: any | null;
4147

48+
/**
49+
* The helper text to display at the top of the radio group.
50+
*/
51+
@Prop() helperText?: string;
52+
53+
/**
54+
* The error text to display at the top of the radio group.
55+
*/
56+
@Prop() errorText?: string;
57+
4258
@Watch('value')
4359
valueChanged(value: any | undefined) {
4460
this.setRadioTabindex(value);
@@ -224,13 +240,69 @@ export class RadioGroup implements ComponentInterface {
224240
radioToFocus?.setFocus();
225241
}
226242

243+
/**
244+
* Renders the helper text or error text values
245+
*/
246+
private renderHintText() {
247+
const { helperText, errorText, helperTextId, errorTextId } = this;
248+
249+
const hasHintText = !!helperText || !!errorText;
250+
if (!hasHintText) {
251+
return;
252+
}
253+
254+
return (
255+
<div class="radio-group-top">
256+
<div id={helperTextId} class="helper-text">
257+
{helperText}
258+
</div>
259+
<div id={errorTextId} class="error-text">
260+
{errorText}
261+
</div>
262+
</div>
263+
);
264+
}
265+
266+
private getHintTextID(): string | undefined {
267+
const { el, helperText, errorText, helperTextId, errorTextId } = this;
268+
269+
if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
270+
return errorTextId;
271+
}
272+
273+
if (helperText) {
274+
return helperTextId;
275+
}
276+
277+
return undefined;
278+
}
279+
227280
render() {
228281
const { label, labelId, el, name, value } = this;
229282
const mode = getIonMode(this);
230283

231284
renderHiddenInput(true, el, name, value, false);
232285

233-
return <Host role="radiogroup" aria-labelledby={label ? labelId : null} onClick={this.onClick} class={mode}></Host>;
286+
return (
287+
<Host
288+
role="radiogroup"
289+
aria-labelledby={label ? labelId : null}
290+
aria-describedby={this.getHintTextID()}
291+
aria-invalid={this.getHintTextID() === this.errorTextId}
292+
onClick={this.onClick}
293+
class={mode}
294+
>
295+
{this.renderHintText()}
296+
{/*
297+
TODO(FW-6279): Wrapping the slot in a div is a workaround due to a
298+
Stencil issue. Without the wrapper, the children radio will fire the
299+
blur event on focus, instead of waiting for them to be blurred.
300+
*/}
301+
<div class="radio-group-wrapper">
302+
<slot></slot>
303+
</div>
304+
</Host>
305+
);
234306
}
235307
}
236308

0 commit comments

Comments
 (0)