diff options
Diffstat (limited to 'src/components/organisms/forms')
4 files changed, 288 insertions, 0 deletions
| diff --git a/src/components/organisms/forms/comment-form.module.scss b/src/components/organisms/forms/comment-form.module.scss new file mode 100644 index 0000000..f3f2646 --- /dev/null +++ b/src/components/organisms/forms/comment-form.module.scss @@ -0,0 +1,8 @@ +.field { +  width: 100%; +} + +.button { +  display: block; +  margin: auto; +} diff --git a/src/components/organisms/forms/comment-form.stories.tsx b/src/components/organisms/forms/comment-form.stories.tsx new file mode 100644 index 0000000..1ab7cf2 --- /dev/null +++ b/src/components/organisms/forms/comment-form.stories.tsx @@ -0,0 +1,86 @@ +import Notice from '@components/atoms/layout/notice'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import CommentFormComponent from './comment-form'; + +export default { +  title: 'Organisms/Forms', +  component: CommentFormComponent, +  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, +      }, +    }, +    saveComment: { +      control: { +        type: null, +      }, +      description: 'A callback function to process the comment form data.', +      type: { +        name: 'function', +        required: true, +      }, +    }, +    title: { +      control: { +        type: 'text', +      }, +      description: 'The form title.', +      table: { +        category: 'Options', +      }, +      type: { +        name: 'string', +        required: false, +      }, +    }, +    titleLevel: { +      control: { +        type: 'number', +      }, +      description: 'The title level (hn).', +      table: { +        category: 'Options', +      }, +      type: { +        name: 'number', +        required: false, +      }, +    }, +  }, +} as ComponentMeta<typeof CommentFormComponent>; + +const Template: ComponentStory<typeof CommentFormComponent> = (args) => ( +  <IntlProvider locale="en"> +    <CommentFormComponent {...args} /> +  </IntlProvider> +); + +export const CommentForm = Template.bind({}); +CommentForm.args = { +  saveComment: (reset: () => void) => { +    reset(); +  }, +}; diff --git a/src/components/organisms/forms/comment-form.test.tsx b/src/components/organisms/forms/comment-form.test.tsx new file mode 100644 index 0000000..0d387b5 --- /dev/null +++ b/src/components/organisms/forms/comment-form.test.tsx @@ -0,0 +1,20 @@ +import { render, screen } from '@test-utils'; +import CommentForm from './comment-form'; + +const title = 'Cum voluptas voluptatibus'; + +describe('CommentForm', () => { +  it('renders a form', () => { +    render(<CommentForm saveComment={() => null} />); +    expect(screen.getByRole('form')).toBeInTheDocument(); +  }); + +  it('renders an optional title', () => { +    render( +      <CommentForm saveComment={() => null} title={title} titleLevel={2} /> +    ); +    expect( +      screen.getByRole('heading', { level: 2, name: title }) +    ).toBeInTheDocument(); +  }); +}); diff --git a/src/components/organisms/forms/comment-form.tsx b/src/components/organisms/forms/comment-form.tsx new file mode 100644 index 0000000..6acbf94 --- /dev/null +++ b/src/components/organisms/forms/comment-form.tsx @@ -0,0 +1,174 @@ +import Button from '@components/atoms/buttons/button'; +import Form from '@components/atoms/forms/form'; +import Heading, { type HeadingLevel } from '@components/atoms/headings/heading'; +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 './comment-form.module.scss'; + +export type CommentFormProps = { +  /** +   * Set additional classnames to the form wrapper. +   */ +  className?: string; +  /** +   * Pass a component to print a success/error message. +   */ +  Notice?: ReactNode; +  /** +   * A callback function to save comment. It takes a function as parameter to +   * reset the form. +   */ +  saveComment: (reset: () => void) => void; +  /** +   * The form title. +   */ +  title?: string; +  /** +   * The title level. +   */ +  titleLevel?: HeadingLevel; +}; + +const CommentForm: VFC<CommentFormProps> = ({ +  className = '', +  Notice, +  saveComment, +  title, +  titleLevel = 2, +}) => { +  const intl = useIntl(); +  const [name, setName] = useState<string>(''); +  const [email, setEmail] = useState<string>(''); +  const [website, setWebsite] = useState<string>(''); +  const [comment, setComment] = useState<string>(''); +  const [isSubmitting, setIsSubmitting] = useState<boolean>(false); + +  /** +   * Reset all the form fields. +   */ +  const resetForm = () => { +    setName(''); +    setEmail(''); +    setWebsite(''); +    setComment(''); +    setIsSubmitting(false); +  }; + +  const nameLabel = intl.formatMessage({ +    defaultMessage: 'Name:', +    description: 'CommentForm: name label', +    id: 'ZIrTee', +  }); + +  const emailLabel = intl.formatMessage({ +    defaultMessage: 'Email:', +    description: 'CommentForm: email label', +    id: 'Bh7z5v', +  }); + +  const websiteLabel = intl.formatMessage({ +    defaultMessage: 'Website:', +    description: 'CommentForm: website label', +    id: 'u41qSk', +  }); + +  const commentLabel = intl.formatMessage({ +    defaultMessage: 'Comment:', +    description: 'CommentForm: comment label', +    id: 'A8hGaK', +  }); + +  const formTitle = intl.formatMessage({ +    defaultMessage: 'Comment form', +    description: 'CommentForm: aria label', +    id: 'dz2kDV', +  }); + +  const formAriaLabel = title ? undefined : formTitle; +  const formId = 'comment-form-title'; +  const formLabelledBy = title ? formId : undefined; + +  /** +   * Handle form submit. +   */ +  const submitHandler = () => { +    setIsSubmitting(true); +    saveComment(resetForm); +  }; + +  return ( +    <Form +      onSubmit={submitHandler} +      className={className} +      aria-label={formAriaLabel} +      aria-labelledby={formLabelledBy} +    > +      {title && ( +        <Heading id={formId} level={titleLevel}> +          {title} +        </Heading> +      )} +      <LabelledField +        type="text" +        id="commenter-name" +        name="commenter-name" +        label={nameLabel} +        required={true} +        value={name} +        setValue={setName} +        className={styles.field} +      /> +      <LabelledField +        type="email" +        id="commenter-email" +        name="commenter-email" +        label={emailLabel} +        required={true} +        value={email} +        setValue={setEmail} +        className={styles.field} +      /> +      <LabelledField +        type="text" +        id="commenter-website" +        name="commenter-website" +        label={websiteLabel} +        required={false} +        value={website} +        setValue={setWebsite} +        className={styles.field} +      /> +      <LabelledField +        type="textarea" +        id="commenter-comment" +        name="commenter-comment" +        label={commentLabel} +        required={true} +        value={comment} +        setValue={setComment} +        className={styles.field} +      /> +      <Button type="submit" kind="primary" className={styles.button}> +        {intl.formatMessage({ +          defaultMessage: 'Publish', +          description: 'CommentForm: submit button', +          id: 'OL0Yzx', +        })} +      </Button> +      {isSubmitting && ( +        <Spinner +          message={intl.formatMessage({ +            defaultMessage: 'Submitting...', +            description: 'CommentForm: spinner message on submit', +            id: 'IY5ew6', +          })} +        /> +      )} +      {Notice} +    </Form> +  ); +}; + +export default CommentForm; | 
