From c87c615b5866b8a8f361eeb0764bfdea85740e90 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Tue, 10 Oct 2023 19:37:51 +0200
Subject: refactor(components): replace Meta component with MetaList

It removes items complexity by allowing consumers to use any label/value
association. Translations should also be defined by the consumer.
Each item can now be configured separately (borders, layout...).
---
 src/components/organisms/layout/summary.tsx | 175 +++++++++++++++++++++-------
 1 file changed, 133 insertions(+), 42 deletions(-)

(limited to 'src/components/organisms/layout/summary.tsx')

diff --git a/src/components/organisms/layout/summary.tsx b/src/components/organisms/layout/summary.tsx
index fa3dfe5..f5c16cd 100644
--- a/src/components/organisms/layout/summary.tsx
+++ b/src/components/organisms/layout/summary.tsx
@@ -2,6 +2,7 @@ import NextImage, { type ImageProps as NextImageProps } from 'next/image';
 import type { FC, ReactNode } from 'react';
 import { useIntl } from 'react-intl';
 import type { Article, Meta as MetaType } from '../../../types';
+import { getFormattedDate } from '../../../utils/helpers';
 import { useReadingTime } from '../../../utils/hooks';
 import {
   ButtonLink,
@@ -11,7 +12,7 @@ import {
   Link,
   Figure,
 } from '../../atoms';
-import { Meta, type MetaData } from '../../molecules';
+import { MetaList, type MetaItemData } from '../../molecules';
 import styles from './summary.module.scss';
 
 export type Cover = Pick<NextImageProps, 'alt' | 'src' | 'width' | 'height'>;
@@ -69,42 +70,134 @@ export const Summary: FC<SummaryProps> = ({
       ),
     }
   );
-  const { author, commentsCount, cover, dates, thematics, topics, wordsCount } =
-    meta;
-  const readingTime = useReadingTime(wordsCount, true);
+  const readingTime = useReadingTime(meta.wordsCount, true);
 
-  const getMeta = (): MetaData => {
-    return {
-      author: author?.name,
-      publication: { date: dates.publication },
-      update:
-        dates.update && dates.publication !== dates.update
-          ? { date: dates.update }
-          : undefined,
-      readingTime,
-      thematics: thematics?.map((thematic) => (
-        <Link key={thematic.id} href={thematic.url}>
-          {thematic.name}
-        </Link>
-      )),
-      topics: topics?.map((topic) => (
-        <Link key={topic.id} href={topic.url}>
-          {topic.name}
-        </Link>
-      )),
-      comments: {
-        about: title,
-        count: commentsCount ?? 0,
-        target: `${url}#comments`,
+  /**
+   * Retrieve a formatted date (and time).
+   *
+   * @param {string} date - A date string.
+   * @returns {JSX.Element} The formatted date wrapped in a time element.
+   */
+  const getDate = (date: string): JSX.Element => {
+    const isoDate = new Date(`${date}`).toISOString();
+
+    return <time dateTime={isoDate}>{getFormattedDate(date)}</time>;
+  };
+
+  const getMetaItems = (): MetaItemData[] => {
+    const summaryMeta: MetaItemData[] = [
+      {
+        id: 'publication-date',
+        label: intl.formatMessage({
+          defaultMessage: 'Published on:',
+          description: 'Summary: publication date label',
+          id: 'TvQ2Ee',
+        }),
+        value: getDate(meta.dates.publication),
       },
-    };
+    ];
+
+    if (meta.dates.update && meta.dates.update !== meta.dates.publication)
+      summaryMeta.push({
+        id: 'update-date',
+        label: intl.formatMessage({
+          defaultMessage: 'Updated on:',
+          description: 'Summary: update date label',
+          id: 'f0Z/Po',
+        }),
+        value: getDate(meta.dates.update),
+      });
+
+    summaryMeta.push({
+      id: 'reading-time',
+      label: intl.formatMessage({
+        defaultMessage: 'Reading time:',
+        description: 'Summary: reading time label',
+        id: 'tyzdql',
+      }),
+      value: readingTime,
+    });
+
+    if (meta.author)
+      summaryMeta.push({
+        id: 'author',
+        label: intl.formatMessage({
+          defaultMessage: 'Written by:',
+          description: 'Summary: author label',
+          id: 'r/6HOI',
+        }),
+        value: meta.author.name,
+      });
+
+    if (meta.thematics)
+      summaryMeta.push({
+        id: 'thematics',
+        label: intl.formatMessage({
+          defaultMessage: 'Thematics:',
+          description: 'Summary: thematics label',
+          id: 'bk0WOp',
+        }),
+        value: meta.thematics.map((thematic) => {
+          return {
+            id: `thematic-${thematic.id}`,
+            value: <Link href={thematic.url}>{thematic.name}</Link>,
+          };
+        }),
+      });
+
+    if (meta.topics)
+      summaryMeta.push({
+        id: 'topics',
+        label: intl.formatMessage({
+          defaultMessage: 'Topics:',
+          description: 'Summary: topics label',
+          id: 'yIZ+AC',
+        }),
+        value: meta.topics.map((topic) => {
+          return {
+            id: `topic-${topic.id}`,
+            value: <Link href={topic.url}>{topic.name}</Link>,
+          };
+        }),
+      });
+
+    if (meta.commentsCount !== undefined) {
+      const commentsCount = intl.formatMessage(
+        {
+          defaultMessage:
+            '{commentsCount, plural, =0 {No comments} one {# comment} other {# comments}}<a11y> about {title}</a11y>',
+          description: 'Summary: comments count',
+          id: 'ye/vlA',
+        },
+        {
+          a11y: (chunks: ReactNode) => (
+            <span className="screen-reader-text">{chunks}</span>
+          ),
+          commentsCount: meta.commentsCount,
+          title,
+        }
+      );
+      summaryMeta.push({
+        id: 'comments-count',
+        label: intl.formatMessage({
+          defaultMessage: 'Comments:',
+          description: 'Summary: comments label',
+          id: 'bfPp0g',
+        }),
+        value: (
+          <Link href={`${url}#comments`}>{commentsCount as JSX.Element}</Link>
+        ),
+      });
+    }
+
+    return summaryMeta;
   };
 
   return (
     <article className={styles.wrapper}>
-      {cover ? (
+      {meta.cover ? (
         <Figure>
-          <NextImage {...cover} className={styles.cover} />
+          <NextImage {...meta.cover} className={styles.cover} />
         </Figure>
       ) : null}
       <header className={styles.header}>
@@ -121,21 +214,19 @@ export const Summary: FC<SummaryProps> = ({
           dangerouslySetInnerHTML={{ __html: intro }}
         />
         <ButtonLink className={styles['read-more']} to={url}>
-          <>
-            {readMore}
-            <Icon
-              aria-hidden={true}
-              className={styles.icon}
-              // eslint-disable-next-line react/jsx-no-literals -- Direction allowed
-              orientation="right"
-              // eslint-disable-next-line react/jsx-no-literals -- Shape allowed
-              shape="arrow"
-            />
-          </>
+          {readMore}
+          <Icon
+            aria-hidden={true}
+            className={styles.icon}
+            // eslint-disable-next-line react/jsx-no-literals -- Direction allowed
+            orientation="right"
+            // eslint-disable-next-line react/jsx-no-literals -- Shape allowed
+            shape="arrow"
+          />
         </ButtonLink>
       </div>
       <footer className={styles.footer}>
-        <Meta className={styles.meta} data={getMeta()} spacing="xs" />
+        <MetaList className={styles.meta} items={getMetaItems()} />
       </footer>
     </article>
   );
-- 
cgit v1.2.3

