Skip to content

Commit 99d2f1c

Browse files
feat(checkbox): add helperText and errorText properties (#30140)
Issue number: resolves #29810 --------- ## What is the current behavior? Checkbox does not support helper and error text. ## What is the new behavior? Adds support for helper and error text, similar to input and textarea. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information - [Bottom Content: Preview](https://ionic-framework-git-rou-11141-ionic1.vercel.app/src/components/checkbox/test/bottom-content) - [Item: Preview](https://ionic-framework-git-rou-11141-ionic1.vercel.app/src/components/checkbox/test/item) --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
1 parent 335672d commit 99d2f1c

File tree

71 files changed

+578
-30
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+578
-30
lines changed

core/api.txt

Lines changed: 5 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
@@ -431,8 +433,11 @@ ion-checkbox,css-prop,--size,md
431433
ion-checkbox,css-prop,--transition,ios
432434
ion-checkbox,css-prop,--transition,md
433435
ion-checkbox,part,container
436+
ion-checkbox,part,error-text
437+
ion-checkbox,part,helper-text
434438
ion-checkbox,part,label
435439
ion-checkbox,part,mark
440+
ion-checkbox,part,supporting-text
436441

437442
ion-chip,shadow
438443
ion-chip,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true

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 label and displayed when an error is detected.
628+
*/
629+
"errorText"?: string;
630+
/**
631+
* Text that is placed under the checkbox label and displayed when no error is detected.
632+
*/
633+
"helperText"?: string;
626634
/**
627635
* If `true`, the checkbox will visually appear as indeterminate.
628636
*/
@@ -5419,6 +5427,14 @@ declare namespace LocalJSX {
54195427
* If `true`, the user cannot interact with the checkbox.
54205428
*/
54215429
"disabled"?: boolean;
5430+
/**
5431+
* Text that is placed under the checkbox label and displayed when an error is detected.
5432+
*/
5433+
"errorText"?: string;
5434+
/**
5435+
* Text that is placed under the checkbox label and displayed when no error is detected.
5436+
*/
5437+
"helperText"?: string;
54225438
/**
54235439
* If `true`, the checkbox will visually appear as indeterminate.
54245440
*/

core/src/components/checkbox/checkbox.scss

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -148,46 +148,53 @@ input {
148148
opacity: 0;
149149
}
150150

151-
// Justify Content
152-
// ---------------------------------------------
151+
// Checkbox Bottom Content
152+
// ----------------------------------------------------------------
153+
154+
.checkbox-bottom {
155+
@include padding(4px, null, null, null);
156+
157+
display: flex;
153158

154-
:host(.checkbox-justify-space-between) .checkbox-wrapper {
155159
justify-content: space-between;
156-
}
157160

158-
:host(.checkbox-justify-start) .checkbox-wrapper {
159-
justify-content: start;
161+
font-size: dynamic-font(12px);
162+
163+
white-space: normal;
160164
}
161165

162-
:host(.checkbox-justify-end) .checkbox-wrapper {
163-
justify-content: end;
166+
:host(.checkbox-label-placement-stacked) .checkbox-bottom {
167+
font-size: dynamic-font(16px);
164168
}
165169

166-
// Align Items
167-
// ---------------------------------------------
170+
// Checkbox Hint Text
171+
// ----------------------------------------------------------------
168172

169-
:host(.checkbox-alignment-start) .checkbox-wrapper {
170-
align-items: start;
171-
}
173+
/**
174+
* Error text should only be shown when .ion-invalid is
175+
* present on the checkbox. Otherwise the helper text should
176+
* be shown.
177+
*/
178+
.checkbox-bottom .error-text {
179+
display: none;
172180

173-
:host(.checkbox-alignment-center) .checkbox-wrapper {
174-
align-items: center;
181+
color: ion-color(danger, base);
175182
}
176183

177-
// Justify Content & Align Items
178-
// ---------------------------------------------
184+
.checkbox-bottom .helper-text {
185+
display: block;
179186

180-
// The checkbox should be displayed as block when either justify
181-
// or alignment is set; otherwise, these properties will have no
182-
// visible effect.
183-
:host(.checkbox-justify-space-between),
184-
:host(.checkbox-justify-start),
185-
:host(.checkbox-justify-end),
186-
:host(.checkbox-alignment-start),
187-
:host(.checkbox-alignment-center) {
187+
color: $text-color-step-300;
188+
}
189+
190+
:host(.ion-touched.ion-invalid) .checkbox-bottom .error-text {
188191
display: block;
189192
}
190193

194+
:host(.ion-touched.ion-invalid) .checkbox-bottom .helper-text {
195+
display: none;
196+
}
197+
191198
// Label Placement - Start
192199
// ----------------------------------------------------------------
193200

@@ -217,6 +224,8 @@ input {
217224
*/
218225
:host(.checkbox-label-placement-end) .checkbox-wrapper {
219226
flex-direction: row-reverse;
227+
228+
justify-content: start;
220229
}
221230

222231
/**
@@ -260,6 +269,8 @@ input {
260269
*/
261270
:host(.checkbox-label-placement-stacked) .checkbox-wrapper {
262271
flex-direction: column;
272+
273+
text-align: center;
263274
}
264275

265276
:host(.checkbox-label-placement-stacked) .label-text-wrapper {
@@ -287,6 +298,46 @@ input {
287298
@include transform-origin(center, top);
288299
}
289300

301+
// Justify Content
302+
// ---------------------------------------------
303+
304+
:host(.checkbox-justify-space-between) .checkbox-wrapper {
305+
justify-content: space-between;
306+
}
307+
308+
:host(.checkbox-justify-start) .checkbox-wrapper {
309+
justify-content: start;
310+
}
311+
312+
:host(.checkbox-justify-end) .checkbox-wrapper {
313+
justify-content: end;
314+
}
315+
316+
// Align Items
317+
// ---------------------------------------------
318+
319+
:host(.checkbox-alignment-start) .checkbox-wrapper {
320+
align-items: start;
321+
}
322+
323+
:host(.checkbox-alignment-center) .checkbox-wrapper {
324+
align-items: center;
325+
}
326+
327+
// Justify Content & Align Items
328+
// ---------------------------------------------
329+
330+
// The checkbox should be displayed as block when either justify
331+
// or alignment is set; otherwise, these properties will have no
332+
// visible effect.
333+
:host(.checkbox-justify-space-between),
334+
:host(.checkbox-justify-start),
335+
:host(.checkbox-justify-end),
336+
:host(.checkbox-alignment-start),
337+
:host(.checkbox-alignment-center) {
338+
display: block;
339+
}
340+
290341
// Checked / Indeterminate Checkbox
291342
// ---------------------------------------------
292343

core/src/components/checkbox/checkbox.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
1717
* @part container - The container for the checkbox mark.
1818
* @part label - The label text describing the checkbox.
1919
* @part mark - The checkmark used to indicate the checked state.
20+
* @part supporting-text - Supporting text displayed beneath the checkbox label.
21+
* @part helper-text - Supporting text displayed beneath the checkbox label when the checkbox is valid.
22+
* @part error-text - Supporting text displayed beneath the checkbox label when the checkbox is invalid and touched.
2023
*/
2124
@Component({
2225
tag: 'ion-checkbox',
@@ -28,6 +31,8 @@ import type { CheckboxChangeEventDetail } from './checkbox-interface';
2831
})
2932
export class Checkbox implements ComponentInterface {
3033
private inputId = `ion-cb-${checkboxIds++}`;
34+
private helperTextId = `${this.inputId}-helper-text`;
35+
private errorTextId = `${this.inputId}-error-text`;
3136
private focusEl?: HTMLElement;
3237
private inheritedAttributes: Attributes = {};
3338

@@ -60,6 +65,16 @@ export class Checkbox implements ComponentInterface {
6065
*/
6166
@Prop() disabled = false;
6267

68+
/**
69+
* Text that is placed under the checkbox label and displayed when an error is detected.
70+
*/
71+
@Prop() errorText?: string;
72+
73+
/**
74+
* Text that is placed under the checkbox label and displayed when no error is detected.
75+
*/
76+
@Prop() helperText?: string;
77+
6378
/**
6479
* The value of the checkbox does not mean if it's checked or not, use the `checked`
6580
* property for that.
@@ -174,6 +189,48 @@ export class Checkbox implements ComponentInterface {
174189
this.toggleChecked(ev);
175190
};
176191

192+
private getHintTextID(): string | undefined {
193+
const { el, helperText, errorText, helperTextId, errorTextId } = this;
194+
195+
if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
196+
return errorTextId;
197+
}
198+
199+
if (helperText) {
200+
return helperTextId;
201+
}
202+
203+
return undefined;
204+
}
205+
206+
/**
207+
* Responsible for rendering helper text and error text.
208+
* This element should only be rendered if hint text is set.
209+
*/
210+
private renderHintText() {
211+
const { helperText, errorText, helperTextId, errorTextId } = this;
212+
213+
/**
214+
* undefined and empty string values should
215+
* be treated as not having helper/error text.
216+
*/
217+
const hasHintText = !!helperText || !!errorText;
218+
if (!hasHintText) {
219+
return;
220+
}
221+
222+
return (
223+
<div class="checkbox-bottom">
224+
<div id={helperTextId} class="helper-text" part="supporting-text helper-text">
225+
{helperText}
226+
</div>
227+
<div id={errorTextId} class="error-text" part="supporting-text error-text">
228+
{errorText}
229+
</div>
230+
</div>
231+
);
232+
}
233+
177234
render() {
178235
const {
179236
color,
@@ -199,6 +256,8 @@ export class Checkbox implements ComponentInterface {
199256
return (
200257
<Host
201258
aria-checked={indeterminate ? 'mixed' : `${checked}`}
259+
aria-describedby={this.getHintTextID()}
260+
aria-invalid={this.getHintTextID() === this.errorTextId}
202261
class={createColorClasses(color, {
203262
[mode]: true,
204263
'in-item': hostContext('ion-item', el),
@@ -237,6 +296,7 @@ export class Checkbox implements ComponentInterface {
237296
part="label"
238297
>
239298
<slot></slot>
299+
{this.renderHintText()}
240300
</div>
241301
<div class="native-wrapper">
242302
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">

0 commit comments

Comments
 (0)