From bd9c9ae7e2ae973969569dd434836de9f38b07d4 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 7 Nov 2023 16:55:58 +0100 Subject: 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) --- .../approved-comment/approved-comment.module.scss | 52 ++++++ .../approved-comment/approved-comment.stories.tsx | 126 ++++++++++++++ .../approved-comment/approved-comment.test.tsx | 108 ++++++++++++ .../comment/approved-comment/approved-comment.tsx | 177 +++++++++++++++++++ .../organisms/comment/approved-comment/index.ts | 1 + src/components/organisms/comment/index.ts | 3 + .../organisms/comment/pending-comment/index.ts | 1 + .../pending-comment/pending-comment.stories.tsx | 21 +++ .../pending-comment/pending-comment.test.tsx | 13 ++ .../comment/pending-comment/pending-comment.tsx | 36 ++++ .../organisms/comment/reply-comment-form/index.ts | 1 + .../reply-comment-form.module.scss | 13 ++ .../reply-comment-form.stories.tsx | 25 +++ .../reply-comment-form/reply-comment-form.test.tsx | 26 +++ .../reply-comment-form/reply-comment-form.tsx | 74 ++++++++ src/components/organisms/index.ts | 1 + src/components/organisms/layout/comment.fixture.ts | 35 ---- .../organisms/layout/comment.module.scss | 78 --------- .../organisms/layout/comment.stories.tsx | 115 ------------- src/components/organisms/layout/comment.test.tsx | 44 ----- src/components/organisms/layout/comment.tsx | 189 --------------------- .../organisms/layout/comments-list.fixture.ts | 2 + .../organisms/layout/comments-list.module.scss | 3 + .../organisms/layout/comments-list.test.tsx | 3 +- src/components/organisms/layout/comments-list.tsx | 88 ++++++++-- src/components/organisms/layout/index.ts | 1 - 26 files changed, 758 insertions(+), 478 deletions(-) create mode 100644 src/components/organisms/comment/approved-comment/approved-comment.module.scss create mode 100644 src/components/organisms/comment/approved-comment/approved-comment.stories.tsx create mode 100644 src/components/organisms/comment/approved-comment/approved-comment.test.tsx create mode 100644 src/components/organisms/comment/approved-comment/approved-comment.tsx create mode 100644 src/components/organisms/comment/approved-comment/index.ts create mode 100644 src/components/organisms/comment/index.ts create mode 100644 src/components/organisms/comment/pending-comment/index.ts create mode 100644 src/components/organisms/comment/pending-comment/pending-comment.stories.tsx create mode 100644 src/components/organisms/comment/pending-comment/pending-comment.test.tsx create mode 100644 src/components/organisms/comment/pending-comment/pending-comment.tsx create mode 100644 src/components/organisms/comment/reply-comment-form/index.ts create mode 100644 src/components/organisms/comment/reply-comment-form/reply-comment-form.module.scss create mode 100644 src/components/organisms/comment/reply-comment-form/reply-comment-form.stories.tsx create mode 100644 src/components/organisms/comment/reply-comment-form/reply-comment-form.test.tsx create mode 100644 src/components/organisms/comment/reply-comment-form/reply-comment-form.tsx delete mode 100644 src/components/organisms/layout/comment.fixture.ts delete mode 100644 src/components/organisms/layout/comment.module.scss delete mode 100644 src/components/organisms/layout/comment.stories.tsx delete mode 100644 src/components/organisms/layout/comment.test.tsx delete mode 100644 src/components/organisms/layout/comment.tsx create mode 100644 src/components/organisms/layout/comments-list.module.scss (limited to 'src/components/organisms') 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; + +const Template: ComponentStory = (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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + // 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; + +export type ApprovedCommentProps = Omit< + CardProps, + | '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 ( + + + + ) : undefined + } + id={commentId} + ref={ref} + variant={2} + > + + + {author.website ? ( + {author.name} + ) : ( + author.name + )} + + + + + {replyBtn ? ( + + + + + + ) : null} + + ); +}; + +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'; diff --git a/src/components/organisms/comment/index.ts b/src/components/organisms/comment/index.ts new file mode 100644 index 0000000..60ab263 --- /dev/null +++ b/src/components/organisms/comment/index.ts @@ -0,0 +1,3 @@ +export * from './approved-comment'; +export * from './reply-comment-form'; +export * from './pending-comment'; diff --git a/src/components/organisms/comment/pending-comment/index.ts b/src/components/organisms/comment/pending-comment/index.ts new file mode 100644 index 0000000..9fc189f --- /dev/null +++ b/src/components/organisms/comment/pending-comment/index.ts @@ -0,0 +1 @@ +export * from './pending-comment'; diff --git a/src/components/organisms/comment/pending-comment/pending-comment.stories.tsx b/src/components/organisms/comment/pending-comment/pending-comment.stories.tsx new file mode 100644 index 0000000..1b6e1d9 --- /dev/null +++ b/src/components/organisms/comment/pending-comment/pending-comment.stories.tsx @@ -0,0 +1,21 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { PendingComment } from './pending-comment'; + +/** + * PendingComment - Storybook Meta + */ +export default { + title: 'Organisms/Comment/PendingComment', + component: PendingComment, + argTypes: {}, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +/** + * PendingComment Stories - Default + */ +export const Default = Template.bind({}); +Default.args = {}; diff --git a/src/components/organisms/comment/pending-comment/pending-comment.test.tsx b/src/components/organisms/comment/pending-comment/pending-comment.test.tsx new file mode 100644 index 0000000..87914c3 --- /dev/null +++ b/src/components/organisms/comment/pending-comment/pending-comment.test.tsx @@ -0,0 +1,13 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../../tests/utils'; +import { PendingComment } from './pending-comment'; + +describe('PendingComment', () => { + it('renders a text to inform user', () => { + render(); + + expect( + rtlScreen.getByText('This comment is awaiting moderation…') + ).toBeInTheDocument(); + }); +}); diff --git a/src/components/organisms/comment/pending-comment/pending-comment.tsx b/src/components/organisms/comment/pending-comment/pending-comment.tsx new file mode 100644 index 0000000..0d37ac2 --- /dev/null +++ b/src/components/organisms/comment/pending-comment/pending-comment.tsx @@ -0,0 +1,36 @@ +import { type ForwardRefRenderFunction, forwardRef } from 'react'; +import { useIntl } from 'react-intl'; +import { Card, CardBody, type CardProps } from '../../../molecules'; + +export type PendingCommentProps = Omit< + CardProps, + | 'children' + | 'content' + | 'cover' + | 'id' + | 'isCentered' + | 'linkTo' + | 'meta' + | 'variant' +>; + +const PendingCommentWithRef: ForwardRefRenderFunction< + HTMLDivElement, + PendingCommentProps +> = (props, ref) => { + const intl = useIntl(); + + return ( + + + {intl.formatMessage({ + defaultMessage: 'This comment is awaiting moderation…', + description: 'PendingComment: awaiting moderation text', + id: '1d/xvG', + })} + + + ); +}; + +export const PendingComment = forwardRef(PendingCommentWithRef); diff --git a/src/components/organisms/comment/reply-comment-form/index.ts b/src/components/organisms/comment/reply-comment-form/index.ts new file mode 100644 index 0000000..d461a03 --- /dev/null +++ b/src/components/organisms/comment/reply-comment-form/index.ts @@ -0,0 +1 @@ +export * from './reply-comment-form'; diff --git a/src/components/organisms/comment/reply-comment-form/reply-comment-form.module.scss b/src/components/organisms/comment/reply-comment-form/reply-comment-form.module.scss new file mode 100644 index 0000000..cd7c3ca --- /dev/null +++ b/src/components/organisms/comment/reply-comment-form/reply-comment-form.module.scss @@ -0,0 +1,13 @@ +.body { + margin-inline: auto; + width: 100%; +} + +.form { + margin-inline: auto; +} + +:where(.body) > *:first-child { + width: fit-content; + margin: 0 auto var(--spacing-md); +} diff --git a/src/components/organisms/comment/reply-comment-form/reply-comment-form.stories.tsx b/src/components/organisms/comment/reply-comment-form/reply-comment-form.stories.tsx new file mode 100644 index 0000000..57174ea --- /dev/null +++ b/src/components/organisms/comment/reply-comment-form/reply-comment-form.stories.tsx @@ -0,0 +1,25 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Heading } from '../../../atoms'; +import { ReplyCommentForm } from './reply-comment-form'; + +/** + * ReplyCommentForm - Storybook Meta + */ +export default { + title: 'Organisms/Comment/ReplyCommentForm', + component: ReplyCommentForm, + argTypes: {}, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +/** + * ReplyCommentForm Stories - Default + */ +export const Default = Template.bind({}); +Default.args = { + commentId: 5, + heading: Reply to comment 5, +}; diff --git a/src/components/organisms/comment/reply-comment-form/reply-comment-form.test.tsx b/src/components/organisms/comment/reply-comment-form/reply-comment-form.test.tsx new file mode 100644 index 0000000..f02dd48 --- /dev/null +++ b/src/components/organisms/comment/reply-comment-form/reply-comment-form.test.tsx @@ -0,0 +1,26 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../../tests/utils'; +import { Heading } from '../../../atoms'; +import { ReplyCommentForm } from './reply-comment-form'; + +describe('ReplyCommentForm', () => { + it('renders a form with a heading', () => { + const commentId = 5; + const heading = 'odio autem voluptas'; + const headingLvl = 3; + + render( + {heading}} + /> + ); + + expect( + rtlScreen.getByRole('heading', { level: headingLvl }) + ).toHaveTextContent(heading); + expect(rtlScreen.getByRole('form')).toHaveAccessibleName( + `Leave a reply to comment ${commentId}` + ); + }); +}); diff --git a/src/components/organisms/comment/reply-comment-form/reply-comment-form.tsx b/src/components/organisms/comment/reply-comment-form/reply-comment-form.tsx new file mode 100644 index 0000000..a027ba2 --- /dev/null +++ b/src/components/organisms/comment/reply-comment-form/reply-comment-form.tsx @@ -0,0 +1,74 @@ +import { + type ForwardRefRenderFunction, + type ReactElement, + type ReactNode, + forwardRef, +} from 'react'; +import { useIntl } from 'react-intl'; +import type { HeadingProps } from '../../../atoms'; +import { Card, CardBody, type CardProps } from '../../../molecules'; +import { CommentForm, type CommentFormProps } from '../../forms'; +import styles from './reply-comment-form.module.scss'; + +export type ReplyCommentFormProps = Omit< + CardProps, + | 'children' + | 'content' + | 'cover' + | 'id' + | 'isCentered' + | 'linkTo' + | 'meta' + | 'onSubmit' + | 'variant' +> & + Pick & { + /** + * Add additional contents below the form. + */ + children?: ReactNode; + /** + * The comment id related to the reply. + */ + commentId: number; + /** + * The form heading. + */ + heading: ReactElement; + }; + +const ReplyCommentFormWithRef: ForwardRefRenderFunction< + HTMLDivElement, + ReplyCommentFormProps +> = ( + { children, className = '', commentId, heading, onSubmit, ...props }, + ref +) => { + const wrapperClass = `${styles.wrapper} ${className}`; + const intl = useIntl(); + const formLabel = intl.formatMessage( + { + defaultMessage: 'Leave a reply to comment {id}', + description: 'ReplyCommentForm: an accessible name for the reply form', + id: 'ndAawq', + }, + { id: commentId } + ); + + return ( + + + {heading} + + {children} + + + ); +}; + +export const ReplyCommentForm = forwardRef(ReplyCommentFormWithRef); diff --git a/src/components/organisms/index.ts b/src/components/organisms/index.ts index 43414fa..7962603 100644 --- a/src/components/organisms/index.ts +++ b/src/components/organisms/index.ts @@ -1,3 +1,4 @@ +export * from './comment'; export * from './forms'; export * from './layout'; export * from './nav'; diff --git a/src/components/organisms/layout/comment.fixture.ts b/src/components/organisms/layout/comment.fixture.ts deleted file mode 100644 index 84aa20e..0000000 --- a/src/components/organisms/layout/comment.fixture.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { UserCommentProps } from './comment'; - -export const author = { - avatar: { - alt: 'Author avatar', - height: 480, - src: 'http://placeimg.com/640/480', - width: 640, - }, - name: 'Armand', - website: 'https://www.armandphilippot.com/', -}; - -export const content = - 'Harum aut cumque iure fugit neque sequi cupiditate repudiandae laudantium. Ratione aut assumenda qui illum voluptas accusamus quis officiis exercitationem. Consectetur est harum eius perspiciatis officiis nihil. Aut corporis minima debitis adipisci possimus debitis et.'; - -export const date = '2021-04-03 23:04:24'; - -export const meta = { - author, - date, -}; - -export const id = 5; - -export const saveComment = () => undefined; - -export const data: UserCommentProps = { - approved: true, - content, - id, - meta, - parentId: 0, - onSubmit: saveComment, -}; diff --git a/src/components/organisms/layout/comment.module.scss b/src/components/organisms/layout/comment.module.scss deleted file mode 100644 index 096f4c4..0000000 --- a/src/components/organisms/layout/comment.module.scss +++ /dev/null @@ -1,78 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; -@use "../../../styles/abstracts/mixins" as mix; -@use "../../../styles/abstracts/placeholders"; - -.avatar { - img { - border-radius: fun.convert-px(3); - box-shadow: - 0 0 0 fun.convert-px(1) var(--color-shadow-light), - fun.convert-px(2) fun.convert-px(2) 0 fun.convert-px(1) - var(--color-shadow); - } -} - -.author { - color: var(--color-primary-darker); - font-family: var(--font-family-regular); - font-size: var(--font-size-md); - font-weight: 600; - text-align: center; - 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; - } - } - } -} - -.form { - &__wrapper { - margin-top: var(--spacing-sm); - } - - &__heading { - width: fit-content; - margin: 0 auto var(--spacing-md) auto; - } - - margin-inline: auto; -} diff --git a/src/components/organisms/layout/comment.stories.tsx b/src/components/organisms/layout/comment.stories.tsx deleted file mode 100644 index 7426fc3..0000000 --- a/src/components/organisms/layout/comment.stories.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import { UserComment } from './comment'; -import { data } from './comment.fixture'; - -const saveComment = async () => { - /** Do nothing. */ -}; - -/** - * Comment - Storybook Meta - */ -export default { - title: 'Organisms/Layout/Comment', - component: UserComment, - args: { - canReply: true, - onSubmit: saveComment, - }, - argTypes: { - author: { - description: 'The author data.', - type: { - name: 'object', - required: true, - value: {}, - }, - }, - canReply: { - control: { - type: 'boolean', - }, - description: 'Enable or disable the reply button.', - table: { - category: 'Options', - defaultValue: { summary: true }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - content: { - control: { - type: 'text', - }, - description: 'The comment body.', - type: { - name: 'string', - required: true, - }, - }, - id: { - control: { - type: 'number', - }, - description: 'The comment id.', - type: { - name: 'number', - required: true, - }, - }, - parentId: { - control: { - type: null, - }, - description: 'The parent id if it is a reply.', - type: { - name: 'number', - required: false, - }, - }, - publication: { - description: 'The publication date.', - type: { - name: 'object', - required: true, - value: {}, - }, - }, - onSubmit: { - control: { - type: null, - }, - description: 'A callback function to save the comment form data.', - table: { - category: 'Events', - }, - type: { - name: 'function', - required: true, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ( - -); - -/** - * Layout Stories - Approved - */ -export const Approved = Template.bind({}); -Approved.args = { - ...data, -}; - -/** - * Layout Stories - Unapproved - */ -export const Unapproved = Template.bind({}); -Unapproved.args = { - ...data, - approved: false, -}; diff --git a/src/components/organisms/layout/comment.test.tsx b/src/components/organisms/layout/comment.test.tsx deleted file mode 100644 index 0e0ea3a..0000000 --- a/src/components/organisms/layout/comment.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen as rtlScreen } from '../../../../tests/utils'; -import { UserComment } from './comment'; -import { author, data, id } from './comment.fixture'; - -describe('UserComment', () => { - it('renders an avatar', () => { - render(); - expect( - rtlScreen.getByRole('img', { name: author.avatar.alt }) - ).toBeInTheDocument(); - }); - - it('renders the author website url', () => { - render(); - expect(rtlScreen.getByRole('link', { name: author.name })).toHaveAttribute( - 'href', - author.website - ); - }); - - it('renders a permalink to the comment', () => { - render(); - expect( - rtlScreen.getByRole('link', { - name: /\sat\s/, - }) - ).toHaveAttribute('href', `#comment-${id}`); - }); - - it('renders a reply button', () => { - render(); - expect( - rtlScreen.getByRole('button', { name: 'Reply' }) - ).toBeInTheDocument(); - }); - - it('does not render a reply button', () => { - render(); - expect( - rtlScreen.queryByRole('button', { name: 'Reply' }) - ).not.toBeInTheDocument(); - }); -}); diff --git a/src/components/organisms/layout/comment.tsx b/src/components/organisms/layout/comment.tsx deleted file mode 100644 index b55bb3d..0000000 --- a/src/components/organisms/layout/comment.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import NextImage from 'next/image'; -import Script from 'next/script'; -import type { FC } from 'react'; -import { useIntl } from 'react-intl'; -import type { Comment as CommentSchema, WithContext } from 'schema-dts'; -import type { SingleComment } from '../../../types'; -import { useSettings, useToggle } from '../../../utils/hooks'; -import { Button, Heading, Link, Time } from '../../atoms'; -import { - Card, - CardActions, - CardBody, - CardCover, - CardFooter, - CardHeader, - CardMeta, - CardTitle, -} from '../../molecules'; -import { CommentForm, type CommentFormProps } from '../forms'; -import styles from './comment.module.scss'; - -export type UserCommentProps = Pick< - SingleComment, - 'approved' | 'content' | 'id' | 'meta' | 'parentId' -> & - Pick & { - /** - * Enable or disable the reply button. Default: true. - */ - canReply?: boolean; - }; - -/** - * UserComment component - * - * Render a single comment. - */ -export const UserComment: FC = ({ - approved, - canReply = true, - content, - id, - meta, - onSubmit, - parentId, - ...props -}) => { - const intl = useIntl(); - const { website } = useSettings(); - const [isReplying, toggleIsReplying] = useToggle(false); - - if (!approved) { - return ( -
- {intl.formatMessage({ - defaultMessage: 'This comment is awaiting moderation...', - description: 'Comment: awaiting moderation', - id: '6a1Uo6', - })} -
- ); - } - - const { author, date } = meta; - - const buttonLabel = isReplying - ? intl.formatMessage({ - defaultMessage: 'Cancel reply', - description: 'Comment: cancel reply button', - id: 'LCorTC', - }) - : intl.formatMessage({ - defaultMessage: 'Reply', - description: 'Comment: reply button', - id: 'hzHuCc', - }); - const formTitle = intl.formatMessage({ - defaultMessage: 'Leave a reply', - description: 'Comment: comment form title', - id: '2fD5CI', - }); - - const commentSchema: WithContext = { - '@context': 'https://schema.org', - '@id': `${website.url}/#comment-${id}`, - '@type': 'Comment', - parentItem: parentId - ? { '@id': `${website.url}/#comment-${parentId}` } - : undefined, - about: { '@type': 'Article', '@id': `${website.url}/#article` }, - author: { - '@type': 'Person', - name: author.name, - image: author.avatar?.src, - url: author.website, - }, - creator: { - '@type': 'Person', - name: author.name, - image: author.avatar?.src, - url: author.website, - }, - dateCreated: date, - datePublished: date, - text: content, - }; - - return ( - <> -