Skip to content

Commit 1089f5b

Browse files
committed
feat(select): add helper and error text
1 parent e101f2e commit 1089f5b

File tree

7 files changed

+442
-2
lines changed

7 files changed

+442
-2
lines changed

core/src/components.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2755,6 +2755,10 @@ export namespace Components {
27552755
* If `true`, the user cannot interact with the select.
27562756
*/
27572757
"disabled": boolean;
2758+
/**
2759+
* Text that is placed under the select and displayed when an error is detected.
2760+
*/
2761+
"errorText"?: string;
27582762
/**
27592763
* The toggle icon to show when the select is open. If defined, the icon rotation behavior in `md` mode will be disabled. If undefined, `toggleIcon` will be used for when the select is both open and closed.
27602764
*/
@@ -2763,6 +2767,10 @@ export namespace Components {
27632767
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
27642768
*/
27652769
"fill"?: 'outline' | 'solid';
2770+
/**
2771+
* Text that is placed under the select and displayed when no error is detected.
2772+
*/
2773+
"helperText"?: string;
27662774
/**
27672775
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
27682776
*/
@@ -7568,6 +7576,10 @@ declare namespace LocalJSX {
75687576
* If `true`, the user cannot interact with the select.
75697577
*/
75707578
"disabled"?: boolean;
7579+
/**
7580+
* Text that is placed under the select and displayed when an error is detected.
7581+
*/
7582+
"errorText"?: string;
75717583
/**
75727584
* The toggle icon to show when the select is open. If defined, the icon rotation behavior in `md` mode will be disabled. If undefined, `toggleIcon` will be used for when the select is both open and closed.
75737585
*/
@@ -7576,6 +7588,10 @@ declare namespace LocalJSX {
75767588
* The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available in `md` mode.
75777589
*/
75787590
"fill"?: 'outline' | 'solid';
7591+
/**
7592+
* Text that is placed under the select and displayed when no error is detected.
7593+
*/
7594+
"helperText"?: string;
75797595
/**
75807596
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
75817597
*/

core/src/components/select/select.scss

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,34 @@ button {
340340
display: none;
341341
}
342342

343+
// Select Hint Text
344+
// ----------------------------------------------------------------
345+
346+
/**
347+
* Error text should only be shown when .ion-invalid is
348+
* present on the input. Otherwise the helper text should
349+
* be shown.
350+
*/
351+
.input-bottom .error-text {
352+
display: none;
353+
354+
color: var(--highlight-color-invalid);
355+
}
356+
357+
.input-bottom .helper-text {
358+
display: block;
359+
360+
color: #{$text-color-step-450};
361+
}
362+
363+
:host(.ion-touched.ion-invalid) .input-bottom .error-text {
364+
display: block;
365+
}
366+
367+
:host(.ion-touched.ion-invalid) .input-bottom .helper-text {
368+
display: none;
369+
}
370+
343371
// Select Native Wrapper
344372
// ----------------------------------------------------------------
345373

core/src/components/select/select.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,15 @@ import type { SelectChangeEventDetail, SelectInterface, SelectCompareFn } from '
5252
})
5353
export class Select implements ComponentInterface {
5454
private inputId = `ion-sel-${selectIds++}`;
55+
private helperTextId = `${this.inputId}-helper-text`;
56+
private errorTextId = `${this.inputId}-error-text`;
5557
private overlay?: OverlaySelect;
5658
private focusEl?: HTMLButtonElement;
5759
private mutationO?: MutationObserver;
5860
private inheritedAttributes: Attributes = {};
5961
private nativeWrapperEl: HTMLElement | undefined;
6062
private notchSpacerEl: HTMLElement | undefined;
63+
6164

6265
private notchController?: NotchController;
6366

@@ -98,6 +101,16 @@ export class Select implements ComponentInterface {
98101
*/
99102
@Prop() fill?: 'outline' | 'solid';
100103

104+
/**
105+
* Text that is placed under the select and displayed when no error is detected.
106+
*/
107+
@Prop() helperText?: string;
108+
109+
/**
110+
* Text that is placed under the select and displayed when an error is detected.
111+
*/
112+
@Prop() errorText?: string;
113+
101114
/**
102115
* The interface the select should use: `action-sheet`, `popover`, `alert`, or `modal`.
103116
*/
@@ -714,6 +727,36 @@ export class Select implements ComponentInterface {
714727
return this.getText() !== '';
715728
}
716729

730+
/**
731+
* Renders the helper text or error text values
732+
*/
733+
private renderHintText() {
734+
const { helperText, errorText, helperTextId, errorTextId } = this;
735+
736+
return [
737+
<div id={helperTextId} class="helper-text">
738+
{helperText}
739+
</div>,
740+
<div id={errorTextId} class="error-text">
741+
{errorText}
742+
</div>,
743+
];
744+
}
745+
746+
private getHintTextID(): string | undefined {
747+
const { el, helperText, errorText, helperTextId, errorTextId } = this;
748+
749+
if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
750+
return errorTextId;
751+
}
752+
753+
if (helperText) {
754+
return helperTextId;
755+
}
756+
757+
return undefined;
758+
}
759+
717760
private get childOpts() {
718761
return Array.from(this.el.querySelectorAll('ion-select-option'));
719762
}
@@ -812,6 +855,33 @@ export class Select implements ComponentInterface {
812855
this.ionBlur.emit();
813856
};
814857

858+
/**
859+
* Responsible for rendering helper text and
860+
* error text. This element should only
861+
* be rendered if hint text is set.
862+
*/
863+
private renderBottomContent() {
864+
const { helperText, errorText } = this;
865+
866+
/**
867+
* undefined and empty string values should
868+
* be treated as not having helper/error text.
869+
*/
870+
const hasHintText = !!helperText || !!errorText;
871+
console.log(`HelperText: ${helperText}`);
872+
console.log(`errorText: ${errorText}`);
873+
if (!hasHintText) {
874+
console.log("No text");
875+
return;
876+
}
877+
878+
return (
879+
<div class="input-bottom">
880+
{this.renderHintText()}
881+
</div>
882+
);
883+
}
884+
815885
private renderLabel() {
816886
const { label } = this;
817887

@@ -1069,6 +1139,7 @@ export class Select implements ComponentInterface {
10691139
{hasFloatingOrStackedLabel && this.renderSelectIcon()}
10701140
{shouldRenderHighlight && <div class="select-highlight"></div>}
10711141
</label>
1142+
{this.renderBottomContent()}
10721143
</Host>
10731144
);
10741145
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Input - Bottom Content</title>
6+
<meta
7+
name="viewport"
8+
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
9+
/>
10+
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
11+
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
12+
<script src="../../../../../scripts/testing/scripts.js"></script>
13+
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
14+
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
15+
<style>
16+
.grid {
17+
display: grid;
18+
grid-template-columns: repeat(3, minmax(250px, 1fr));
19+
grid-row-gap: 20px;
20+
grid-column-gap: 20px;
21+
}
22+
h2 {
23+
font-size: 12px;
24+
font-weight: normal;
25+
26+
color: #6f7378;
27+
28+
margin-top: 10px;
29+
}
30+
@media screen and (max-width: 800px) {
31+
.grid {
32+
grid-template-columns: 1fr;
33+
padding: 0;
34+
}
35+
}
36+
37+
ion-input.custom-error-color {
38+
--highlight-color-invalid: purple;
39+
}
40+
</style>
41+
</head>
42+
43+
<body>
44+
<ion-app>
45+
<ion-header>
46+
<ion-toolbar>
47+
<ion-title>select - Bottom Content</ion-title>
48+
</ion-toolbar>
49+
</ion-header>
50+
51+
<ion-content id="content" class="ion-padding">
52+
<div class="grid">
53+
54+
<div class="grid-item">
55+
<h2>Select with Helper</h2>
56+
<ion-select class="ion-touched ion-invalid" helperText="Select a fruit"
57+
errorText="No fruit selected">
58+
<div slot="label">Favorite Fruit <ion-text color="danger">(Required)</ion-text></div>
59+
<ion-select-option value="apple">Apple</ion-select-option>
60+
<ion-select-option value="bananna">Bananna</ion-select-option>
61+
</ion-select>
62+
</div>
63+
64+
<div class="grid-item">
65+
<h2>Counter with Error</h2>
66+
<ion-input
67+
class="ion-touched ion-invalid"
68+
label="Email"
69+
counter="true"
70+
maxlength="100"
71+
error-text="Please enter a valid email"
72+
></ion-input>
73+
</div>
74+
</div>
75+
76+
<script>
77+
</script>
78+
</ion-content>
79+
</ion-app>
80+
</body>
81+
</html>

0 commit comments

Comments
 (0)