aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/figure/figure.tsx
blob: 4dd5b105f1848ba674e2f4181828019fe05314d6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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);