Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(radio): add helperText and errorText properties #30162

Open
wants to merge 13 commits into
base: feature-8.5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1325,6 +1325,8 @@ ion-radio,shadow
ion-radio,prop,alignment,"center" | "start" | undefined,undefined,false,false
ion-radio,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-radio,prop,disabled,boolean,false,false,false
ion-radio,prop,errorText,string | undefined,undefined,false,false
ion-radio,prop,helperText,string | undefined,undefined,false,false
ion-radio,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false
ion-radio,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
ion-radio,prop,mode,"ios" | "md",undefined,false,false
Expand All @@ -1341,8 +1343,11 @@ ion-radio,css-prop,--color-checked,md
ion-radio,css-prop,--inner-border-radius,ios
ion-radio,css-prop,--inner-border-radius,md
ion-radio,part,container
ion-radio,part,error-text
ion-radio,part,helper-text
ion-radio,part,label
ion-radio,part,mark
ion-radio,part,supporting-text

ion-radio-group,none
ion-radio-group,prop,allowEmptySelection,boolean,false,false,false
Expand Down
16 changes: 16 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,14 @@ export namespace Components {
* If `true`, the user cannot interact with the radio.
*/
"disabled": boolean;
/**
* Text that is placed under the radio label and displayed when an error is detected.
*/
"errorText"?: string;
/**
* Text that is placed under the radio label and displayed when no error is detected.
*/
"helperText"?: string;
/**
* How to pack the label and radio within a line. `"start"`: The label and radio will appear on the left in LTR and on the right in RTL. `"end"`: The label and radio will appear on the right in LTR and on the left in RTL. `"space-between"`: The label and radio will appear on opposite ends of the line with space between the two elements. Setting this property will change the radio `display` to `block`.
*/
Expand Down Expand Up @@ -7041,6 +7049,14 @@ declare namespace LocalJSX {
* If `true`, the user cannot interact with the radio.
*/
"disabled"?: boolean;
/**
* Text that is placed under the radio label and displayed when an error is detected.
*/
"errorText"?: string;
/**
* Text that is placed under the radio label and displayed when no error is detected.
*/
"helperText"?: string;
/**
* How to pack the label and radio within a line. `"start"`: The label and radio will appear on the left in LTR and on the right in RTL. `"end"`: The label and radio will appear on the right in LTR and on the left in RTL. `"space-between"`: The label and radio will appear on opposite ends of the line with space between the two elements. Setting this property will change the radio `display` to `block`.
*/
Expand Down
101 changes: 76 additions & 25 deletions core/src/components/radio/radio.scss
Original file line number Diff line number Diff line change
Expand Up @@ -140,46 +140,53 @@ input {
align-items: center;
}

// Radio Justify
// --------------------------------------------------
// Radio Bottom Content
// ----------------------------------------------------------------

.radio-bottom {
@include padding(4px, null, null, null);

display: flex;

:host(.radio-justify-space-between) .radio-wrapper {
justify-content: space-between;
}

:host(.radio-justify-start) .radio-wrapper {
justify-content: start;
font-size: dynamic-font(12px);

white-space: normal;
}

:host(.radio-justify-end) .radio-wrapper {
justify-content: end;
:host(.radio-label-placement-stacked) .radio-bottom {
font-size: dynamic-font(16px);
}

// Radio Align
// --------------------------------------------------
// Radio Hint Text
// ----------------------------------------------------------------

:host(.radio-alignment-start) .radio-wrapper {
align-items: start;
}
/**
* Error text should only be shown when .ion-invalid is
* present on the radio. Otherwise the helper text should
* be shown.
*/
.radio-bottom .error-text {
display: none;

:host(.radio-alignment-center) .radio-wrapper {
align-items: center;
color: ion-color(danger, base);
}

// Justify Content & Align Items
// ---------------------------------------------
.radio-bottom .helper-text {
display: block;

// The radio should be displayed as block when either justify
// or alignment is set; otherwise, these properties will have no
// visible effect.
:host(.radio-justify-space-between),
:host(.radio-justify-start),
:host(.radio-justify-end),
:host(.radio-alignment-start),
:host(.radio-alignment-center) {
color: $text-color-step-300;
}

:host(.ion-touched.ion-invalid) .radio-bottom .error-text {
display: block;
}

:host(.ion-touched.ion-invalid) .radio-bottom .helper-text {
display: none;
}

// Radio Label Placement - Start
// ----------------------------------------------------------------

Expand Down Expand Up @@ -209,6 +216,8 @@ input {
*/
:host(.radio-label-placement-end) .radio-wrapper {
flex-direction: row-reverse;

justify-content: start;
}

/**
Expand Down Expand Up @@ -251,6 +260,8 @@ input {
*/
:host(.radio-label-placement-stacked) .radio-wrapper {
flex-direction: column;

text-align: center;
}

:host(.radio-label-placement-stacked) .label-text-wrapper {
Expand All @@ -277,3 +288,43 @@ input {
:host(.radio-label-placement-stacked.radio-alignment-center) .label-text-wrapper {
@include transform-origin(center, top);
}

// Radio Justify
// --------------------------------------------------

:host(.radio-justify-space-between) .radio-wrapper {
justify-content: space-between;
}

:host(.radio-justify-start) .radio-wrapper {
justify-content: start;
}

:host(.radio-justify-end) .radio-wrapper {
justify-content: end;
}

// Radio Align
// --------------------------------------------------

:host(.radio-alignment-start) .radio-wrapper {
align-items: start;
}

:host(.radio-alignment-center) .radio-wrapper {
align-items: center;
}

// Justify Content & Align Items
// ---------------------------------------------

// The radio should be displayed as block when either justify
// or alignment is set; otherwise, these properties will have no
// visible effect.
:host(.radio-justify-space-between),
:host(.radio-justify-start),
:host(.radio-justify-end),
:host(.radio-alignment-start),
:host(.radio-alignment-center) {
display: block;
}
60 changes: 60 additions & 0 deletions core/src/components/radio/radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import type { Color } from '../../interface';
* @part container - The container for the radio mark.
* @part label - The label text describing the radio.
* @part mark - The checkmark or dot used to indicate the checked state.
* @part supporting-text - Supporting text displayed beneath the radio label.
* @part helper-text - Supporting text displayed beneath the radio label when the radio is valid.
* @part error-text - Supporting text displayed beneath the radio label when the radio is invalid and touched.
*/
@Component({
tag: 'ion-radio',
Expand All @@ -26,6 +29,8 @@ import type { Color } from '../../interface';
})
export class Radio implements ComponentInterface {
private inputId = `ion-rb-${radioButtonIds++}`;
private helperTextId = `${this.inputId}-helper-text`;
private errorTextId = `${this.inputId}-error-text`;
private radioGroup: HTMLIonRadioGroupElement | null = null;

@Element() el!: HTMLIonRadioElement;
Expand Down Expand Up @@ -58,6 +63,16 @@ export class Radio implements ComponentInterface {
*/
@Prop() disabled = false;

/**
* Text that is placed under the radio label and displayed when an error is detected.
*/
@Prop() errorText?: string;

/**
* Text that is placed under the radio label and displayed when no error is detected.
*/
@Prop() helperText?: string;

/**
* the value of the radio.
*/
Expand Down Expand Up @@ -212,6 +227,48 @@ export class Radio implements ComponentInterface {
);
}

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;
}

/**
* Responsible for rendering helper text and error text.
* This element should only be rendered if hint text is set.
*/
private renderHintText() {
const { helperText, errorText, helperTextId, errorTextId } = this;

/**
* undefined and empty string values should
* be treated as not having helper/error text.
*/
const hasHintText = !!helperText || !!errorText;
if (!hasHintText) {
return;
}

return (
<div class="radio-bottom">
<div id={helperTextId} class="helper-text" part="supporting-text helper-text">
{helperText}
</div>
<div id={errorTextId} class="error-text" part="supporting-text error-text">
{errorText}
</div>
</div>
);
}

render() {
const { checked, disabled, color, el, justify, labelPlacement, hasLabel, buttonTabindex, alignment } = this;
const mode = getIonMode(this);
Expand All @@ -237,6 +294,8 @@ export class Radio implements ComponentInterface {
role="radio"
aria-checked={checked ? 'true' : 'false'}
aria-disabled={disabled ? 'true' : null}
aria-describedby={this.getHintTextID()}
aria-invalid={this.getHintTextID() === this.errorTextId}
tabindex={buttonTabindex}
>
<label class="radio-wrapper">
Expand All @@ -248,6 +307,7 @@ export class Radio implements ComponentInterface {
part="label"
>
<slot></slot>
{this.renderHintText()}
</div>
<div class="native-wrapper">{this.renderRadioControl()}</div>
</label>
Expand Down
Loading
Loading