mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[CL-124] Add validator stories (#15400)
* adding validation stories * add one story for all validations * fix form field story import * move validation docs * fix maxValue default value * add play function to submit form
This commit is contained in:
@@ -72,6 +72,7 @@ export default {
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
imports: [
|
||||
A11yTitleDirective,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
FormFieldModule,
|
||||
@@ -88,7 +89,6 @@ export default {
|
||||
TextFieldModule,
|
||||
BadgeModule,
|
||||
],
|
||||
declarations: [A11yTitleDirective],
|
||||
providers: [
|
||||
{
|
||||
provide: I18nService,
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
AbstractControl,
|
||||
FormBuilder,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
ValidationErrors,
|
||||
ValidatorFn,
|
||||
Validators,
|
||||
} from "@angular/forms";
|
||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { userEvent, getByText } from "@storybook/test";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -15,6 +8,7 @@ import { ButtonModule } from "../button";
|
||||
import { CheckboxModule } from "../checkbox";
|
||||
import { FormControlModule } from "../form-control";
|
||||
import { FormFieldModule } from "../form-field";
|
||||
import { trimValidator, forbiddenCharacters } from "../form-field/bit-validators";
|
||||
import { InputModule } from "../input/input.module";
|
||||
import { MultiSelectModule } from "../multi-select";
|
||||
import { RadioButtonModule } from "../radio-button";
|
||||
@@ -48,13 +42,19 @@ export default {
|
||||
required: "required",
|
||||
checkboxRequired: "Option is required",
|
||||
inputRequired: "Input is required.",
|
||||
inputEmail: "Input is not an email-address.",
|
||||
inputEmail: "Input is not an email address.",
|
||||
inputForbiddenCharacters: (char) =>
|
||||
`The following characters are not allowed: "${char}"`,
|
||||
inputMinValue: (min) => `Input value must be at least ${min}.`,
|
||||
inputMaxValue: (max) => `Input value must not exceed ${max}.`,
|
||||
inputMinLength: (min) => `Input value must be at least ${min} characters long.`,
|
||||
inputMaxLength: (max) => `Input value must not exceed ${max} characters in length.`,
|
||||
inputTrimValidator: `Input must not contain only whitespace.`,
|
||||
multiSelectPlaceholder: "-- Type to Filter --",
|
||||
multiSelectLoading: "Retrieving options...",
|
||||
multiSelectNotFound: "No items found",
|
||||
multiSelectClearAll: "Clear all",
|
||||
fieldsNeedAttention: "__$1__ field(s) above need your attention.",
|
||||
});
|
||||
},
|
||||
},
|
||||
@@ -72,7 +72,7 @@ export default {
|
||||
const fb = new FormBuilder();
|
||||
const exampleFormObj = fb.group({
|
||||
name: ["", [Validators.required]],
|
||||
email: ["", [Validators.required, Validators.email, forbiddenNameValidator(/bit/i)]],
|
||||
email: ["", [Validators.required, Validators.email, forbiddenCharacters(["#"])]],
|
||||
country: [undefined as string | undefined, [Validators.required]],
|
||||
groups: [],
|
||||
terms: [false, [Validators.requiredTrue]],
|
||||
@@ -80,14 +80,6 @@ const exampleFormObj = fb.group({
|
||||
age: [null, [Validators.min(0), Validators.max(150)]],
|
||||
});
|
||||
|
||||
// Custom error message, `message` is shown as the error message
|
||||
function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
return (control: AbstractControl): ValidationErrors | null => {
|
||||
const forbidden = nameRe.test(control.value);
|
||||
return forbidden ? { forbiddenName: { message: "forbiddenName" } } : null;
|
||||
};
|
||||
}
|
||||
|
||||
type Story = StoryObj;
|
||||
|
||||
export const FullExample: Story = {
|
||||
@@ -177,3 +169,95 @@ export const FullExample: Story = {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const showValidationsFormObj = fb.group({
|
||||
required: ["", [Validators.required]],
|
||||
whitespace: [" ", trimValidator],
|
||||
email: ["example?bad-email", [Validators.email]],
|
||||
minLength: ["Hello", [Validators.minLength(8)]],
|
||||
maxLength: ["Hello there", [Validators.maxLength(8)]],
|
||||
minValue: [9, [Validators.min(10)]],
|
||||
maxValue: [15, [Validators.max(10)]],
|
||||
forbiddenChars: ["Th!$ value cont#in$ forbidden char$", forbiddenCharacters(["#", "!", "$"])],
|
||||
});
|
||||
|
||||
export const Validations: Story = {
|
||||
render: (args) => ({
|
||||
props: {
|
||||
formObj: showValidationsFormObj,
|
||||
submit: () => showValidationsFormObj.markAllAsTouched(),
|
||||
...args,
|
||||
},
|
||||
template: /*html*/ `
|
||||
<form [formGroup]="formObj" (ngSubmit)="submit()">
|
||||
<bit-form-field>
|
||||
<bit-label>Required validation</bit-label>
|
||||
<input bitInput formControlName="required" />
|
||||
<bit-hint>This field is required. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>Email validation</bit-label>
|
||||
<input bitInput type="email" formControlName="email" />
|
||||
<bit-hint>This field contains a malformed email address. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>Min length validation</bit-label>
|
||||
<input bitInput formControlName="minLength" />
|
||||
<bit-hint>Value must be at least 8 characters. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>Max length validation</bit-label>
|
||||
<input bitInput formControlName="maxLength" />
|
||||
<bit-hint>Value must be less then 8 characters. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>Min number value validation</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="number"
|
||||
formControlName="minValue"
|
||||
/>
|
||||
<bit-hint>Value must be greater than 10. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>Max number value validation</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
type="number"
|
||||
formControlName="maxValue"
|
||||
/>
|
||||
<bit-hint>Value must be less than than 10. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>Forbidden characters validation</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
formControlName="forbiddenChars"
|
||||
/>
|
||||
<bit-hint>Value must not contain '#', '!' or '$'. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-field>
|
||||
<bit-label>White space validation</bit-label>
|
||||
<input bitInput formControlName="whitespace" />
|
||||
<bit-hint>This input contains only white space. Submit form or blur input to see error</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<button type="submit" bitButton buttonType="primary">Submit</button>
|
||||
<bit-error-summary [formGroup]="formObj"></bit-error-summary>
|
||||
</form>
|
||||
`,
|
||||
}),
|
||||
play: async (context) => {
|
||||
const canvas = context.canvasElement;
|
||||
const submitButton = getByText(canvas, "Submit");
|
||||
|
||||
await userEvent.click(submitButton);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -142,8 +142,20 @@ If a checkbox group has more than 4 options a
|
||||
|
||||
<Canvas of={checkboxStories.Default} />
|
||||
|
||||
## Validation messages
|
||||
|
||||
These are examples of our default validation error messages:
|
||||
|
||||
<Canvas of={formStories.Validations} />
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Icon Buttons in Form Fields
|
||||
|
||||
When adding prefix or suffix icon buttons to a form field, be sure to use the `appA11yTitle`
|
||||
directive to provide a label for screenreaders. Typically, the label should follow this pattern:
|
||||
`{Action} {field label}`, i.e. "Copy username".
|
||||
|
||||
### Required Fields
|
||||
|
||||
- Use "(required)" in the label of each required form field styled the same as the field's helper
|
||||
@@ -152,12 +164,6 @@ If a checkbox group has more than 4 options a
|
||||
helper text.
|
||||
- **Example:** "Billing Email is required if owned by a business".
|
||||
|
||||
### Icon Buttons in Form Fields
|
||||
|
||||
When adding prefix or suffix icon buttons to a form field, be sure to use the `appA11yTitle`
|
||||
directive to provide a label for screenreaders. Typically, the label should follow this pattern:
|
||||
`{Action} {field label}`, i.e. "Copy username".
|
||||
|
||||
### Form Field Errors
|
||||
|
||||
- When a resting field is filled out, validation is triggered when the user de-focuses the field
|
||||
|
||||
Reference in New Issue
Block a user