aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-04-15 14:49:41 +0200
committerArmand Philippot <git@armandphilippot.com>2022-04-15 14:49:41 +0200
commit64570357f9608ad6638b1f8cc283ee9dd1cc3264 (patch)
tree32d95b6299f4c8b220af728b2173f040e526edd6 /src/components
parentb836f0a9f8b783e3328983ad087aa2b7b297b43a (diff)
chore: add a ContactForm component
Diffstat (limited to 'src/components')
-rw-r--r--src/components/atoms/layout/notice.tsx5
-rw-r--r--src/components/organisms/forms/contact-form.module.scss8
-rw-r--r--src/components/organisms/forms/contact-form.stories.tsx59
-rw-r--r--src/components/organisms/forms/contact-form.test.tsx46
-rw-r--r--src/components/organisms/forms/contact-form.tsx150
5 files changed, 268 insertions, 0 deletions
diff --git a/src/components/atoms/layout/notice.tsx b/src/components/atoms/layout/notice.tsx
index e919182..b6e09c5 100644
--- a/src/components/atoms/layout/notice.tsx
+++ b/src/components/atoms/layout/notice.tsx
@@ -14,6 +14,11 @@ export type NoticeProps = {
message: string;
};
+/**
+ * Notice component
+ *
+ * Render a colored message depending on notice kind.
+ */
const Notice: VFC<NoticeProps> = ({ kind, message }) => {
const kindClass = `wrapper--${kind}`;
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;