From 15522ec9146f6f1956620355c44dea2a6a75b67c Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Mon, 9 Oct 2023 18:26:23 +0200
Subject: refactor(components): replace ResponsiveImage with Figure component

The styles applied to ResponsiveImage are related to the figure and
figcaption elements. Those elements could be use with other contents
than images. So I extracted them in a Figure component. The
ResponsiveImage component is no longer useful: the consumer should use
the Image component from `next` and wrap it in a link if needed.
---
 src/components/atoms/figure/figure.tsx | 72 ++++++++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)
 create mode 100644 src/components/atoms/figure/figure.tsx

(limited to 'src/components/atoms/figure/figure.tsx')

diff --git a/src/components/atoms/figure/figure.tsx b/src/components/atoms/figure/figure.tsx
new file mode 100644
index 0000000..4dd5b10
--- /dev/null
+++ b/src/components/atoms/figure/figure.tsx
@@ -0,0 +1,72 @@
+import {
+  forwardRef,
+  type ReactNode,
+  type ForwardRefRenderFunction,
+  type HTMLAttributes,
+  useId,
+} from 'react';
+import styles from './figure.module.scss';
+
+export type FigureProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
+  /**
+   * The contents (ie. an image, illustration, diagram, code snippet, etc.).
+   */
+  children: ReactNode;
+  /**
+   * A figure caption.
+   */
+  caption?: ReactNode;
+  /**
+   * Should we wrap the contents with borders?
+   */
+  hasBorders?: boolean;
+};
+
+const FigureWithRef: ForwardRefRenderFunction<HTMLElement, FigureProps> = (
+  {
+    'aria-labelledby': ariaLabelledBy,
+    caption,
+    children,
+    className = '',
+    hasBorders,
+    ...props
+  },
+  ref
+) => {
+  const captionId = useId();
+  const bordersModifier = hasBorders ? styles['wrapper--has-borders'] : '';
+  const figureClass = `${styles.wrapper} ${bordersModifier} ${className}`;
+
+  /**
+   * We need to ensure that the figcaption is used as an accessible name for the
+   * figure. In Testing Library, it is not automatically associated, it could
+   * also be the case in some browsers. However if the consumer provide its own
+   * `aria-labelled-by` attribute, it should be used instead of the caption (we
+   * could combine them but we cannot know which order is the more logical).
+   */
+  const figureLabelledBy =
+    caption && !ariaLabelledBy ? captionId : ariaLabelledBy;
+
+  return (
+    <figure
+      {...props}
+      aria-labelledby={figureLabelledBy}
+      className={figureClass}
+      ref={ref}
+    >
+      {children}
+      {caption ? (
+        <figcaption className={styles.caption} id={captionId}>
+          {caption}
+        </figcaption>
+      ) : null}
+    </figure>
+  );
+};
+
+/**
+ * Figure component
+ *
+ * Render a responsive image wrapped in a figure element.
+ */
+export const Figure = forwardRef(FigureWithRef);
-- 
cgit v1.2.3

