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"; 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"; import { SelectModule } from "../select"; import { I18nMockService } from "../utils/i18n-mock.service"; import { countries } from "./countries"; export default { title: "Component Library/Form", decorators: [ moduleMetadata({ imports: [ FormsModule, ReactiveFormsModule, FormFieldModule, InputModule, ButtonModule, FormControlModule, CheckboxModule, RadioButtonModule, SelectModule, MultiSelectModule, ], providers: [ { provide: I18nService, useFactory: () => { return new I18nMockService({ selectPlaceholder: "-- Select --", required: "required", checkboxRequired: "Option is required", inputRequired: "Input is required.", 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.", }); }, }, ], }), ], parameters: { design: { type: "figma", url: "https://www.figma.com/design/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=13213-55392&t=b5tDKylm5sWm2yKo-4", }, }, } as Meta; const fb = new FormBuilder(); const exampleFormObj = fb.group({ name: ["", [Validators.required]], email: ["", [Validators.required, Validators.email, forbiddenCharacters(["#"])]], country: [undefined as string | undefined, [Validators.required]], groups: [], terms: [false, [Validators.requiredTrue]], updates: ["yes"], age: [null, [Validators.min(0), Validators.max(150)]], }); type Story = StoryObj; export const FullExample: Story = { render: (args) => ({ props: { formObj: exampleFormObj, submit: () => exampleFormObj.markAllAsTouched(), ...args, }, template: /*html*/ `
`, }), args: { countries, baseItems: [ { id: "1", listName: "Group 1", labelName: "Group 1", icon: "bwi-family" }, { id: "2", listName: "Group 2", labelName: "Group 2", icon: "bwi-family" }, { id: "3", listName: "Group 3", labelName: "Group 3", icon: "bwi-family" }, { id: "4", listName: "Group 4", labelName: "Group 4", icon: "bwi-family" }, { id: "5", listName: "Group 5", labelName: "Group 5", icon: "bwi-family" }, { id: "6", listName: "Group 6", labelName: "Group 6", icon: "bwi-family" }, { id: "7", listName: "Group 7", labelName: "Group 7", icon: "bwi-family" }, ], }, }; 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*/ ` `, }), play: async (context) => { const canvas = context.canvasElement; const submitButton = getByText(canvas, "Submit"); await userEvent.click(submitButton); }, };