diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-09 17:18:46 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:15:27 +0100 |
| commit | f699802b837d7d9fcf150ff2bf00cd3c5475c87a (patch) | |
| tree | 6c96a140193e7386b454b6d444058a99a0e07454 /src/components/organisms/comments-list/comments-list.tsx | |
| parent | bd9c9ae7e2ae973969569dd434836de9f38b07d4 (diff) | |
refactor(components): rewrite CommentsList component
* use ApprovedCommentProps to make CommentData type
* add the author name of the parent on reply form heading
* add tests
Diffstat (limited to 'src/components/organisms/comments-list/comments-list.tsx')
| -rw-r--r-- | src/components/organisms/comments-list/comments-list.tsx | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/src/components/organisms/comments-list/comments-list.tsx b/src/components/organisms/comments-list/comments-list.tsx new file mode 100644 index 0000000..0470f99 --- /dev/null +++ b/src/components/organisms/comments-list/comments-list.tsx @@ -0,0 +1,147 @@ +import { + forwardRef, + useCallback, + useState, + type ForwardRefRenderFunction, +} from 'react'; +import { useIntl } from 'react-intl'; +import type { Nullable } from '../../../types'; +import { Heading, List, ListItem, type ListProps } from '../../atoms'; +import { + ApprovedComment, + type ApprovedCommentProps, + PendingComment, + ReplyCommentForm, + type ReplyCommentFormProps, +} from '../comment'; +import styles from './comments-list.module.scss'; + +export type CommentData = Pick< + ApprovedCommentProps, + 'author' | 'content' | 'id' | 'publicationDate' +> & { + isApproved: boolean; + replies?: CommentData[]; +}; + +export type CommentsListProps = Omit< + ListProps<true, false>, + | 'children' + | 'hideMarker' + | 'isHierarchical' + | 'isInline' + | 'isOrdered' + | 'onSubmit' + | 'spacing' +> & + Pick<ReplyCommentFormProps, 'onSubmit'> & { + /** + * The comments. + */ + comments: CommentData[]; + /** + * A positive integer. When depth is set to `0`, replies are not used. + * + * @default 0 + */ + depth?: number; + }; + +const CommentsListWithRef: ForwardRefRenderFunction< + HTMLOListElement, + CommentsListProps +> = ({ comments, depth = 0, onSubmit, ...props }, ref) => { + const [replyingTo, setReplyingTo] = useState<Nullable<number>>(null); + const intl = useIntl(); + + const toggleReply = useCallback((id: number) => { + setReplyingTo((prevId) => { + if (prevId === id) return null; + return id; + }); + }, []); + + const getComments = useCallback( + (data: CommentData[], currentDepth = 0) => { + const isLastLevel = depth === currentDepth; + + return data.map(({ isApproved, replies, ...comment }) => { + const replyFormHeading = intl.formatMessage( + { + defaultMessage: 'Leave a reply to {name}', + description: 'CommentsList: comment form title', + id: 'c1Ju/q', + }, + { name: comment.author.name } + ); + 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}> + {isApproved ? ( + <> + <ApprovedComment + {...comment} + onReply={toggleReply} + replyBtn={isLastLevel ? undefined : replyBtnLabel} + /> + {replyingTo === comment.id ? ( + <ReplyCommentForm + className={styles.reply} + commentId={comment.id} + heading={<Heading level={2}>{replyFormHeading}</Heading>} + onSubmit={onSubmit} + /> + ) : null} + {replies?.length && !isLastLevel ? ( + <List + hideMarker + isOrdered + // eslint-disable-next-line react/jsx-no-literals + spacing="sm" + > + {getComments(replies, currentDepth + 1)} + </List> + ) : null} + </> + ) : ( + <PendingComment /> + )} + </ListItem> + ); + }); + }, + [depth, intl, onSubmit, replyingTo, toggleReply] + ); + + return ( + <List + {...props} + hideMarker + isOrdered + ref={ref} + // eslint-disable-next-line react/jsx-no-literals + spacing="sm" + > + {getComments(comments)} + </List> + ); +}; + +/** + * CommentsList component + * + * Render a list of comments. + */ +export const CommentsList = forwardRef(CommentsListWithRef); |
