diff options
Diffstat (limited to 'src/components/organisms')
4 files changed, 263 insertions, 0 deletions
| diff --git a/src/components/organisms/forms/contact-form.module.scss b/src/components/organisms/forms/contact-form.module.scss new file mode 100644 index 0000000..f3f2646 --- /dev/null +++ b/src/components/organisms/forms/contact-form.module.scss @@ -0,0 +1,8 @@ +.field { +  width: 100%; +} + +.button { +  display: block; +  margin: auto; +} diff --git a/src/components/organisms/forms/contact-form.stories.tsx b/src/components/organisms/forms/contact-form.stories.tsx new file mode 100644 index 0000000..2c8ab32 --- /dev/null +++ b/src/components/organisms/forms/contact-form.stories.tsx @@ -0,0 +1,59 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import ContactFormComponent from './contact-form'; + +export default { +  title: 'Organisms/Forms', +  component: ContactFormComponent, +  argTypes: { +    className: { +      control: { +        type: 'text', +      }, +      description: 'Set additional classnames to the form wrapper.', +      table: { +        category: 'Styles', +      }, +      type: { +        name: 'string', +        required: false, +      }, +    }, +    Notice: { +      control: { +        type: null, +      }, +      description: 'A component to display a success or error message.', +      table: { +        category: 'Options', +      }, +      type: { +        name: 'function', +        required: false, +      }, +    }, +    sendMail: { +      control: { +        type: null, +      }, +      description: 'A callback function to process the contact form data.', +      type: { +        name: 'function', +        required: true, +      }, +    }, +  }, +} as ComponentMeta<typeof ContactFormComponent>; + +const Template: ComponentStory<typeof ContactFormComponent> = (args) => ( +  <IntlProvider locale="en"> +    <ContactFormComponent {...args} /> +  </IntlProvider> +); + +export const ContactForm = Template.bind({}); +ContactForm.args = { +  sendMail: (reset: () => void) => { +    reset(); +  }, +}; diff --git a/src/components/organisms/forms/contact-form.test.tsx b/src/components/organisms/forms/contact-form.test.tsx new file mode 100644 index 0000000..744f147 --- /dev/null +++ b/src/components/organisms/forms/contact-form.test.tsx @@ -0,0 +1,46 @@ +import { render, screen } from '@test-utils'; +import ContactForm from './contact-form'; + +const props = { +  sendMail: () => null, +}; + +describe('ContactForm', () => { +  it('renders a contact form', () => { +    render(<ContactForm {...props} />); +    expect( +      screen.getByRole('form', { name: 'Contact form' }) +    ).toBeInTheDocument(); +  }); + +  it('renders a name field', () => { +    render(<ContactForm {...props} />); +    expect(screen.getByRole('textbox', { name: /^Name:/ })).toBeInTheDocument(); +  }); + +  it('renders an email field', () => { +    render(<ContactForm {...props} />); +    expect( +      screen.getByRole('textbox', { name: /^Email:/ }) +    ).toBeInTheDocument(); +  }); + +  it('renders an object field', () => { +    render(<ContactForm {...props} />); +    expect( +      screen.getByRole('textbox', { name: /^Object:/ }) +    ).toBeInTheDocument(); +  }); + +  it('renders a message field', () => { +    render(<ContactForm {...props} />); +    expect( +      screen.getByRole('textbox', { name: /^Message:/ }) +    ).toBeInTheDocument(); +  }); + +  it('renders a submit button', () => { +    render(<ContactForm {...props} />); +    expect(screen.getByRole('button', { name: /^Send/ })).toBeInTheDocument(); +  }); +}); diff --git a/src/components/organisms/forms/contact-form.tsx b/src/components/organisms/forms/contact-form.tsx new file mode 100644 index 0000000..994244a --- /dev/null +++ b/src/components/organisms/forms/contact-form.tsx @@ -0,0 +1,150 @@ +import Button from '@components/atoms/buttons/button'; +import Form from '@components/atoms/forms/form'; +import Spinner from '@components/atoms/loaders/spinner'; +import LabelledField from '@components/molecules/forms/labelled-field'; +import { ReactNode, useState, VFC } from 'react'; +import { useIntl } from 'react-intl'; +import styles from './contact-form.module.scss'; + +export type ContactFormProps = { +  /** +   * Set additional classnames to the form wrapper. +   */ +  className?: string; +  /** +   * Pass a component to print a success/error message. +   */ +  Notice?: ReactNode; +  /** +   * A callback function to send mail. It takes a function as parameter to +   * reset the form. +   */ +  sendMail: (reset: () => void) => void; +}; + +/** + * ContactForm component + * + * Render a contact form. + */ +const ContactForm: VFC<ContactFormProps> = ({ +  className = '', +  Notice, +  sendMail, +}) => { +  const intl = useIntl(); +  const [name, setName] = useState<string>(''); +  const [email, setEmail] = useState<string>(''); +  const [object, setObject] = useState<string>(''); +  const [message, setMessage] = useState<string>(''); +  const [isSubmitting, setIsSubmitting] = useState<boolean>(false); + +  /** +   * Reset all the form fields. +   */ +  const resetForm = () => { +    setName(''); +    setEmail(''); +    setObject(''); +    setMessage(''); +    setIsSubmitting(false); +  }; + +  const formName = intl.formatMessage({ +    defaultMessage: 'Contact form', +    description: 'ContactForm: form accessible name', +    id: 'HFdzae', +  }); + +  const nameLabel = intl.formatMessage({ +    defaultMessage: 'Name:', +    description: 'ContactForm: name label', +    id: '1dCuCx', +  }); + +  const emailLabel = intl.formatMessage({ +    defaultMessage: 'Email:', +    description: 'ContactForm: email label', +    id: 'w4B5PA', +  }); + +  const objectLabel = intl.formatMessage({ +    defaultMessage: 'Object:', +    description: 'ContactForm: object label', +    id: 's8/tyz', +  }); + +  const messageLabel = intl.formatMessage({ +    defaultMessage: 'Message:', +    description: 'ContactForm: message label', +    id: 'yN5P+m', +  }); + +  const submitHandler = () => { +    setIsSubmitting(true); +    sendMail(resetForm); +  }; + +  return ( +    <Form aria-label={formName} onSubmit={submitHandler} className={className}> +      <LabelledField +        type="text" +        id="contact-name" +        name="contact-name" +        label={nameLabel} +        required={true} +        value={name} +        setValue={setName} +        className={styles.field} +      /> +      <LabelledField +        type="email" +        id="contact-email" +        name="contact-email" +        label={emailLabel} +        required={true} +        value={email} +        setValue={setEmail} +        className={styles.field} +      /> +      <LabelledField +        type="text" +        id="contact-object" +        name="contact-object" +        label={objectLabel} +        value={object} +        setValue={setObject} +        className={styles.field} +      /> +      <LabelledField +        type="textarea" +        id="contact-message" +        name="contact-message" +        label={messageLabel} +        required={true} +        value={message} +        setValue={setMessage} +        className={styles.field} +      /> +      <Button type="submit" kind="primary" className={styles.button}> +        {intl.formatMessage({ +          defaultMessage: 'Send', +          description: 'ContactForm: send button', +          id: 'VkAnvv', +        })} +      </Button> +      {isSubmitting && ( +        <Spinner +          message={intl.formatMessage({ +            defaultMessage: 'Sending mail...', +            description: 'ContactForm: spinner message on submit', +            id: 'xaqaYQ', +          })} +        /> +      )} +      {Notice} +    </Form> +  ); +}; + +export default ContactForm; | 
