import { keyframes, styled } from '@storybook/theming'; import { ErrorMessage, Field as FormikInput, Form as FormikForm, Formik, FormikProps, } from 'formik'; import React, { FC, HTMLAttributes, useCallback, useState } from 'react'; import { Icons, WithTooltip } from '@storybook/components'; const errorMap = { email: { required: { normal: 'Please enter your email address', tooltip: 'We do require an email address and a password as a minimum in order to be able to create an account for you to log in with', }, format: { normal: 'Please enter a correctly formatted email address', tooltip: 'Your email address is formatted incorrectly and is not correct - please double check for misspelling', }, }, password: { required: { normal: 'Please enter a password', tooltip: 'A password is required to create an account', }, length: { normal: 'Please enter a password of minimum 6 characters', tooltip: 'For security reasons we enforce a password length of minimum 6 characters - but have no other requirements', }, }, verifiedPassword: { required: { normal: 'Please verify your password', tooltip: 'Verification of your password is required to ensure no errors in the spelling of the password', }, match: { normal: 'Your passwords do not match', tooltip: 'Your verification password has to match your password to make sure you have not misspelled', }, }, }; // https://emailregex.com/ const email99RegExp = new RegExp( // eslint-disable-next-line no-useless-escape /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ ); export interface AccountFormResponse { success: boolean; } export interface AccountFormValues { email: string; password: string; } interface FormValues extends AccountFormValues { verifiedPassword: string; } interface FormErrors { email?: string; emailTooltip?: string; password?: string; passwordTooltip?: string; verifiedPassword?: string; verifiedPasswordTooltip?: string; } export type AccountFormProps = { passwordVerification?: boolean; onSubmit?: (values: AccountFormValues) => void; onTransactionStart?: (values: AccountFormValues) => void; onTransactionEnd?: (values: AccountFormResponse) => void; }; export const AccountForm: FC = ({ passwordVerification, onSubmit, onTransactionStart, onTransactionEnd, }) => { const [state, setState] = useState({ transacting: false, transactionSuccess: false, transactionFailure: false, }); const handleFormSubmit = useCallback( async ({ email, password }: FormValues, { setSubmitting, resetForm }) => { if (onSubmit) { onSubmit({ email, password }); } if (onTransactionStart) { onTransactionStart({ email, password }); } setSubmitting(true); setState({ ...state, transacting: true, }); await new Promise((r) => setTimeout(r, 2100)); const success = Math.random() < 1; if (onTransactionEnd) { onTransactionEnd({ success }); } setSubmitting(false); resetForm({ values: { email: '', password: '', verifiedPassword: '' } }); setState({ ...state, transacting: false, transactionSuccess: success === true, transactionFailure: success === false, }); }, [setState, onTransactionEnd, onTransactionStart] ); return ( Storybook icon <title>Storybook {!state.transactionSuccess && !state.transactionFailure && ( Create an account to join the Storybook community )} {state.transactionSuccess && !state.transactionFailure && (

Everything is perfect. Your account is ready and we should probably get you started!

So why don't you get started then?

