summaryrefslogtreecommitdiffstats
path: root/src/components/organisms/layout/posts-list.tsx
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-04-21 18:12:29 +0200
committerArmand Philippot <git@armandphilippot.com>2022-04-21 18:12:29 +0200
commit94a8e31f25ed3d92f9eb808e3264bd4ac8c039c7 (patch)
tree15faac7c00be70b41127ad24318717c4719dfaba /src/components/organisms/layout/posts-list.tsx
parent3a68d155afd4559e141bcb6c1ca3d833d3bd4667 (diff)
chore: add a PostsList component
Diffstat (limited to 'src/components/organisms/layout/posts-list.tsx')
-rw-r--r--src/components/organisms/layout/posts-list.tsx122
1 files changed, 122 insertions, 0 deletions
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;