Skip to content

Commit

Permalink
Merge branch 'main' into L3-4162-sale-header-banner-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaron Hill authored and Aaron Hill committed Dec 12, 2024
2 parents 8773b48 + 55426e1 commit 63cd4ff
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 140 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# [1.102.0](https://github.com/PhillipsAuctionHouse/seldon/compare/v1.101.0...v1.102.0) (2024-12-11)


### Features

* **seldonimage:** L3-4753 more opts ([#448](https://github.com/PhillipsAuctionHouse/seldon/issues/448)) ([61c9933](https://github.com/PhillipsAuctionHouse/seldon/commit/61c9933f084db82ab356705bd24af4968c98d765))

# [1.101.0](https://github.com/PhillipsAuctionHouse/seldon/compare/v1.100.2...v1.101.0) (2024-12-10)


### Features

* **objecttile:** L3-4753 accept srcset & sizes from object tile ([#447](https://github.com/PhillipsAuctionHouse/seldon/issues/447)) ([dfd959f](https://github.com/PhillipsAuctionHouse/seldon/commit/dfd959fbcd3ee97ec927a7b456078ca576757da3))

## [1.100.2](https://github.com/PhillipsAuctionHouse/seldon/compare/v1.100.1...v1.100.2) (2024-12-05)


Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@phillips/seldon",
"version": "1.100.2",
"version": "1.102.0",
"repository": {
"type": "git",
"url": "https://github.com/PhillipsAuctionHouse/seldon"
Expand Down
27 changes: 7 additions & 20 deletions src/components/SeldonImage/SeldonImage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import SeldonImage from './SeldonImage';
import { runCommonTests } from '../../utils/testUtils';
import { px } from '../../utils';
Expand Down Expand Up @@ -39,26 +39,13 @@ describe('SeldonImage', () => {
expect(image).toHaveStyle({ borderRadius: '50%' });
});

it('removes hidden classes on image load', () => {
const { container } = render(<SeldonImage src="test-image.jpg" alt="" />);
const image = screen.getByTestId(`seldon-image-img`);

expect(container.firstChild).toHaveClass(`${px}-seldon-image--hidden`);
expect(image).toHaveClass(`${px}-seldon-image-img--hidden`);

fireEvent.load(image);

expect(container.firstChild).not.toHaveClass(`${px}-seldon-image--hidden`);
expect(image).not.toHaveClass(`${px}-seldon-image-img--hidden`);
});

it('sets loading state to error when image is invalid', () => {
it('sets loading state to error when image is invalid', async () => {
render(<SeldonImage src="broken" alt="" />);
const image = screen.getByTestId(`seldon-image-img`);
expect(image).toHaveClass(`${px}-seldon-image-img--hidden`);
fireEvent.error(image);

const errorPlaceholder = screen.getByTestId('header-logo-svg');
expect(errorPlaceholder).toBeInTheDocument();
await waitFor(() => {
fireEvent.error(image);
const errorPlaceholder = screen.getByTestId('header-logo-svg');
expect(errorPlaceholder).toBeInTheDocument();
});
});
});
86 changes: 76 additions & 10 deletions src/components/SeldonImage/SeldonImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export interface SeldonImageProps extends ComponentProps<'div'> {
* The sizes of the image [https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes]
*/
sizes?: string;
/**
* The loading attribute of the image. [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading]
*/
loading?: ComponentProps<'img'>['loading'];
/**
* The fetch priority of the image. [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-fetchpriority]
*/
fetchPriority?: ComponentProps<'img'>['fetchPriority'];
/**
* The alt text of the image.
*/
Expand All @@ -49,17 +57,41 @@ export interface SeldonImageProps extends ComponentProps<'div'> {
errorText?: string;
}

function isImageValid(src: string) {
function isImageValid({
img,
src,
srcSet,
sizes,
}: {
img: HTMLImageElement | null;
src: string;
srcSet?: string;
sizes?: string;
}) {
const promise = new Promise((resolve) => {
const img = document.createElement('img');
img.onerror = () => resolve(false);
img.onload = () => resolve(true);
img.src = src;
let imgElement = img;
if (!imgElement) {
imgElement = document.createElement('img');
}
imgElement.onerror = () => resolve(false);
imgElement.onload = () => resolve(true);
if (srcSet) {
imgElement.srcset = srcSet;
}
if (sizes) {
imgElement.sizes = sizes;
}
imgElement.src = src;
if (imgElement.complete) {
resolve(true);
}
});

return promise;
}

const isServer = typeof window === 'undefined';

/**
* ## Overview
*
Expand All @@ -83,6 +115,8 @@ const SeldonImage = memo(
alt,
srcSet,
sizes,
loading,
fetchPriority,
errorText = 'Error loading image',
...props
},
Expand All @@ -91,16 +125,44 @@ const SeldonImage = memo(
const { className: baseClassName, ...commonProps } = getCommonProps(props, 'SeldonImage');
const imgRef = useRef<HTMLImageElement>(null);

const [loadingState, setLoadingState] = useState<'loading' | 'loaded' | 'error'>('loading');
const [loadingState, setLoadingState] = useState<'loading' | 'loaded' | 'error'>(() => {
if (isServer) {
return 'loading';
}
const element = document.getElementById(src);

if (element instanceof HTMLImageElement && !element.classList.contains(`${baseClassName}-img--hidden`)) {
return 'loaded';
}

const img = document.createElement('img');
if (srcSet) {
img.srcset = srcSet;
}
if (sizes) {
img.sizes = sizes;
}
img.src = src;
if (img.complete) {
return 'loaded';
}

return 'loading';
});

const loadImage = useCallback(async () => {
const isValid = await isImageValid(src);
const isValid = await isImageValid({
img: imgRef.current,
src,
srcSet,
sizes,
});
if (!isValid) {
setLoadingState('error');
} else {
setLoadingState('loaded');
}
}, [src]);
}, [src, srcSet, sizes]);

useEffect(() => {
void loadImage();
Expand All @@ -110,7 +172,6 @@ const SeldonImage = memo(
<div
ref={ref}
className={classnames(baseClassName, className, {
[`${baseClassName}--hidden`]: loadingState === 'loading' || loadingState === 'error',
[`${baseClassName}--aspect-ratio-${aspectRatio.replace('/', '-')}`]: aspectRatio !== 'none',
})}
role="img"
Expand All @@ -133,16 +194,21 @@ const SeldonImage = memo(
) : null}
<img
className={classnames(`${baseClassName}-img`, imageClassName, {
[`${baseClassName}-img--hidden`]: loadingState !== 'loaded',
[`${baseClassName}-img--hidden`]: loadingState === 'error',
[`${baseClassName}-img--object-fit-${objectFit}`]: objectFit !== 'none',
})}
id={src}
style={imageStyle}
src={src}
srcSet={srcSet}
sizes={sizes}
alt={alt}
data-testid={`${commonProps['data-testid']}-img`}
ref={imgRef}
loading={loading}
// @ts-expect-error - React throws error when this is passed as fetchPriority, so we need to disable the rule
// let it be known that this is a valid attribute [https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority]
fetchpriority={fetchPriority} // eslint-disable-line react/no-unknown-property
onLoad={() => {
setLoadingState('loaded');
}}
Expand Down
4 changes: 0 additions & 4 deletions src/components/SeldonImage/_seldonImage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
aspect-ratio: 1 / 1;
}

&--hidden {
background-color: #eee;
}

&--error {
width: 100%;

Expand Down
7 changes: 4 additions & 3 deletions src/patterns/ObjectTile/ObjectTile.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Meta } from '@storybook/react';
import { addMinutes } from 'date-fns';

import ObjectTile, { ObjectTileProps } from './ObjectTile';
import ObjectTile from './ObjectTile';
import { LotStatus } from '../../types/commonTypes';
import { BidMessage, BidSnapshot, BidStatusEnum } from '../BidSnapshot';
import { Favorite } from '../../assets/icons';
import { ComponentProps } from 'react';

// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta = {
Expand Down Expand Up @@ -41,8 +42,8 @@ const args = {
modelText: 'Nautilus',
};

export const NoImage = (props: ObjectTileProps) => <ObjectTile {...props} imageUrl={undefined} />;
export const NoImage = (props: ComponentProps<typeof ObjectTile>) => <ObjectTile {...props} imageUrl={undefined} />;
NoImage.args = args;
export const Playground = (props: ObjectTileProps) => <ObjectTile {...props} />;
export const Playground = (props: ComponentProps<typeof ObjectTile>) => <ObjectTile {...props} />;
Playground.args = args;
Playground.argTypes = {};
Loading

0 comments on commit 63cd4ff

Please sign in to comment.