{ setState({ transacting: false, transactionSuccess: false, transactionFailure: false, }); }} > Go back
)} {state.transactionFailure && !state.transactionSuccess && (

What a mess, this API is not working

Someone should probably have a stern talking to about this, but it won't be me - coz I'm gonna head out into the nice weather

{ setState({ transacting: false, transactionSuccess: false, transactionFailure: false, }); }} > Go back
)} {!state.transactionSuccess && !state.transactionFailure && ( { const errors: FormErrors = {}; if (!email) { errors.email = errorMap.email.required.normal; errors.emailTooltip = errorMap.email.required.tooltip; } else { const validEmail = email.match(email99RegExp); if (validEmail === null) { errors.email = errorMap.email.format.normal; errors.emailTooltip = errorMap.email.format.tooltip; } } if (!password) { errors.password = errorMap.password.required.normal; errors.passwordTooltip = errorMap.password.required.tooltip; } else if (password.length < 6) { errors.password = errorMap.password.length.normal; errors.passwordTooltip = errorMap.password.length.tooltip; } if (passwordVerification && !verifiedPassword) { errors.verifiedPassword = errorMap.verifiedPassword.required.normal; errors.verifiedPasswordTooltip = errorMap.verifiedPassword.required.tooltip; } else if (passwordVerification && password !== verifiedPassword) { errors.verifiedPassword = errorMap.verifiedPassword.match.normal; errors.verifiedPasswordTooltip = errorMap.verifiedPassword.match.tooltip; } return errors; }} > {({ errors: _errors, isSubmitting, dirty }: FormikProps) => { const errors = _errors as FormErrors; return (
{({ field }: { field: HTMLAttributes }) => ( <> {errors.email && ( {errors.emailTooltip}} > )} )} {({ field }: { field: HTMLAttributes }) => ( )} {errors.password && ( {errors.passwordTooltip}}> )} {passwordVerification && ( {({ field }: { field: HTMLAttributes }) => ( )} {errors.verifiedPassword && ( {errors.verifiedPasswordTooltip}} > )} )} Create Account Reset
); }}
)}
); }; const Wrapper = styled.section(({ theme }) => ({ fontFamily: theme.typography.fonts.base, display: 'flex', flexDirection: 'column', alignItems: 'center', width: 450, padding: 32, backgroundColor: theme.background.content, borderRadius: 7, })); const Brand = styled.div({ display: 'flex', alignItems: 'center', justifyContent: 'center', }); const Title = styled.svg({ height: 40, zIndex: 1, left: -32, position: 'relative', }); const logoAnimation = keyframes({ '0': { transform: 'rotateY(0deg)', transformOrigin: '50% 5% 0', }, '100%': { transform: 'rotateY(360deg)', transformOrigin: '50% 5% 0', }, }); interface LogoProps { transacting: boolean; } const Logo = styled.svg( ({ transacting }) => transacting && { animation: `${logoAnimation} 1250ms both infinite`, }, { height: 40, zIndex: 10, marginLeft: 32 } ); const Introduction = styled.p({ marginTop: 20, textAlign: 'center', }); const Content = styled.div({ display: 'flex', alignItems: 'flex-start', justifyContent: 'center', width: 350, minHeight: 189, marginTop: 8, }); const Presentation = styled.div({ textAlign: 'center', }); const Form = styled(FormikForm)({ width: '100%', alignSelf: 'flex-start', '&[aria-disabled="true"]': { opacity: 0.6, }, }); const FieldWrapper = styled.div({ display: 'flex', flexDirection: 'column', justifyContent: 'stretch', marginBottom: 10, }); const Label = styled.label({ fontSize: 13, fontWeight: 500, marginBottom: 6, }); const Input = styled.input(({ theme }) => ({ fontSize: 14, color: theme.color.defaultText, padding: '10px 15px', borderRadius: 4, appearance: 'none', outline: 'none', border: '0 none', boxShadow: 'rgb(0 0 0 / 10%) 0px 0px 0px 1px inset', '&:focus': { boxShadow: 'rgb(30 167 253) 0px 0px 0px 1px inset', }, '&:active': { boxShadow: 'rgb(30 167 253) 0px 0px 0px 1px inset', }, '&[aria-invalid="true"]': { boxShadow: 'rgb(255 68 0) 0px 0px 0px 1px inset', }, })); const ErrorWrapper = styled.div({ display: 'flex', alignItems: 'flex-start', fontSize: 11, marginTop: 6, cursor: 'help', }); const ErrorIcon = styled(Icons)(({ theme }) => ({ fill: theme.color.defaultText, opacity: 0.8, marginRight: 6, marginLeft: 2, marginTop: 1, })); const ErrorTooltip = styled.div(({ theme }) => ({ fontFamily: theme.typography.fonts.base, fontSize: 13, padding: 8, maxWidth: 350, })); const Actions = styled.div({ alignSelf: 'stretch', display: 'flex', justifyContent: 'space-between', marginTop: 24, }); const Error = styled(ErrorMessage)({}); interface ButtonProps { dirty?: boolean; } const Button = styled.button({ backgroundColor: 'transparent', border: '0 none', outline: 'none', appearance: 'none', fontWeight: 500, fontSize: 12, flexBasis: '50%', cursor: 'pointer', padding: '11px 16px', borderRadius: 4, textTransform: 'uppercase', '&:focus': { textDecoration: 'underline', fontWeight: 700, }, '&:active': { textDecoration: 'underline', fontWeight: 700, }, '&[aria-disabled="true"]': { cursor: 'default', }, }); const Submit = styled(Button)(({ theme, dirty }) => ({ marginRight: 8, backgroundColor: theme.color.secondary, color: theme.color.inverseText, opacity: dirty ? 1 : 0.6, boxShadow: 'rgb(30 167 253 / 10%) 0 0 0 1px inset', })); const Reset = styled(Button)(({ theme }) => ({ marginLeft: 8, boxShadow: 'rgb(30 167 253) 0 0 0 1px inset', color: theme.color.secondary, }));