Commit b871d0ab by Moorthy G

feat(shared): forwardRef support for ResponsiveImage and ResponsiveVideo components

- ResponsiveImage now forwards ref to underlying img and keeps Source subcomponent
- ResponsiveVideo now forwards ref to underlying video and merges refs safely

BREAKING CHANGE: Consumers can now pass refs; no API removals
parent c3f821de
import { memo } from 'react'; import React, { forwardRef, memo } from 'react';
import React from 'react';
import { getImageProps } from 'next/image'; import { getImageProps } from 'next/image';
import breakpoints from './breakpoints'; import breakpoints from './breakpoints';
...@@ -31,9 +30,12 @@ export interface SourceProps extends React.HTMLAttributes<HTMLSourceElement> { ...@@ -31,9 +30,12 @@ export interface SourceProps extends React.HTMLAttributes<HTMLSourceElement> {
quality?: number; quality?: number;
} }
export const ResponsiveImage: React.FC<ResponsiveImageProps> & { type ResponsiveImageCompoundComponent = React.ForwardRefExoticComponent<
Source: React.FC<SourceProps>; React.PropsWithoutRef<ResponsiveImageProps> & React.RefAttributes<HTMLImageElement>
} = ({ > & { Source: React.FC<SourceProps> };
const ResponsiveImage = forwardRef<HTMLImageElement, ResponsiveImageProps>(
({
src, src,
width, width,
height, height,
...@@ -43,7 +45,7 @@ export const ResponsiveImage: React.FC<ResponsiveImageProps> & { ...@@ -43,7 +45,7 @@ export const ResponsiveImage: React.FC<ResponsiveImageProps> & {
priority, priority,
children, children,
...rest ...rest
}) => { }, ref) => {
if (src) { if (src) {
const { props } = getImageProps({ const { props } = getImageProps({
src, src,
...@@ -58,13 +60,18 @@ export const ResponsiveImage: React.FC<ResponsiveImageProps> & { ...@@ -58,13 +60,18 @@ export const ResponsiveImage: React.FC<ResponsiveImageProps> & {
return ( return (
<picture> <picture>
{children} {children}
<img {...props} {...rest} /> <img ref={ref} {...props} {...rest} />
</picture> </picture>
); );
} }
}; }
);
const ResponsiveImageWithStatics = ResponsiveImage as ResponsiveImageCompoundComponent;
ResponsiveImageWithStatics.displayName = 'ResponsiveImage';
ResponsiveImage.Source = memo( ResponsiveImageWithStatics.Source = memo(
({ src, width, height, sizes, media, quality, ...rest }: SourceProps) => { ({ src, width, height, sizes, media, quality, ...rest }: SourceProps) => {
if (src) { if (src) {
const query = breakpoints[media] || media; const query = breakpoints[media] || media;
...@@ -91,7 +98,7 @@ ResponsiveImage.Source = memo( ...@@ -91,7 +98,7 @@ ResponsiveImage.Source = memo(
} }
); );
ResponsiveImage.displayName = 'ResponsiveImage'; ResponsiveImageWithStatics.Source.displayName = 'ResponsiveImageSource';
ResponsiveImage.Source.displayName = 'ResponsiveImageSource';
export default ResponsiveImage; export { ResponsiveImageWithStatics as ResponsiveImage };
export default ResponsiveImageWithStatics;
'use client'; 'use client';
import React, { useState, useEffect, useRef } from 'react'; import React, { forwardRef, useEffect, useRef, useState } from 'react';
import breakpoints from './breakpoints'; import breakpoints from './breakpoints';
export interface ResponsiveVideoProps export interface ResponsiveVideoProps
...@@ -21,17 +21,26 @@ export interface ResponsiveVideoProps ...@@ -21,17 +21,26 @@ export interface ResponsiveVideoProps
autoPlayFallback?: React.ReactNode; autoPlayFallback?: React.ReactNode;
} }
const ResponsiveVideo = ({ const ResponsiveVideo = forwardRef<HTMLVideoElement, ResponsiveVideoProps>(({
src, src,
srcSet, srcSet,
autoPlay, autoPlay,
autoPlayFallback, autoPlayFallback,
...rest ...rest
}: ResponsiveVideoProps) => { }, ref) => {
const [isAutoPlayFailed, setIsAutoPlayFailed] = useState(false); const [isAutoPlayFailed, setIsAutoPlayFailed] = useState(false);
const [currentSrc, setCurrentSrc] = useState(src); const [currentSrc, setCurrentSrc] = useState(src);
const videoRef = useRef<HTMLVideoElement>(null); const videoRef = useRef<HTMLVideoElement>(null);
const setRefs = (node: HTMLVideoElement | null) => {
videoRef.current = node;
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
(ref as React.MutableRefObject<HTMLVideoElement | null>).current = node;
}
};
useEffect(() => { useEffect(() => {
const handleResize = () => { const handleResize = () => {
/** find the set that matches current media query */ /** find the set that matches current media query */
...@@ -82,8 +91,8 @@ const ResponsiveVideo = ({ ...@@ -82,8 +91,8 @@ const ResponsiveVideo = ({
return isAutoPlayFailed && autoPlayFallback ? ( return isAutoPlayFailed && autoPlayFallback ? (
autoPlayFallback autoPlayFallback
) : ( ) : (
<video ref={videoRef} src={currentSrc} {...rest} /> <video ref={setRefs} src={currentSrc} {...rest} />
); );
}; });
export default ResponsiveVideo; export default ResponsiveVideo;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment