aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/organisms/comment/approved-comment
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-11-07 16:55:58 +0100
committerArmand Philippot <git@armandphilippot.com>2023-11-11 18:15:27 +0100
commitbd9c9ae7e2ae973969569dd434836de9f38b07d4 (patch)
tree84905097c4f2c2db36794c20910e3893189a65e1 /src/components/organisms/comment/approved-comment
parentc9c1c90b30e243563bb4f731da15b3fe657556d2 (diff)
refactor(components): split Comment component into 3 components
* add ApprovedComment, PendingComment and ReplyCommentForm components * let consumer handle reply form visibility * move structured data into article page (each article already has the comments data and already handle json ltd schema so I prefered to move the schema in the final consumer instead of adding a script element foreach comment)
Diffstat (limited to 'src/components/organisms/comment/approved-comment')
-rw-r--r--src/components/organisms/comment/approved-comment/approved-comment.module.scss52
-rw-r--r--src/components/organisms/comment/approved-comment/approved-comment.stories.tsx126
-rw-r--r--src/components/organisms/comment/approved-comment/approved-comment.test.tsx108
-rw-r--r--src/components/organisms/comment/approved-comment/approved-comment.tsx177
-rw-r--r--src/components/organisms/comment/approved-comment/index.ts1
5 files changed, 464 insertions, 0 deletions
diff --git a/src/components/organisms/comment/approved-comment/approved-comment.module.scss b/src/components/organisms/comment/approved-comment/approved-comment.module.scss
new file mode 100644
index 0000000..7906632
--- /dev/null
+++ b/src/components/organisms/comment/approved-comment/approved-comment.module.scss
@@ -0,0 +1,52 @@
+@use "../../../../styles/abstracts/placeholders";
+
+.author {
+ color: var(--color-primary-darker);
+ font-family: var(--font-family-regular);
+ font-size: var(--font-size-md);
+ font-weight: 600;
+ text-shadow: none;
+}
+
+.body {
+ overflow-wrap: break-word;
+
+ :global {
+ a {
+ @extend %link;
+
+ &[hreflang],
+ &.download,
+ &.external {
+ @extend %link-with-icon;
+ }
+
+ &[hreflang] {
+ @extend %link-with-lang;
+ }
+
+ &[hreflang]:not(.download, .external) {
+ --is-icon-hidden: "";
+ }
+
+ &.download {
+ @extend %download-link;
+ }
+
+ &.external {
+ @extend %external-link;
+ }
+
+ &.download,
+ &.external {
+ &:not([hreflang]) {
+ --is-lang-hidden: "";
+ }
+ }
+
+ &.external.download {
+ @extend %external-download-link;
+ }
+ }
+ }
+}
diff --git a/src/components/organisms/comment/approved-comment/approved-comment.stories.tsx b/src/components/organisms/comment/approved-comment/approved-comment.stories.tsx
new file mode 100644
index 0000000..36afa6b
--- /dev/null
+++ b/src/components/organisms/comment/approved-comment/approved-comment.stories.tsx
@@ -0,0 +1,126 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { ApprovedComment } from './approved-comment';
+
+/**
+ * ApprovedComment - Storybook Meta
+ */
+export default {
+ title: 'Organisms/Comment/ApprovedComment',
+ component: ApprovedComment,
+ argTypes: {
+ author: {
+ description: 'The author data.',
+ type: {
+ name: 'object',
+ required: true,
+ value: {},
+ },
+ },
+ content: {
+ control: {
+ type: 'text',
+ },
+ description: 'The comment body.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ id: {
+ control: {
+ type: 'string',
+ },
+ description: 'The comment id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ publicationDate: {
+ control: {
+ type: 'text',
+ },
+ description: 'The publication date.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ replyBtn: {
+ control: {
+ type: null,
+ },
+ description: 'Add a reply button.',
+ type: {
+ name: 'function',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta<typeof ApprovedComment>;
+
+const Template: ComponentStory<typeof ApprovedComment> = (args) => (
+ <ApprovedComment {...args} />
+);
+
+/**
+ * ApprovedComment Stories - Default
+ */
+export const Default = Template.bind({});
+Default.args = {
+ author: {
+ name: 'Kameron.Conn',
+ },
+ content:
+ 'Quia est eos deserunt qui perferendis est pariatur eaque. Deserunt omnis quis consectetur ea quam a cupiditate. Velit laboriosam rem nihil numquam quia.',
+ id: 1,
+ publicationDate: '2023-11-06',
+};
+
+/**
+ * ApprovedComment Stories - WithAvatar
+ */
+export const WithAvatar = Template.bind({});
+WithAvatar.args = {
+ author: {
+ avatar: {
+ alt: 'Kameron.Conn avatar',
+ src: 'https://cloudflare-ipfs.com/ipfs/Qmd3W5DuhgHirLHGVixi6V76LhCkZUz6pnFt5AJBiyvHye/avatar/82.jpg',
+ },
+ name: 'Kameron.Conn',
+ },
+ content:
+ 'Quia est eos deserunt qui perferendis est pariatur eaque. Deserunt omnis quis consectetur ea quam a cupiditate. Velit laboriosam rem nihil numquam quia.',
+ id: 2,
+ publicationDate: '2023-11-06',
+};
+
+/**
+ * ApprovedComment Stories - WithWebsite
+ */
+export const WithWebsite = Template.bind({});
+WithWebsite.args = {
+ author: {
+ name: 'Kameron.Conn',
+ website: 'https://www.armandphilippot.com/',
+ },
+ content:
+ 'Quia est eos deserunt qui perferendis est pariatur eaque. Deserunt omnis quis consectetur ea quam a cupiditate. Velit laboriosam rem nihil numquam quia.',
+ id: 3,
+ publicationDate: '2023-11-06',
+};
+
+/**
+ * ApprovedComment Stories - WithReplyBtn
+ */
+export const WithReplyBtn = Template.bind({});
+WithReplyBtn.args = {
+ author: {
+ name: 'Kameron.Conn',
+ },
+ content:
+ 'Quia est eos deserunt qui perferendis est pariatur eaque. Deserunt omnis quis consectetur ea quam a cupiditate. Velit laboriosam rem nihil numquam quia.',
+ id: 4,
+ publicationDate: '2023-11-06',
+ replyBtn: 'Reply',
+};
diff --git a/src/components/organisms/comment/approved-comment/approved-comment.test.tsx b/src/components/organisms/comment/approved-comment/approved-comment.test.tsx
new file mode 100644
index 0000000..2e29b5f
--- /dev/null
+++ b/src/components/organisms/comment/approved-comment/approved-comment.test.tsx
@@ -0,0 +1,108 @@
+import { describe, expect, it } from '@jest/globals';
+import { userEvent } from '@testing-library/user-event';
+import { render, screen as rtlScreen } from '../../../../../tests/utils';
+import { ApprovedComment, type CommentAuthor } from './approved-comment';
+
+describe('ApprovedComment', () => {
+ const user = userEvent.setup();
+
+ it('renders the author, the publication date, the comment and a permalink', () => {
+ const author = {
+ name: 'Delbert_Jacobi45',
+ } satisfies CommentAuthor;
+ const content = 'Repellat ab non et.';
+ const id = 1;
+ const publicationDate = '2023';
+
+ render(
+ <ApprovedComment
+ author={author}
+ content={content}
+ id={id}
+ publicationDate={publicationDate}
+ />
+ );
+
+ expect(rtlScreen.getByText(author.name)).toBeInTheDocument();
+ expect(rtlScreen.getByText(content)).toBeInTheDocument();
+ expect(
+ rtlScreen.getByText(new RegExp(publicationDate))
+ ).toBeInTheDocument();
+ expect(rtlScreen.getByRole('link')).toHaveAttribute(
+ 'href',
+ `#comment-${id}`
+ );
+ });
+
+ it('can render the author avatar', () => {
+ const author = {
+ avatar: {
+ alt: 'enim ut maiores',
+ src: 'https://picsum.photos/640/480',
+ },
+ name: 'Sandra82',
+ } satisfies CommentAuthor;
+
+ render(
+ <ApprovedComment
+ author={author}
+ content="Ab qui aliquam esse."
+ id={2}
+ publicationDate="2022-11-03"
+ />
+ );
+
+ expect(rtlScreen.getByRole('img')).toHaveAccessibleName(author.avatar.alt);
+ });
+
+ it('can render a link to the author website', () => {
+ const author = {
+ name: 'Esmeralda51',
+ website: 'http://example.net',
+ } satisfies CommentAuthor;
+
+ render(
+ <ApprovedComment
+ author={author}
+ content="Ab qui aliquam esse."
+ id={2}
+ publicationDate="2022-11-03"
+ />
+ );
+
+ expect(rtlScreen.getByRole('link', { name: author.name })).toHaveAttribute(
+ 'href',
+ author.website
+ );
+ });
+
+ it('can render a reply button', async () => {
+ const id = 6;
+ const replyBtn = 'dolore recusandae voluptas';
+ const handleReply = jest.fn((_id: number) => {
+ // do nothing
+ });
+
+ render(
+ <ApprovedComment
+ author={{ name: 'Kurtis5' }}
+ content="Ab qui aliquam esse."
+ id={id}
+ onReply={handleReply}
+ publicationDate="2022-11-03"
+ replyBtn={replyBtn}
+ />
+ );
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect.assertions(4);
+
+ expect(rtlScreen.getByRole('button')).toHaveTextContent(replyBtn);
+ expect(handleReply).not.toHaveBeenCalled();
+
+ await user.click(rtlScreen.getByRole('button'));
+
+ expect(handleReply).toHaveBeenCalledTimes(1);
+ expect(handleReply).toHaveBeenCalledWith(id);
+ });
+});
diff --git a/src/components/organisms/comment/approved-comment/approved-comment.tsx b/src/components/organisms/comment/approved-comment/approved-comment.tsx
new file mode 100644
index 0000000..db5345b
--- /dev/null
+++ b/src/components/organisms/comment/approved-comment/approved-comment.tsx
@@ -0,0 +1,177 @@
+import NextImage from 'next/image';
+import { type ForwardRefRenderFunction, forwardRef, useCallback } from 'react';
+import { useIntl } from 'react-intl';
+import { Button, Link, Time } from '../../../atoms';
+import {
+ Card,
+ CardBody,
+ CardCover,
+ CardHeader,
+ CardMeta,
+ type CardProps,
+ CardTitle,
+ CardFooter,
+ CardActions,
+} from '../../../molecules';
+import styles from './approved-comment.module.scss';
+
+export type CommentAuthorAvatar = {
+ /**
+ * The alternative text for the avatar.
+ */
+ alt: string;
+ /**
+ * The avatar url.
+ */
+ src: string;
+};
+
+export type CommentAuthor = {
+ /**
+ * The author avatar.
+ */
+ avatar?: CommentAuthorAvatar;
+ /**
+ * The author name.
+ */
+ name: string;
+ /**
+ * The author website.
+ */
+ website?: string;
+};
+
+export type CommentReplyHandler = (id: number) => void | Promise<void>;
+
+export type ApprovedCommentProps = Omit<
+ CardProps<undefined>,
+ | 'children'
+ | 'content'
+ | 'cover'
+ | 'id'
+ | 'isCentered'
+ | 'linkTo'
+ | 'meta'
+ | 'variant'
+> & {
+ /**
+ * The author data.
+ */
+ author: CommentAuthor;
+ /**
+ * The comment.
+ */
+ content: string;
+ /**
+ * The comment id.
+ */
+ id: number;
+ /**
+ * A callback function to handle reply.
+ */
+ onReply?: CommentReplyHandler;
+ /**
+ * The publication date of the comment.
+ */
+ publicationDate: string;
+ /**
+ * Add a reply button to the comment by providing a label.
+ */
+ replyBtn?: string;
+};
+
+const ApprovedCommentWithRef: ForwardRefRenderFunction<
+ HTMLDivElement,
+ ApprovedCommentProps
+> = (
+ {
+ author,
+ className = '',
+ content,
+ id,
+ onReply,
+ publicationDate,
+ replyBtn,
+ ...props
+ },
+ ref
+) => {
+ const intl = useIntl();
+ const commentClass = `${className}`;
+ const commentId = `comment-${id}`;
+ const commentLink = `#${commentId}`;
+ const publicationDateLabel = intl.formatMessage({
+ defaultMessage: 'Published on:',
+ description: 'ApprovedComment: publication date label',
+ id: 'NzeU3V',
+ });
+
+ const handleReply = useCallback(() => {
+ if (onReply) onReply(id);
+ }, [id, onReply]);
+
+ return (
+ <Card
+ {...props}
+ className={commentClass}
+ cover={
+ author.avatar ? (
+ <CardCover hasBorders>
+ <NextImage
+ alt={author.avatar.alt}
+ height={96}
+ src={author.avatar.src}
+ width={96}
+ />
+ </CardCover>
+ ) : undefined
+ }
+ id={commentId}
+ ref={ref}
+ variant={2}
+ >
+ <CardHeader>
+ <CardTitle className={styles.author} isFake>
+ {author.website ? (
+ <Link href={author.website}>{author.name}</Link>
+ ) : (
+ author.name
+ )}
+ </CardTitle>
+ <CardMeta
+ hasInlinedItems
+ items={[
+ {
+ id: 'publication-date',
+ label: publicationDateLabel,
+ value: (
+ <Link href={commentLink}>
+ <Time date={publicationDate} showTime />
+ </Link>
+ ),
+ },
+ ]}
+ />
+ </CardHeader>
+ <CardBody
+ className={styles.body}
+ dangerouslySetInnerHTML={{ __html: content }}
+ />
+ {replyBtn ? (
+ <CardFooter>
+ <CardActions>
+ <Button
+ // eslint-disable-next-line react/jsx-no-literals
+ kind="tertiary"
+ onClick={handleReply}
+ >
+ {replyBtn}
+ </Button>
+ </CardActions>
+ </CardFooter>
+ ) : null}
+ </Card>
+ );
+};
+
+export const ApprovedComment = forwardRef(ApprovedCommentWithRef);
diff --git a/src/components/organisms/comment/approved-comment/index.ts b/src/components/organisms/comment/approved-comment/index.ts
new file mode 100644
index 0000000..444b0c3
--- /dev/null
+++ b/src/components/organisms/comment/approved-comment/index.ts
@@ -0,0 +1 @@
+export * from './approved-comment';