import React from 'react'; import * as Yup from 'yup'; import {withKnobs, boolean} from '@storybook/addon-knobs'; import {action} from '@storybook/addon-actions'; import {Card, Grid} from '@material-ui/core'; import {SectionHeader} from 'scplus-shared-components'; import FormValidationMessage from './components/FormValidationMessage'; import {createDocsPage} from './utils/createDocsPage'; import * as markdown from '../notes/SQForm.md'; import { SQForm, SQFormTextarea, SQFormTextField, SQFormButton, SQFormAutocomplete, SQFormCheckbox, SQFormDropdown, SQFormReadOnlyField, SQFormResetButtonWithConfirmation, SQFormInclusionList, SQFormInclusionListItem, SQFormMultiSelect, SQFormRadioButtonGroup, SQFormCheckboxGroup, SQFormMaskedTextField, SQFormMultiValue, MASKS, } from '../src'; import type {FieldArrayRenderProps, FormikHelpers} from 'formik'; export default { title: 'Forms/SQForm', decorators: [withKnobs], parameters: { docs: {page: createDocsPage({markdown})}, }, }; const MOCK_AUTOCOMPLETE_OPTIONS = Array.from(new Array(10000)) .map(() => { const randomValue = random(10 + Math.ceil(Math.random() * 20)); return { label: randomValue, value: randomValue, isDisabled: Math.random() > 0.8, }; }) .sort((a, b) => a.label.toUpperCase().localeCompare(b.label.toUpperCase())); const ACTIONS_AUTOCOMPLETE_OPTIONS = [ {label: 'Action 1', value: 1}, {label: 'Action 2', value: 2}, ]; const MOCK_FORM_ENTITY = { firstName: '', lastName: 'Doe', city: '', age: '', state: '', tenThousandOptions: '', note: '', preferredPet: '', warrantyOptions: [], warrantyOptionsSelectAll: false, favoriteColors: [], }; const MOCK_ACTIONS_FORM_ENTITY = { actions: 2, note: '', }; const MOCK_FORM_WITH_BOOLEANS_ENTITY = { ...MOCK_FORM_ENTITY, hobby: '', cool: false, lame: false, favoriteColors: [2, 4], }; const MOCK_FORM_FOR_FIELD_ARRAY = { ...MOCK_FORM_ENTITY, friends: ['Joe', 'Jane', 'Jack', 'Jill'], }; const MOCK_FORM_FOR_CHECKBOX_GROUP = { friends: ['Joe', 'Jane', 'Jack', 'Jill'], selectAll: false, }; const MOCK_STATE_OPTIONS = [ {label: 'Arizona', value: 'AZ'}, {label: 'Kansas', value: 'KS'}, {label: 'Missouri', value: 'MO'}, ]; const MOCK_FORM_FOR_MULTISELECT = { friends: [], }; const MOCK_FRIENDS_OPTIONS = [ {label: 'Joe', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jane', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jack', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jill', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'John', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jocelyn', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jacob', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jackson', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Josh', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Joseph', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jeremy', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Joel', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jonah', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Judah', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jimmy', value: random(10 + Math.ceil(Math.random() * 20))}, {label: 'Jessica', value: random(10 + Math.ceil(Math.random() * 20))}, ]; const MOCK_MULTI_VALUE_OPTIONS = [ {label: 'Green', value: 0}, {label: 'Red', value: 1}, {label: 'Orange', value: 2}, {label: 'Pink', value: 3}, {label: 'Purple', value: 4}, {label: 'Black', value: 5}, {label: 'White', value: 6}, {label: 'Blue', value: 7}, ]; const MOCK_FORM_MASKED_FIELDS = { phone: '', zip: '', currency: '', percent: '', email: '', date: '', ssn: '', custom: '', }; const RADIO_GROUP_OPTIONS = [ {label: 'Cat', value: 'cat'}, {label: 'Dog', value: 'dog'}, {label: 'Both', value: 'both', isDisabled: true}, ]; const CHECKBOX_GROUP_OPTIONS = [ {label: 'Glass', value: 1}, {label: 'Drivetrain', value: 2}, {label: 'Brakes', value: 3}, {label: 'Interior', value: 4, isDisabled: true}, ]; const handleSubmit = <TValues extends unknown>( values: TValues, actions: FormikHelpers<TValues> ) => { window.alert(JSON.stringify(values, null, 2)); actions.setSubmitting(false); actions.resetForm(); }; export const BasicForm = (): JSX.Element => { return ( <Card raised style={{padding: 16}}> <SQForm initialValues={MOCK_FORM_WITH_BOOLEANS_ENTITY} onSubmit={handleSubmit} muiGridProps={{spacing: 4}} > <SQFormTextField name="firstName" label="First name" size={3} maxCharacters={10} /> <SQFormTextField name="lastName" label="Last name" size={3} /> <SQFormReadOnlyField name="city" label="City" /> <SQFormReadOnlyField name="state" label="State" size={1} /> <SQFormAutocomplete name="tenThousandOptions" label="Ten Thousand Options" size={6} onInputChange={action('Update local state')} lockWidthToField={false} > {MOCK_AUTOCOMPLETE_OPTIONS} </SQFormAutocomplete> <SQFormTextField name="hobby" label="Hobby" size={4} /> <SQFormTextField name="age" label="Age" type="number" size={2} /> <SQFormDropdown name="state" label="State" displayEmpty={true} size={4}> {MOCK_STATE_OPTIONS} </SQFormDropdown> <SQFormCheckbox name="cool" label="Cool" /> <SQFormCheckbox name="lame" label="Lame" isDisabled={true} /> <SQFormRadioButtonGroup name="preferredPet" groupLabel="Cat or Dog?" shouldDisplayInRow={true} > {RADIO_GROUP_OPTIONS} </SQFormRadioButtonGroup> <SQFormCheckboxGroup name="warrantyOptions" groupLabel="Warranty Options" shouldDisplayInRow={true} shouldUseSelectAll={true} > {CHECKBOX_GROUP_OPTIONS} </SQFormCheckboxGroup> <SQFormMultiValue name="favoriteColors" label="Your Favorite Colors" size={4} > {MOCK_MULTI_VALUE_OPTIONS} </SQFormMultiValue> <Grid item sm={12}> <Grid container justifyContent="space-between"> <SQFormResetButtonWithConfirmation variant="outlined" confirmationContent="You are about to reset this form. Any unsaved info for this customer will be removed" > RESET </SQFormResetButtonWithConfirmation> <SQFormButton>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; export const FormWithValidation = (): JSX.Element => { const validationSchema = Yup.object({ firstName: Yup.string().required(), lastName: Yup.string().required(), city: Yup.string(), age: Yup.number().min(1, 'Age 1-65').max(65, 'Age 1-65').required(), state: Yup.string().required(), tenThousandOptions: Yup.string().required(), preferredPet: Yup.string().required(), warrantyOptions: Yup.array().required().min(1, 'One option required'), note: Yup.string().required(), favoriteColors: Yup.array() .of( Yup.lazy((value: unknown) => { return typeof value === 'number' ? Yup.number() : Yup.string(); }) as never ) .required() .min(1), }); return ( <Card raised style={{padding: 16}}> <SQForm initialValues={MOCK_FORM_ENTITY} onSubmit={handleSubmit} validationSchema={validationSchema} > <SQFormTextField name="firstName" label="First name" size={6} maxCharacters={10} /> <SQFormTextField name="lastName" label="Last name" size={6} /> <SQFormTextField name="city" label="City" size={3} /> <SQFormAutocomplete name="tenThousandOptions" label="Ten Thousand Options" size={6} > {MOCK_AUTOCOMPLETE_OPTIONS} </SQFormAutocomplete> <SQFormDropdown name="state" label="State" displayEmpty={true} size={5}> {MOCK_STATE_OPTIONS} </SQFormDropdown> <SQFormTextField name="age" label="Age" type="number" size={2} /> <SQFormTextarea name="note" label="Note" size={5} maxCharacters={100} /> <SQFormRadioButtonGroup name="preferredPet" groupLabel="Cat or Dog?" shouldDisplayInRow={true} > {RADIO_GROUP_OPTIONS} </SQFormRadioButtonGroup> <SQFormCheckboxGroup name="warrantyOptions" groupLabel="Warranty Options" shouldDisplayInRow={true} > {CHECKBOX_GROUP_OPTIONS} </SQFormCheckboxGroup> <SQFormMultiValue name="favoriteColors" label="Your Favorite Colors" size={4} > {MOCK_MULTI_VALUE_OPTIONS} </SQFormMultiValue> <Grid item sm={12}> <Grid container justifyContent="space-between"> <SQFormButton title="Reset" type="reset"> RESET </SQFormButton> <FormValidationMessage /> <SQFormButton>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; export const formWithFieldArray = (): JSX.Element => { return ( <Card raised style={{padding: 16}}> <SQForm initialValues={MOCK_FORM_FOR_FIELD_ARRAY} onSubmit={handleSubmit} muiGridProps={{spacing: 4}} > <SQFormTextField name="firstName" label="First name" size={3} /> <SQFormTextField name="lastName" label="Last name" size={3} /> <SQFormReadOnlyField name="city" label="City" /> <SQFormReadOnlyField name="state" label="State" size={1} /> <SQFormAutocomplete name="tenThousandOptions" label="Ten Thousand Options" size={6} > {MOCK_AUTOCOMPLETE_OPTIONS} </SQFormAutocomplete> <SQFormTextField name="hobby" label="Hobby" size={4} /> <SQFormTextField name="age" label="Age" size={2} /> <SQFormDropdown name="state" label="State" displayEmpty={true} size={4}> {MOCK_STATE_OPTIONS} </SQFormDropdown> <SQFormCheckbox name="cool" label="Cool" /> <SQFormCheckbox name="lame" label="Lame" isDisabled={true} /> <Grid item sm={12}> <Grid container justifyContent="flex-end"> <SQFormButton shouldRequireFieldUpdates={true}>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; // oftentimes this data might be fetched asynchronously so we'd need to memoize it inside the component const names = [ 'Jim', 'Jake', 'John', 'Jose', 'Jaipal', 'Joe', 'Jane', 'Jack', 'Jill', ]; export const formWithInclusionlist = (): JSX.Element => { return ( <Card raised style={{padding: 16}}> <SectionHeader title="Friends" /> <SQForm // the property you want to store the array of checked items determines the `name` prop below initialValues={MOCK_FORM_FOR_CHECKBOX_GROUP} onSubmit={handleSubmit} muiGridProps={{spacing: 4}} > {/* the group's `name` string should always match the item's `name` string */} <SQFormInclusionList name="friends" useSelectAll={true} selectAllData={names} // whatever you'd want 'select all' to include selectAllContainerProps={{ // MUI Grid container props, plus a style prop if you're feeling fancy direction: 'column', wrap: 'nowrap', style: { padding: '16px 16px 0 16px', }, }} selectAllProps={ // any props that a SQFormInclusionListItem accepts // realistically, these would only include `isDisabled`, `size`, `label`, { label: 'ALL THE PEEPS', } } > {(arrayHelpers: FieldArrayRenderProps): JSX.Element => { const {values} = arrayHelpers.form; return ( <Grid container direction="column" wrap="nowrap" style={{ height: 200, overflow: 'auto', padding: '0 16px', }} > {names.map((name) => { return ( <Grid item key={name}> <SQFormInclusionListItem name="friends" label={name} isChecked={values.friends.includes(name)} onChange={(e) => { if (e.target.checked) { arrayHelpers.push(name); } else { const idx = values.friends.indexOf(name); arrayHelpers.remove(idx); } }} /> </Grid> ); })} </Grid> ); }} </SQFormInclusionList> <Grid item sm={12}> <Grid container justifyContent="space-between"> <SQFormResetButtonWithConfirmation variant="outlined" confirmationContent="You are about to reset this form. Any unsaved info for this customer will be removed" > RESET </SQFormResetButtonWithConfirmation> <SQFormButton>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; export const basicFormWithMultiSelect = (): JSX.Element => { const validationSchema = Yup.object({ friends: Yup.array().of(Yup.string()).required().min(1, 'Required'), }); return ( <Card raised style={{padding: '16px', minWidth: '768px'}}> <SQForm initialValues={MOCK_FORM_FOR_MULTISELECT} onSubmit={handleSubmit} validationSchema={validationSchema} muiGridProps={{ spacing: 2, justifyContent: 'space-between', alignItems: 'center', }} > <SQFormMultiSelect name="friends" label="Friends" size={5} onChange={action('Friends selected')} useSelectAll={boolean('Use Select All', true)} > {MOCK_FRIENDS_OPTIONS} </SQFormMultiSelect> <Grid item style={{alignSelf: 'flex-end'}}> <SQFormButton>Submit</SQFormButton> </Grid> </SQForm> </Card> ); }; export const basicFormWithMaskedFields = (): JSX.Element => { const validationSchema = Yup.object({ phone: Yup.string() .required() .transform((value) => value.replace(/[^\d]/g, '')) .min(10, 'Phone number must be 10 digits'), zip: Yup.string() .required() .transform((value) => value.replace(/[^\d]/g, '')) .min(5, 'Zip code must be 5 digits'), currency: Yup.string().required(), }); return ( <Card raised style={{padding: '16px', minWidth: '768px'}}> <SQForm initialValues={MOCK_FORM_MASKED_FIELDS} onSubmit={handleSubmit} validationSchema={validationSchema} muiGridProps={{ spacing: 2, justifyContent: 'space-between', alignItems: 'center', }} > <SQFormMaskedTextField name="phone" label="Phone" size={4} mask={MASKS.phone} /> <SQFormMaskedTextField name="zip" label="Zip Code" size={4} mask={MASKS.zip} /> <SQFormMaskedTextField name="currency" label="Currency" size={4} mask={MASKS.currency} /> <SQFormMaskedTextField name="percent" label="Percent" size={4} mask={MASKS.percent} /> <SQFormMaskedTextField name="email" label="Email" size={4} mask={MASKS.email} /> <SQFormMaskedTextField name="date" label="Date" size={4} mask={MASKS.date} /> <SQFormMaskedTextField name="ssn" label="SSN" size={4} mask={MASKS.ssn} /> <SQFormMaskedTextField name="custom" label="Custom mask (ex: Canadian postal code)" size={4} placeholder="A1B 2C3" mask={[/[A-Z]/i, /\d/, /[A-Z]/i, ' ', /\d/, /[A-Z]/i, /\d/]} /> <Grid item sm={12}> <Grid container justifyContent="space-between"> <SQFormResetButtonWithConfirmation variant="outlined" confirmationContent="You are about to reset this form. Any unsaved info for this customer will be removed" > RESET </SQFormResetButtonWithConfirmation> <SQFormButton>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; export const basicFormWithCustomOnBlur = (): JSX.Element => { return ( <Card raised style={{padding: 16}}> <SQForm initialValues={MOCK_FORM_ENTITY} onSubmit={handleSubmit} muiGridProps={{spacing: 4}} > <SQFormTextField name="firstName" label="First name" size={3} onBlur={action('Blur event!')} /> <SQFormTextField name="lastName" label="Last name" size={3} onBlur={action('Blur event!')} /> <SQFormTextField name="city" label="City" size={2} onBlur={action('Blur event!')} /> <SQFormTextField name="age" label="Age" size={2} onBlur={action('Blur event!')} /> <SQFormDropdown name="state" label="State" displayEmpty={true} size={2} onBlur={action('Blur event!')} > {MOCK_STATE_OPTIONS} </SQFormDropdown> <Grid item sm={12}> <Grid container justifyContent="flex-end"> <SQFormButton>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; export const basicFormWithCustomOnChange = (): JSX.Element => { return ( <Card raised style={{padding: 16}}> <SQForm initialValues={MOCK_FORM_ENTITY} onSubmit={handleSubmit} muiGridProps={{spacing: 4}} > <SQFormTextField name="firstName" label="First name" size={3} onChange={action('Change event!')} /> <SQFormTextField name="lastName" label="Last name" size={3} onChange={action('Change event!')} /> <SQFormTextField name="city" label="City" size={2} onChange={action('Change event!')} /> <SQFormTextField name="age" label="Age" size={2} onChange={action('Change event!')} /> <SQFormDropdown name="state" label="State" displayEmpty={true} size={2} onChange={action('Change event!')} > {MOCK_STATE_OPTIONS} </SQFormDropdown> <Grid item sm={12}> <Grid container justifyContent="flex-end"> <SQFormButton>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; export const applyAnAction = (): JSX.Element => { const validationSchema = Yup.object({ actions: Yup.string().required(), }); return ( <Card raised style={{padding: '16px', minWidth: '768px'}}> <SQForm initialValues={MOCK_ACTIONS_FORM_ENTITY} onSubmit={handleSubmit} validationSchema={validationSchema} muiGridProps={{ spacing: 2, justifyContent: 'space-between', alignItems: 'center', }} > <SQFormAutocomplete name="actions" label="Actions with a default value" size={5} isDisabled={boolean('Disable autocomplete', true)} > {ACTIONS_AUTOCOMPLETE_OPTIONS} </SQFormAutocomplete> <SQFormTextarea name="note" label="Note" size={5} placeholder="Type to add note..." rows={2} rowsMax={2} /> <Grid item style={{alignSelf: 'flex-end'}}> <SQFormButton>Submit</SQFormButton> </Grid> </SQForm> </Card> ); }; export const SQFormCheckboxGroupExample = (): JSX.Element => { const initialValues = { warrantyOptions: ['brakes'], selectAll: false, }; const validationSchema = Yup.object({ warrantyOptions: Yup.array() .min(1, 'Must select at least 1 option') .required(), }); return ( <Card raised style={{padding: '16px', minWidth: '250px'}}> <SQForm initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit} > <SQFormCheckboxGroup name="warrantyOptions" groupLabel="Warranty Options" shouldDisplayInRow={boolean('Should display in row', false)} shouldUseSelectAll={boolean('Should use select all', false)} > {CHECKBOX_GROUP_OPTIONS} </SQFormCheckboxGroup> <Grid item sm={12}> <Grid container justifyContent="flex-end"> <SQFormButton>Submit</SQFormButton> </Grid> </Grid> </SQForm> </Card> ); }; export const ccaChecklist = (): JSX.Element => { const dropdownOptions = [ {label: 'Pitched', value: 'pitched'}, {label: 'Transferred', value: 'transferred'}, ]; const validationSchema = Yup.object({ dvh: Yup.string(), hra: Yup.string(), shield: Yup.string(), }); return ( <Card raised style={{padding: '16px', minWidth: '768px'}}> <SQForm initialValues={{ dvh: '', hra: 'pitched', shield: '', }} onSubmit={handleSubmit} validationSchema={validationSchema} muiGridProps={{ spacing: 2, justifyContent: 'space-between', alignItems: 'center', }} > <SQFormDropdown name="dvh" label="DVH" displayEmpty={true} size={2}> {dropdownOptions} </SQFormDropdown> <SQFormDropdown name="hra" label="HRA" displayEmpty={true} size={2}> {dropdownOptions} </SQFormDropdown> <SQFormDropdown name="shield" label="Reliashield" displayEmpty={true} size={2} > {dropdownOptions} </SQFormDropdown> <Grid item style={{alignSelf: 'flex-end'}}> <SQFormButton>Submit</SQFormButton> </Grid> </SQForm> </Card> ); }; function random(length: number) { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i += 1) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; }