aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/organisms/layout/comments-list.tsx
blob: 385aea96faae33812cb2fad1bc1e73bc3876543f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { useState, type FC, useCallback } from 'react';
import { useIntl } from 'react-intl';
import type { SingleComment } from '../../../types';
import { Heading, List, ListItem } from '../../atoms';
import {
  ApprovedComment,
  type CommentReplyHandler,
  PendingComment,
  ReplyCommentForm,
  type ReplyCommentFormProps,
} from '../comment';
import styles from './comments-list.module.scss';

// eslint-disable-next-line @typescript-eslint/no-magic-numbers
export type CommentsListDepth = 0 | 1 | 2 | 3 | 4;

export type CommentsListProps = Pick<ReplyCommentFormProps, 'onSubmit'> & {
  /**
   * An array of comments.
   */
  comments: SingleComment[];
  /**
   * The maximum depth. Use `0` to not display nested comments.
   */
  depth: CommentsListDepth;
};

/**
 * CommentsList component
 *
 * Render a comments list.
 */
export const CommentsList: FC<CommentsListProps> = ({
  comments,
  depth,
  onSubmit,
}) => {
  const [replyingTo, setReplyingTo] = useState<number | null>(null);
  const intl = useIntl();
  const replyFormHeading = intl.formatMessage({
    defaultMessage: 'Leave a reply',
    description: 'CommentsList: comment form title',
    id: 'w8uLLF',
  });

  const handleReplyFormVisibility: CommentReplyHandler = useCallback((id) => {
    setReplyingTo((prevId) => {
      if (prevId === id) return null;
      return id;
    });
  }, []);

  /**
   * Get each comment wrapped in a list item.
   *
   * @param {SingleComment[]} commentsList - An array of comments.
   * @returns {JSX.Element[]} The list items.
   */
  const getItems = (
    commentsList: SingleComment[],
    startLevel: number
  ): JSX.Element[] => {
    const isLastLevel = startLevel === depth;

    return commentsList.map(
      ({ approved, meta, replies, parentId, ...comment }) => {
        const replyBtnLabel =
          replyingTo === comment.id
            ? intl.formatMessage({
                defaultMessage: 'Cancel reply',
                description: 'CommentsList: cancel reply button',
                id: 'uZj4QI',
              })
            : intl.formatMessage({
                defaultMessage: 'Reply',
                description: 'CommentsList: reply button',
                id: 'Qa9twM',
              });

        return (
          <ListItem key={comment.id}>
            {approved ? (
              <>
                <ApprovedComment
                  {...comment}
                  author={meta.author}
                  onReply={handleReplyFormVisibility}
                  publicationDate={meta.date}
                  replyBtn={replyBtnLabel}
                />
                {replyingTo === comment.id ? (
                  <ReplyCommentForm
                    className={styles.reply}
                    heading={<Heading level={2}>{replyFormHeading}</Heading>}
                    onSubmit={onSubmit}
                    commentId={comment.id}
                  />
                ) : null}
              </>
            ) : (
              <PendingComment />
            )}
            {replies.length && !isLastLevel ? (
              <List hideMarker isOrdered spacing="sm">
                {getItems(replies, startLevel + 1)}
              </List>
            ) : null}
          </ListItem>
        );
      }
    );
  };

  return (
    <List hideMarker isOrdered spacing="sm">
      {getItems(comments, 0)}
    </List>
  );
};