From 36890cfafeba6e30782df1260d7f9e678c7da4bf Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Mon, 2 Oct 2023 17:01:57 +0200
Subject: refactor(components): rewrite DescriptionList component

* add a `spacing` prop
* replace `layout` prop with `isInline` prop
* remove `items` prop (and classNames props) in favor of new components:
Description, Group, Term
* remove `withSeparator` prop (CSS content is announced by screen readers
and Firefox/Safari have no support for alternative text so the consumer
should add itself an element with `aria-hidden` if it need a separator)

Be aware, Meta component and its consumers can be visually broken, they
should be refactored before using them in production.
---
 src/components/molecules/layout/meta.tsx | 103 ++++++++++++++++---------------
 1 file changed, 54 insertions(+), 49 deletions(-)

(limited to 'src/components/molecules/layout/meta.tsx')

diff --git a/src/components/molecules/layout/meta.tsx b/src/components/molecules/layout/meta.tsx
index 53128a7..094c420 100644
--- a/src/components/molecules/layout/meta.tsx
+++ b/src/components/molecules/layout/meta.tsx
@@ -1,16 +1,19 @@
-import { FC, ReactNode } from 'react';
+import type { FC, ReactNode } from 'react';
 import { useIntl } from 'react-intl';
 import { getFormattedDate, getFormattedTime } from '../../../utils/helpers';
 import {
   DescriptionList,
   type DescriptionListProps,
-  type DescriptionListItem,
   Link,
+  Group,
+  Term,
+  Description,
 } from '../../atoms';
+import styles from './meta.module.scss';
 
 export type CustomMeta = {
   label: string;
-  value: ReactNode | ReactNode[];
+  value: ReactNode;
 };
 
 export type MetaComments = {
@@ -106,24 +109,16 @@ export type MetaData = {
   website?: string;
 };
 
-export type MetaKey = keyof MetaData;
+const isCustomMeta = (
+  key: keyof MetaData,
+  _value: unknown
+): _value is MetaData['custom'] => key === 'custom';
 
-export type MetaProps = Omit<
-  DescriptionListProps,
-  'items' | 'withSeparator'
-> & {
+export type MetaProps = Omit<DescriptionListProps, 'children'> & {
   /**
    * The meta data.
    */
   data: MetaData;
-  /**
-   * The items layout.
-   */
-  itemsLayout?: DescriptionListItem['layout'];
-  /**
-   * If true, use a slash to delimitate multiple values. Default: true.
-   */
-  withSeparator?: DescriptionListProps['withSeparator'];
 };
 
 /**
@@ -132,11 +127,13 @@ export type MetaProps = Omit<
  * Renders the given metadata.
  */
 export const Meta: FC<MetaProps> = ({
+  className = '',
   data,
-  itemsLayout = 'inline-values',
-  withSeparator = true,
+  isInline = false,
   ...props
 }) => {
+  const layoutClass = styles[isInline ? 'list--inline' : 'list--stack'];
+  const listClass = `${styles.list} ${layoutClass} ${className}`;
   const intl = useIntl();
 
   /**
@@ -316,7 +313,7 @@ export const Meta: FC<MetaProps> = ({
    * @param {ValueOf<MetaData>} value - The meta value.
    * @returns {string|ReactNode|ReactNode[]} - The formatted value.
    */
-  const getValue = <T extends MetaKey>(
+  const getValue = <T extends keyof MetaData>(
     key: T,
     value: MetaData[T]
   ): string | ReactNode | ReactNode[] => {
@@ -338,12 +335,11 @@ export const Meta: FC<MetaProps> = ({
           { postsCount: value as number }
         );
       case 'website':
-        const url = value as string;
-        return (
-          <Link href={url} external={true}>
-            {url}
+        return typeof value === 'string' ? (
+          <Link href={value} external={true}>
+            {value}
           </Link>
-        );
+        ) : null;
       default:
         return value as string | ReactNode | ReactNode[];
     }
@@ -355,36 +351,45 @@ export const Meta: FC<MetaProps> = ({
    * @param {MetaData} items - The meta.
    * @returns {DescriptionListItem[]} The formatted description list items.
    */
-  const getItems = (items: MetaData): DescriptionListItem[] => {
-    const listItems: DescriptionListItem[] = Object.entries(items)
-      .map(([key, value]) => {
-        if (!key || !value) return;
-
-        const metaKey = key as MetaKey;
+  const getItems = (items: MetaData) => {
+    const entries = Object.entries(items) as [
+      keyof MetaData,
+      MetaData[keyof MetaData],
+    ][];
+    const listItems = entries.map(([key, meta]) => {
+      if (!meta) return null;
 
-        return {
-          id: metaKey,
-          label:
-            metaKey === 'custom'
-              ? (value as CustomMeta).label
-              : getLabel(metaKey),
-          layout: itemsLayout,
-          value:
-            metaKey === 'custom' && (value as CustomMeta)
-              ? (value as CustomMeta).value
-              : getValue(metaKey, value),
-        } as DescriptionListItem;
-      })
-      .filter((item): item is DescriptionListItem => !!item);
+      return (
+        <Group isInline key={key} spacing="2xs">
+          <Term className={styles.term}>
+            {isCustomMeta(key, meta) ? meta.label : getLabel(key)}
+          </Term>
+          {Array.isArray(meta) ? (
+            meta.map((singleMeta, index) => (
+              /* eslint-disable-next-line react/no-array-index-key -- Unsafe,
+               * but also temporary. This component should be removed or
+               * refactored. */
+              <Description className={styles.description} key={index}>
+                {isCustomMeta(key, singleMeta)
+                  ? singleMeta
+                  : getValue(key, singleMeta)}
+              </Description>
+            ))
+          ) : (
+            <Description className={styles.description}>
+              {isCustomMeta(key, meta) ? meta.value : getValue(key, meta)}
+            </Description>
+          )}
+        </Group>
+      );
+    });
 
     return listItems;
   };
 
   return (
-    <DescriptionList
-      {...props}
-      items={getItems(data)}
-      withSeparator={withSeparator}
-    />
+    <DescriptionList {...props} className={listClass} isInline={isInline}>
+      {getItems(data)}
+    </DescriptionList>
   );
 };
-- 
cgit v1.2.3

