Skip to content

Commit c0b9336

Browse files
docs(select): add helperText and errorText section (#4029)
Co-authored-by: Brandy Smith <[email protected]>
1 parent 2d9c682 commit c0b9336

File tree

8 files changed

+344
-0
lines changed

8 files changed

+344
-0
lines changed

docs/api/select.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,16 @@ import TypeaheadExample from '@site/static/usage/v8/select/typeahead/index.md';
253253

254254
<TypeaheadExample />
255255

256+
## Helper & Error Text
257+
258+
Helper and error text can be used inside of a select with the `helperText` and `errorText` property. The error text will not be displayed unless the `ion-invalid` and `ion-touched` classes are added to the `ion-select`. This ensures errors are not shown before the user has a chance to enter data.
259+
260+
In Angular, this is done automatically through form validation. In JavaScript, React and Vue, the class needs to be manually added based on your own validation.
261+
262+
import HelperError from '@site/static/usage/v8/select/helper-error/index.md';
263+
264+
<HelperError />
265+
256266
## Interfaces
257267

258268
### SelectChangeEventDetail
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
```html
2+
<form [formGroup]="myForm" (ngSubmit)="onSubmit()">
3+
<ion-select
4+
formControlName="favFruit"
5+
label="Favorite fruit"
6+
placeholder="Select fruit"
7+
helperText="Select your favorite fruit"
8+
errorText="This field is required"
9+
>
10+
<ion-select-option value="apple">Apple</ion-select-option>
11+
<ion-select-option value="banana">Banana</ion-select-option>
12+
<ion-select-option value="orange">Orange</ion-select-option>
13+
</ion-select>
14+
15+
<br />
16+
17+
<ion-button type="submit" size="small">Submit</ion-button>
18+
</form>
19+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
```ts
2+
import { Component } from '@angular/core';
3+
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
4+
import { IonSelect, IonSelectOption, IonButton } from '@ionic/angular/standalone';
5+
6+
@Component({
7+
selector: 'app-example',
8+
standalone: true,
9+
imports: [IonSelect, IonSelectOption, IonButton, ReactiveFormsModule],
10+
templateUrl: './example.component.html',
11+
styleUrl: './example.component.css',
12+
})
13+
export class ExampleComponent {
14+
myForm: FormGroup;
15+
16+
constructor(private fb: FormBuilder) {
17+
this.myForm = this.fb.group({
18+
favFruit: ['', Validators.required],
19+
});
20+
}
21+
22+
onSubmit() {
23+
// Mark the control as touched to trigger the error message.
24+
// This is needed if the user submits the form without interacting
25+
// with the select.
26+
this.myForm.get('favFruit')!.markAsTouched();
27+
}
28+
}
29+
```
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Input</title>
7+
<link rel="stylesheet" href="../../common.css" />
8+
<script src="../../common.js"></script>
9+
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@8/dist/ionic/ionic.esm.js"></script>
10+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@8/css/ionic.bundle.css" />
11+
</head>
12+
13+
<body>
14+
<div class="container">
15+
<form id="my-form">
16+
<ion-select
17+
label="Favorite fruit"
18+
placeholder="Select fruit"
19+
helper-text="Select your favorite fruit"
20+
error-text="This field is required"
21+
>
22+
<ion-select-option value="apple">Apple</ion-select-option>
23+
<ion-select-option value="banana">Banana</ion-select-option>
24+
<ion-select-option value="orange">Orange</ion-select-option>
25+
</ion-select>
26+
27+
<br />
28+
29+
<ion-button type="submit" size="small">Submit</ion-button>
30+
</form>
31+
</div>
32+
33+
<script>
34+
const form = document.getElementById('my-form');
35+
const favFruit = form.querySelector('ion-select');
36+
37+
form.addEventListener('submit', (event) => submit(event));
38+
favFruit.addEventListener('ionChange', (event) => validateSelect(event));
39+
favFruit.addEventListener('ionBlur', () => onIonBlur({ detail: { value: favFruit.value } }));
40+
41+
const validateSelect = (event) => {
42+
if (!event.detail.value) {
43+
favFruit.classList.add('ion-invalid');
44+
favFruit.classList.remove('ion-valid');
45+
} else {
46+
favFruit.classList.remove('ion-invalid');
47+
favFruit.classList.add('ion-valid');
48+
}
49+
};
50+
51+
const markTouched = () => {
52+
favFruit.classList.add('ion-touched');
53+
};
54+
55+
const onIonBlur = (event) => {
56+
markTouched();
57+
validateSelect(event);
58+
};
59+
60+
const submit = (event) => {
61+
event.preventDefault();
62+
63+
markTouched();
64+
validateSelect({ detail: { value: favFruit.value } });
65+
};
66+
</script>
67+
</body>
68+
</html>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Playground from '@site/src/components/global/Playground';
2+
3+
import javascript from './javascript.md';
4+
import react from './react.md';
5+
import vue from './vue.md';
6+
7+
import angular_example_component_html from './angular/example_component_html.md';
8+
import angular_example_component_ts from './angular/example_component_ts.md';
9+
10+
<Playground
11+
version="8"
12+
size="300px"
13+
code={{
14+
javascript,
15+
react,
16+
vue,
17+
angular: {
18+
files: {
19+
'src/app/example.component.html': angular_example_component_html,
20+
'src/app/example.component.ts': angular_example_component_ts,
21+
},
22+
},
23+
}}
24+
src="usage/v8/select/helper-error/demo.html"
25+
/>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
```html
2+
<form id="my-form">
3+
<ion-select
4+
label="Favorite fruit"
5+
placeholder="Select fruit"
6+
helper-text="Select your favorite fruit"
7+
error-text="This field is required"
8+
>
9+
<ion-select-option value="apple">Apple</ion-select-option>
10+
<ion-select-option value="banana">Banana</ion-select-option>
11+
<ion-select-option value="orange">Orange</ion-select-option>
12+
</ion-select>
13+
14+
<br />
15+
16+
<ion-button type="submit" size="small">Submit</ion-button>
17+
</form>
18+
19+
<script>
20+
const form = document.getElementById('my-form');
21+
const favFruit = form.querySelector('ion-select');
22+
23+
form.addEventListener('submit', (event) => submit(event));
24+
favFruit.addEventListener('ionChange', (event) => validateSelect(event));
25+
favFruit.addEventListener('ionBlur', () => onIonBlur({ detail: { value: favFruit.value } }));
26+
27+
const validateSelect = (event) => {
28+
if (!event.detail.value) {
29+
favFruit.classList.add('ion-invalid');
30+
favFruit.classList.remove('ion-valid');
31+
} else {
32+
favFruit.classList.remove('ion-invalid');
33+
favFruit.classList.add('ion-valid');
34+
}
35+
};
36+
37+
const markTouched = () => {
38+
favFruit.classList.add('ion-touched');
39+
};
40+
41+
const onIonBlur = (event) => {
42+
markTouched();
43+
validateSelect(event);
44+
};
45+
46+
const submit = (event) => {
47+
event.preventDefault();
48+
49+
markTouched();
50+
validateSelect({ detail: { value: favFruit.value } });
51+
};
52+
</script>
53+
```
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
```tsx
2+
import React, { useRef, useState } from 'react';
3+
import { IonSelect, IonSelectOption, IonButton, SelectCustomEvent } from '@ionic/react';
4+
5+
function Example() {
6+
const [isTouched, setIsTouched] = useState<boolean>(false);
7+
const [isValid, setIsValid] = useState<boolean | undefined>();
8+
9+
const favFruitRef = useRef<HTMLIonSelectElement>(null);
10+
11+
const validateSelect = (event: SelectCustomEvent<{ value: string }>) => {
12+
setIsValid(event.detail.value ? true : false);
13+
};
14+
15+
const markTouched = () => {
16+
setIsTouched(true);
17+
};
18+
19+
const onIonBlur = () => {
20+
markTouched();
21+
22+
if (favFruitRef.current) {
23+
validateSelect({ detail: { value: favFruitRef.current.value } } as SelectCustomEvent<{
24+
value: string;
25+
}>);
26+
}
27+
};
28+
29+
const submit = (event: React.FormEvent<HTMLFormElement>) => {
30+
event.preventDefault();
31+
32+
markTouched();
33+
34+
if (favFruitRef.current) {
35+
validateSelect({ detail: { value: favFruitRef.current.value } } as SelectCustomEvent<{
36+
value: string;
37+
}>);
38+
}
39+
};
40+
41+
return (
42+
<>
43+
<form onSubmit={submit}>
44+
<IonSelect
45+
ref={favFruitRef}
46+
label="Favorite fruit"
47+
placeholder="Select fruit"
48+
className={`${isValid ? 'ion-valid' : ''} ${isValid === false ? 'ion-invalid' : ''} ${
49+
isTouched ? 'ion-touched' : ''
50+
}`}
51+
helperText="Select your favorite fruit"
52+
errorText="This field is required"
53+
onIonChange={(event) => validateSelect(event)}
54+
onIonBlur={onIonBlur}
55+
>
56+
<IonSelectOption value="apple">Apple</IonSelectOption>
57+
<IonSelectOption value="banana">Banana</IonSelectOption>
58+
<IonSelectOption value="orange">Orange</IonSelectOption>
59+
</IonSelect>
60+
61+
<br />
62+
63+
<IonButton type="submit" size="small">
64+
Submit
65+
</IonButton>
66+
</form>
67+
</>
68+
);
69+
}
70+
71+
export default Example;
72+
```
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
```html
2+
<template>
3+
<form @submit.prevent="submit">
4+
<ion-select
5+
v-model="favFruit"
6+
label="Favorite fruit"
7+
placeholder="Select fruit"
8+
helper-text="Select your favorite fruit"
9+
error-text="This field is required"
10+
@ionChange="validateSelect"
11+
@ionBlur="onIonBlur"
12+
:class="{ 'ion-valid': isValid, 'ion-invalid': isValid === false, 'ion-touched': isTouched }"
13+
>
14+
<ion-select-option value="apple">Apple</ion-select-option>
15+
<ion-select-option value="banana">Banana</ion-select-option>
16+
<ion-select-option value="orange">Orange</ion-select-option>
17+
</ion-select>
18+
19+
<br />
20+
21+
<ion-button type="submit" size="small">Submit</ion-button>
22+
</form>
23+
</template>
24+
25+
<script lang="ts">
26+
import { defineComponent, ref } from 'vue';
27+
import { IonSelect, IonSelectOption, IonButton, SelectCustomEvent } from '@ionic/vue';
28+
29+
export default defineComponent({
30+
components: {
31+
IonSelect,
32+
IonSelectOption,
33+
IonButton,
34+
},
35+
setup() {
36+
const favFruit = ref('');
37+
const isTouched = ref(false);
38+
const isValid = ref<boolean | undefined>();
39+
40+
const validateSelect = (event: SelectCustomEvent<{ value: string }>) => {
41+
isValid.value = event.detail.value ? true : false;
42+
};
43+
44+
const markTouched = () => {
45+
isTouched.value = true;
46+
};
47+
48+
const onIonBlur = () => {
49+
markTouched();
50+
validateSelect({ detail: { value: favFruit.value } } as SelectCustomEvent<{ value: string }>);
51+
};
52+
53+
const submit = () => {
54+
markTouched();
55+
validateSelect({ detail: { value: favFruit.value } } as SelectCustomEvent<{ value: string }>);
56+
};
57+
58+
return {
59+
favFruit,
60+
isTouched,
61+
isValid,
62+
validateSelect,
63+
submit,
64+
};
65+
},
66+
});
67+
</script>
68+
```

0 commit comments

Comments
 (0)