diff --git a/core/src/components.d.ts b/core/src/components.d.ts index b7675233d1..02a43642d6 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2307,6 +2307,14 @@ export namespace Components { * 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. */ "compareWith"?: string | RadioGroupCompareFn | null; + /** + * The error text to display at the top of the radio group. + */ + "errorText"?: string; + /** + * The helper text to display at the top of the radio group. + */ + "helperText"?: string; /** * The name of the control, which is submitted with the form data. */ @@ -7079,6 +7087,14 @@ declare namespace LocalJSX { * 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. */ "compareWith"?: string | RadioGroupCompareFn | null; + /** + * The error text to display at the top of the radio group. + */ + "errorText"?: string; + /** + * The helper text to display at the top of the radio group. + */ + "helperText"?: string; /** * The name of the control, which is submitted with the form data. */ diff --git a/core/src/components/radio-group/radio-group.ios.scss b/core/src/components/radio-group/radio-group.ios.scss new file mode 100644 index 0000000000..2e26159a36 --- /dev/null +++ b/core/src/components/radio-group/radio-group.ios.scss @@ -0,0 +1,12 @@ +@import "../../themes/ionic.globals.ios"; +@import "./radio-group"; +@import "../item/item.ios.vars"; + +// iOS Radio Group in List +// -------------------------------------------------- + +// Add padding to the error and helper text when used in a +// list to align them with the list header and item text. +ion-list .supporting-text { + @include padding-horizontal($item-ios-padding-start, $item-ios-padding-end); +} diff --git a/core/src/components/radio-group/radio-group.md.scss b/core/src/components/radio-group/radio-group.md.scss new file mode 100644 index 0000000000..73ec9b386f --- /dev/null +++ b/core/src/components/radio-group/radio-group.md.scss @@ -0,0 +1,12 @@ +@import "../../themes/ionic.globals.md"; +@import "./radio-group"; +@import "../item/item.md.vars"; + +// Material Design Radio Group in List +// -------------------------------------------------- + +// Add padding to the error and helper text when used in a +// list to align them with the list header and item text. +ion-list .supporting-text { + @include padding-horizontal($item-md-padding-start, $item-md-padding-end); +} diff --git a/core/src/components/radio-group/radio-group.scss b/core/src/components/radio-group/radio-group.scss new file mode 100644 index 0000000000..a148f12232 --- /dev/null +++ b/core/src/components/radio-group/radio-group.scss @@ -0,0 +1,40 @@ +@import "../../themes/ionic.globals"; + +// Radio Group +// -------------------------------------------------- + +ion-radio-group { + display: block; +} + +// Radio Group: Supporting Text +// -------------------------------------------------- + +.supporting-text { + line-height: 1.5; +} + +/** + * Error text should only be shown when .ion-invalid is + * present on the radio group. Otherwise the helper text should + * be shown. + */ +.error-text { + display: none; + + color: var(--ion-color-danger, #eb445a); +} + +.helper-text { + display: block; + + color: var(--ion-color-step-550, #737373); +} + +.ion-touched.ion-invalid .error-text { + display: block; +} + +.ion-touched.ion-invalid .helper-text { + display: none; +} diff --git a/core/src/components/radio-group/radio-group.tsx b/core/src/components/radio-group/radio-group.tsx index a8762b5f8a..70b6831448 100644 --- a/core/src/components/radio-group/radio-group.tsx +++ b/core/src/components/radio-group/radio-group.tsx @@ -8,9 +8,15 @@ import type { RadioGroupChangeEventDetail, RadioGroupCompareFn } from './radio-g @Component({ tag: 'ion-radio-group', + styleUrls: { + ios: 'radio-group.ios.scss', + md: 'radio-group.md.scss', + }, }) export class RadioGroup implements ComponentInterface { private inputId = `ion-rg-${radioGroupIds++}`; + private helperTextId = `${this.inputId}-helper-text`; + private errorTextId = `${this.inputId}-error-text`; private labelId = `${this.inputId}-lbl`; private label?: HTMLIonLabelElement | null; @@ -39,6 +45,16 @@ export class RadioGroup implements ComponentInterface { */ @Prop({ mutable: true }) value?: any | null; + /** + * The helper text to display at the top of the radio group. + */ + @Prop() helperText?: string; + + /** + * The error text to display at the top of the radio group. + */ + @Prop() errorText?: string; + @Watch('value') valueChanged(value: any | undefined) { this.setRadioTabindex(value); @@ -224,13 +240,62 @@ export class RadioGroup implements ComponentInterface { radioToFocus?.setFocus(); } + /** + * Renders the helper text or error text values + */ + private renderHintText() { + const { helperText, errorText, helperTextId, errorTextId } = this; + + const hasHintText = !!helperText || !!errorText; + if (!hasHintText) { + return; + } + + return ( +
+
+ {helperText} +
+
+ {errorText} +
+
+ ); + } + + private getHintTextID(): string | undefined { + const { el, helperText, errorText, helperTextId, errorTextId } = this; + + if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) { + return errorTextId; + } + + if (helperText) { + return helperTextId; + } + + return undefined; + } + render() { const { label, labelId, el, name, value } = this; const mode = getIonMode(this); renderHiddenInput(true, el, name, value, false); - return ; + return ( + + {this.renderHintText()} + + + ); } } diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 9d6d23b5c1..ba49606252 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -1639,14 +1639,14 @@ export declare interface IonRadio extends Components.IonRadio { @ProxyCmp({ - inputs: ['allowEmptySelection', 'compareWith', 'name', 'value'] + inputs: ['allowEmptySelection', 'compareWith', 'errorText', 'helperText', 'name', 'value'] }) @Component({ selector: 'ion-radio-group', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['allowEmptySelection', 'compareWith', 'name', 'value'], + inputs: ['allowEmptySelection', 'compareWith', 'errorText', 'helperText', 'name', 'value'], }) export class IonRadioGroup { protected el: HTMLIonRadioGroupElement; diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index a866edde10..fd1a2ee785 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -696,6 +696,8 @@ export const IonRadioGroup = /*@__PURE__*/ defineContainer