From 94a8e31f25ed3d92f9eb808e3264bd4ac8c039c7 Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Thu, 21 Apr 2022 18:12:29 +0200
Subject: chore: add a PostsList component

---
 src/components/organisms/layout/posts-list.tsx | 122 +++++++++++++++++++++++++
 1 file changed, 122 insertions(+)
 create mode 100644 src/components/organisms/layout/posts-list.tsx

(limited to 'src/components/organisms/layout/posts-list.tsx')

diff --git a/src/components/organisms/layout/posts-list.tsx b/src/components/organisms/layout/posts-list.tsx
new file mode 100644
index 0000000..d67b03a
--- /dev/null
+++ b/src/components/organisms/layout/posts-list.tsx
@@ -0,0 +1,122 @@
+import Heading, { type HeadingLevel } from '@components/atoms/headings/heading';
+import { FC } from 'react';
+import { useIntl } from 'react-intl';
+import Summary, { type SummaryProps } from './summary';
+import styles from './posts-list.module.scss';
+
+export type Post = SummaryProps & {
+  /**
+   * The post id.
+   */
+  id: string | number;
+};
+
+export type YearCollection = {
+  [key: string]: Post[];
+};
+
+export type PostsListProps = {
+  /**
+   * True to display the posts by year. Default: false.
+   */
+  byYear?: boolean;
+  /**
+   * The posts data.
+   */
+  posts: Post[];
+  /**
+   * The posts heading level (hn).
+   */
+  titleLevel?: HeadingLevel;
+};
+
+/**
+ * Create a collection of posts sorted by year.
+ *
+ * @param {Posts[]} data - A collection of posts.
+ * @returns {YearCollection} The posts sorted by year.
+ */
+const sortPostsByYear = (data: Post[]): YearCollection => {
+  const yearCollection: YearCollection = {};
+
+  data.forEach((post) => {
+    const postYear = new Date(post.meta.publication.value as string)
+      .getFullYear()
+      .toString();
+    yearCollection[postYear] = [...(yearCollection[postYear] || []), post];
+  });
+
+  return yearCollection;
+};
+
+/**
+ * PostsList component
+ *
+ * Render a list of post summaries.
+ */
+const PostsList: FC<PostsListProps> = ({
+  byYear = false,
+  posts,
+  titleLevel,
+}) => {
+  const intl = useIntl();
+
+  /**
+   * Retrieve the list of posts.
+   *
+   * @param {Posts[]} data - A collection fo posts.
+   * @param {HeadingLevel} [headingLevel] - The posts heading level (hn).
+   * @returns {JSX.Element} The list of posts.
+   */
+  const getList = (
+    data: Post[],
+    headingLevel: HeadingLevel = 2
+  ): JSX.Element => {
+    return (
+      <ol className={styles.list}>
+        {data.map(({ id, ...post }) => (
+          <li key={id} className={styles.item}>
+            <Summary {...post} titleLevel={headingLevel} />
+          </li>
+        ))}
+      </ol>
+    );
+  };
+
+  /**
+   * Retrieve the list of posts.
+   *
+   * @returns {JSX.Element | JSX.Element[]} - The posts list.
+   */
+  const getPosts = (): JSX.Element | JSX.Element[] => {
+    if (!byYear) return getList(posts);
+
+    const postsPerYear = sortPostsByYear(posts);
+    const years = Object.keys(postsPerYear).reverse();
+
+    return years.map((year) => {
+      return (
+        <section key={year} className={styles.section}>
+          <Heading level={2} className={styles.year}>
+            {year}
+          </Heading>
+          {getList(postsPerYear[year], titleLevel)}
+        </section>
+      );
+    });
+  };
+
+  return posts.length === 0 ? (
+    <p>
+      {intl.formatMessage({
+        defaultMessage: 'No results found.',
+        description: 'PostsList: no results',
+        id: 'vK7Sxv',
+      })}
+    </p>
+  ) : (
+    <>{getPosts()}</>
+  );
+};
+
+export default PostsList;
-- 
cgit v1.2.3

