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 from 'react';
import React, { forwardRef, memo } from 'react';
import { getImageProps } from 'next/image';
import breakpoints from './breakpoints';
......@@ -31,40 +30,48 @@ export interface SourceProps extends React.HTMLAttributes<HTMLSourceElement> {
quality?: number;
}
export const ResponsiveImage: React.FC<ResponsiveImageProps> & {
Source: React.FC<SourceProps>;
} = ({
src,
width,
height,
alt,
sizes,
quality,
priority,
children,
...rest
}) => {
if (src) {
const { props } = getImageProps({
src,
width,
height,
sizes,
quality,
priority,
alt: alt || ''
});
type ResponsiveImageCompoundComponent = React.ForwardRefExoticComponent<
React.PropsWithoutRef<ResponsiveImageProps> & React.RefAttributes<HTMLImageElement>
> & { Source: React.FC<SourceProps> };
return (
<picture>
{children}
<img {...props} {...rest} />
</picture>
);
const ResponsiveImage = forwardRef<HTMLImageElement, ResponsiveImageProps>(
({
src,
width,
height,
alt,
sizes,
quality,
priority,
children,
...rest
}, ref) => {
if (src) {
const { props } = getImageProps({
src,
width,
height,
sizes,
quality,
priority,
alt: alt || ''
});
return (
<picture>
{children}
<img ref={ref} {...props} {...rest} />
</picture>
);
}
}
};
);
const ResponsiveImageWithStatics = ResponsiveImage as ResponsiveImageCompoundComponent;
ResponsiveImageWithStatics.displayName = 'ResponsiveImage';
ResponsiveImage.Source = memo(
ResponsiveImageWithStatics.Source = memo(
({ src, width, height, sizes, media, quality, ...rest }: SourceProps) => {
if (src) {
const query = breakpoints[media] || media;
......@@ -91,7 +98,7 @@ ResponsiveImage.Source = memo(
}
);
ResponsiveImage.displayName = 'ResponsiveImage';
ResponsiveImage.Source.displayName = 'ResponsiveImageSource';
ResponsiveImageWithStatics.Source.displayName = 'ResponsiveImageSource';
export default ResponsiveImage;
export { ResponsiveImageWithStatics as ResponsiveImage };
export default ResponsiveImageWithStatics;
'use client';
import React, { useState, useEffect, useRef } from 'react';
import React, { forwardRef, useEffect, useRef, useState } from 'react';
import breakpoints from './breakpoints';
export interface ResponsiveVideoProps
......@@ -21,17 +21,26 @@ export interface ResponsiveVideoProps
autoPlayFallback?: React.ReactNode;
}
const ResponsiveVideo = ({
const ResponsiveVideo = forwardRef<HTMLVideoElement, ResponsiveVideoProps>(({
src,
srcSet,
autoPlay,
autoPlayFallback,
...rest
}: ResponsiveVideoProps) => {
}, ref) => {
const [isAutoPlayFailed, setIsAutoPlayFailed] = useState(false);
const [currentSrc, setCurrentSrc] = useState(src);
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(() => {
const handleResize = () => {
/** find the set that matches current media query */
......@@ -82,8 +91,8 @@ const ResponsiveVideo = ({
return isAutoPlayFailed && autoPlayFallback ? (
autoPlayFallback
) : (
<video ref={videoRef} src={currentSrc} {...rest} />
<video ref={setRefs} src={currentSrc} {...rest} />
);
};
});
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