Skip to content

Commit 2aaf9e4

Browse files
committed
feat(checkbox): add helperText and errorText
1 parent 3f8346e commit 2aaf9e4

File tree

8 files changed

+158
-2
lines changed

8 files changed

+158
-2
lines changed

core/api.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,8 @@ ion-checkbox,prop,alignment,"center" | "start" | undefined,undefined,false,false
398398
ion-checkbox,prop,checked,boolean,false,false,false
399399
ion-checkbox,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
400400
ion-checkbox,prop,disabled,boolean,false,false,false
401+
ion-checkbox,prop,errorText,string | undefined,undefined,false,false
402+
ion-checkbox,prop,helperText,string | undefined,undefined,false,false
401403
ion-checkbox,prop,indeterminate,boolean,false,false,false
402404
ion-checkbox,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false
403405
ion-checkbox,prop,labelPlacement,"end" | "fixed" | "stacked" | "start",'start',false,false
@@ -425,6 +427,14 @@ ion-checkbox,css-prop,--checkmark-color,ios
425427
ion-checkbox,css-prop,--checkmark-color,md
426428
ion-checkbox,css-prop,--checkmark-width,ios
427429
ion-checkbox,css-prop,--checkmark-width,md
430+
ion-checkbox,css-prop,--highlight-color-focused,ios
431+
ion-checkbox,css-prop,--highlight-color-focused,md
432+
ion-checkbox,css-prop,--highlight-color-invalid,ios
433+
ion-checkbox,css-prop,--highlight-color-invalid,md
434+
ion-checkbox,css-prop,--highlight-color-valid,ios
435+
ion-checkbox,css-prop,--highlight-color-valid,md
436+
ion-checkbox,css-prop,--highlight-height,ios
437+
ion-checkbox,css-prop,--highlight-height,md
428438
ion-checkbox,css-prop,--size,ios
429439
ion-checkbox,css-prop,--size,md
430440
ion-checkbox,css-prop,--transition,ios

core/src/components.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,14 @@ export namespace Components {
623623
* If `true`, the user cannot interact with the checkbox.
624624
*/
625625
"disabled": boolean;
626+
/**
627+
* Text that is placed under the checkbox and displayed when an error is detected.
628+
*/
629+
"errorText"?: string;
630+
/**
631+
* Text that is placed under the checkbox and displayed when no error is detected.
632+
*/
633+
"helperText"?: string;
626634
/**
627635
* If `true`, the checkbox will visually appear as indeterminate.
628636
*/
@@ -5403,6 +5411,14 @@ declare namespace LocalJSX {
54035411
* If `true`, the user cannot interact with the checkbox.
54045412
*/
54055413
"disabled"?: boolean;
5414+
/**
5415+
* Text that is placed under the checkbox and displayed when an error is detected.
5416+
*/
5417+
"errorText"?: string;
5418+
/**
5419+
* Text that is placed under the checkbox and displayed when no error is detected.
5420+
*/
5421+
"helperText"?: string;
54065422
/**
54075423
* If `true`, the checkbox will visually appear as indeterminate.
54085424
*/

core/src/components/checkbox/checkbox.ios.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
--checkmark-width: #{$checkbox-ios-icon-checkmark-width};
2222
}
2323

24+
// iOS Checkbox: Hint Text
25+
// ----------------------------------------------------------------
26+
27+
.checkbox-bottom .helper-text {
28+
color: #{$text-color-step-450};
29+
}
2430

2531
// iOS Checkbox: Disabled
2632
// -----------------------------------------

core/src/components/checkbox/checkbox.md.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
stroke-dashoffset: 30;
2828
}
2929

30+
// Material Design Checkbox: Hint Text
31+
// ----------------------------------------------------------------
32+
33+
.checkbox-bottom .helper-text {
34+
color: #{$text-color-step-300};
35+
}
36+
3037
// Material Design Checkbox: Checked / Indeterminate
3138
// --------------------------------------------------------
3239

core/src/components/checkbox/checkbox.scss

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
* @prop --border-style: Border style of the checkbox icon
1818
* @prop --border-color-checked: Border color of the checkbox icon when checked
1919
*
20+
* @prop --highlight-height: The height of the highlight on the checkbox. Only applies to md mode
21+
* @prop --highlight-color-focused: The color of the highlight on the checkbox when focused
22+
* @prop --highlight-color-valid: The color of the highlight on the checkbox when valid
23+
* @prop --highlight-color-invalid: The color of the highlight on the checkbox when invalid
24+
*
2025
* @prop --transition: Transition of the checkbox icon
2126
*
2227
* @prop --checkmark-color: Color of the checkbox checkmark when checked
@@ -26,6 +31,9 @@
2631
--border-color-checked: #{ion-color(primary, base)};
2732
--checkmark-color: #{ion-color(primary, contrast)};
2833
--transition: none;
34+
--highlight-color-focused: #{ion-color(primary, base)};
35+
--highlight-color-valid: #{ion-color(success, base)};
36+
--highlight-color-invalid: #{ion-color(danger, base)};
2937

3038
display: inline-block;
3139

@@ -148,6 +156,56 @@ input {
148156
opacity: 0;
149157
}
150158

159+
// Checkbox Bottom Content
160+
// ----------------------------------------------------------------
161+
162+
.checkbox-bottom {
163+
/**
164+
* The bottom content should take on the start and end
165+
* padding so it is always aligned with either the label
166+
* or the start of the checkbox.
167+
*/
168+
@include padding(5px, null, null, null);
169+
170+
display: flex;
171+
172+
justify-content: space-between;
173+
174+
font-size: dynamic-font(12px);
175+
176+
white-space: normal;
177+
}
178+
179+
:host(.checkbox-label-placement-stacked) .checkbox-bottom {
180+
font-size: dynamic-font(16px);
181+
}
182+
183+
// Checkbox Hint Text
184+
// ----------------------------------------------------------------
185+
186+
/**
187+
* Error text should only be shown when .ion-invalid is
188+
* present on the checkbox. Otherwise the helper text should
189+
* be shown.
190+
*/
191+
.checkbox-bottom .error-text {
192+
display: none;
193+
194+
color: var(--highlight-color-invalid);
195+
}
196+
197+
.checkbox-bottom .helper-text {
198+
display: block;
199+
}
200+
201+
:host(.ion-touched.ion-invalid) .checkbox-bottom .error-text {
202+
display: block;
203+
}
204+
205+
:host(.ion-touched.ion-invalid) .checkbox-bottom .helper-text {
206+
display: none;
207+
}
208+
151209
// Justify Content
152210
// ---------------------------------------------
153211

core/src/components/checkbox/checkbox.tsx

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
2828
})
2929
export class Checkbox implements ComponentInterface {
3030
private inputId = `ion-cb-${checkboxIds++}`;
31+
private helperTextId = `${this.inputId}-helper-text`;
32+
private errorTextId = `${this.inputId}-error-text`;
3133
private focusEl?: HTMLElement;
3234
private inheritedAttributes: Attributes = {};
3335

@@ -60,6 +62,16 @@ export class Checkbox implements ComponentInterface {
6062
*/
6163
@Prop() disabled = false;
6264

65+
/**
66+
* Text that is placed under the checkbox and displayed when an error is detected.
67+
*/
68+
@Prop() errorText?: string;
69+
70+
/**
71+
* Text that is placed under the checkbox and displayed when no error is detected.
72+
*/
73+
@Prop() helperText?: string;
74+
6375
/**
6476
* The value of the checkbox does not mean if it's checked or not, use the `checked`
6577
* property for that.
@@ -167,6 +179,48 @@ export class Checkbox implements ComponentInterface {
167179
this.toggleChecked(ev);
168180
};
169181

182+
private getHintTextID(): string | undefined {
183+
const { el, helperText, errorText, helperTextId, errorTextId } = this;
184+
185+
if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
186+
return errorTextId;
187+
}
188+
189+
if (helperText) {
190+
return helperTextId;
191+
}
192+
193+
return undefined;
194+
}
195+
196+
/**
197+
* Responsible for rendering helper text and error text.
198+
* This element should only be rendered if hint text is set.
199+
*/
200+
private renderHintText() {
201+
const { helperText, errorText, helperTextId, errorTextId } = this;
202+
203+
/**
204+
* undefined and empty string values should
205+
* be treated as not having helper/error text.
206+
*/
207+
const hasHintText = !!helperText || !!errorText;
208+
if (!hasHintText) {
209+
return;
210+
}
211+
212+
return (
213+
<div class="checkbox-bottom">
214+
<div id={helperTextId} class="helper-text">
215+
{helperText}
216+
</div>
217+
<div id={errorTextId} class="error-text">
218+
{errorText}
219+
</div>
220+
</div>
221+
);
222+
}
223+
170224
render() {
171225
const {
172226
color,
@@ -218,6 +272,8 @@ export class Checkbox implements ComponentInterface {
218272
onFocus={() => this.onFocus()}
219273
onBlur={() => this.onBlur()}
220274
ref={(focusEl) => (this.focusEl = focusEl)}
275+
aria-describedby={this.getHintTextID()}
276+
aria-invalid={this.getHintTextID() === this.errorTextId}
221277
{...inheritedAttributes}
222278
/>
223279
<div
@@ -228,6 +284,7 @@ export class Checkbox implements ComponentInterface {
228284
part="label"
229285
>
230286
<slot></slot>
287+
{this.renderHintText()}
231288
</div>
232289
<div class="native-wrapper">
233290
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">

packages/angular/src/directives/proxies.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,14 +507,14 @@ export declare interface IonCardTitle extends Components.IonCardTitle {}
507507

508508

509509
@ProxyCmp({
510-
inputs: ['alignment', 'checked', 'color', 'disabled', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'value']
510+
inputs: ['alignment', 'checked', 'color', 'disabled', 'errorText', 'helperText', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'value']
511511
})
512512
@Component({
513513
selector: 'ion-checkbox',
514514
changeDetection: ChangeDetectionStrategy.OnPush,
515515
template: '<ng-content></ng-content>',
516516
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
517-
inputs: ['alignment', 'checked', 'color', 'disabled', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'value'],
517+
inputs: ['alignment', 'checked', 'color', 'disabled', 'errorText', 'helperText', 'indeterminate', 'justify', 'labelPlacement', 'mode', 'name', 'value'],
518518
})
519519
export class IonCheckbox {
520520
protected el: HTMLElement;

packages/vue/src/proxies.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ export const IonCheckbox = /*@__PURE__*/ defineContainer<JSX.IonCheckbox, JSX.Io
217217
'checked',
218218
'indeterminate',
219219
'disabled',
220+
'errorText',
221+
'helperText',
220222
'value',
221223
'labelPlacement',
222224
'justify',

0 commit comments

Comments
 (0)