diff --git a/src/PhotoAlbum.tsx b/src/PhotoAlbum.tsx index 0c33e308..eb15f8b1 100644 --- a/src/PhotoAlbum.tsx +++ b/src/PhotoAlbum.tsx @@ -18,6 +18,7 @@ const resolveLayoutOptions = ({ columns, spacing, padding, + sizes, }: Omit, "photos"> & { viewportWidth?: number; containerWidth: number; @@ -35,6 +36,7 @@ const resolveLayoutOptions = ({ (w) => w / 3, (w) => w / 2, ]), + sizes, }); const PhotoAlbum = (props: PhotoAlbumProps): JSX.Element => { @@ -44,6 +46,7 @@ const PhotoAlbum = (props: PhotoAlbumProps): JSX.Element => columns, spacing, padding, + sizes, onClick, targetRowHeight, defaultContainerWidth = 800, @@ -113,6 +116,7 @@ const PhotoAlbum = (props: PhotoAlbumProps): JSX.Element => padding, columns, targetRowHeight, + sizes, }); const commonLayoutProps = { photos, renderPhoto, instrumentation }; diff --git a/src/components/renderers/PhotoRenderer.tsx b/src/components/renderers/PhotoRenderer.tsx index 9bc702c6..3279eee6 100644 --- a/src/components/renderers/PhotoRenderer.tsx +++ b/src/components/renderers/PhotoRenderer.tsx @@ -1,33 +1,29 @@ import * as React from "react"; + import round from "../../utils/round"; import { LayoutOptions, Photo, PhotoLayout, PhotoProps, RenderPhoto } from "../../types"; -const cssWidth = (photoLayout: PhotoLayout, layoutOptions: LayoutOptions) => { - const { width } = photoLayout; - const { spacing, padding, layout, containerWidth } = layoutOptions; +const calcWidth = ( + base: string, + { width, photosCount }: PhotoLayout, + { spacing, padding, containerWidth }: LayoutOptions +) => { + const gaps = spacing * (photosCount - 1) + 2 * padding * photosCount; + return `calc((${base} - ${gaps}px) / ${round((containerWidth - gaps) / width, 5)})`; +}; - if (layout !== "rows") { - return `calc(100% - ${2 * padding}px)`; +const cssWidth = (layout: PhotoLayout, layoutOptions: LayoutOptions) => { + if (layoutOptions.layout !== "rows") { + return `calc(100% - ${2 * layoutOptions.padding}px)`; } - - const rowSize = photoLayout.photosCount; - return `calc((100% - ${spacing * (rowSize - 1) + 2 * padding * rowSize}px) / ${round( - (containerWidth - spacing * (rowSize - 1) - 2 * padding * rowSize) / width, - 5 - )})`; + return calcWidth("100%", layout, layoutOptions); }; -const srcSetAndSizes = ({ - photo, - layout, - layoutOptions, -}: { - photo: T; - layout: PhotoLayout; - layoutOptions: LayoutOptions; -}) => { - let srcSet; - let sizes; +const calculateSizesValue = (size: string, layout: PhotoLayout, layoutOptions: LayoutOptions) => + calcWidth(size.match(/calc\((.*)\)/)?.[1] ?? size, layout, layoutOptions); + +const srcSetAndSizes = (photo: T, layout: PhotoLayout, layoutOptions: LayoutOptions) => { + let srcSet, sizes; if (photo.images && photo.images.length > 0) { srcSet = photo.images @@ -41,6 +37,14 @@ const srcSetAndSizes = ({ .sort((first, second) => first.width - second.width) .map((image) => `${image.src} ${image.width}w`) .join(", "); + } + + if (!layoutOptions.viewportWidth && layoutOptions.sizes) { + sizes = (layoutOptions.sizes.sizes || []) + .map(({ viewport, size }) => `${viewport} ${calculateSizesValue(size, layout, layoutOptions)}`) + .concat(calculateSizesValue(layoutOptions.sizes.size, layout, layoutOptions)) + .join(", "); + } else { sizes = `${Math.ceil((layout.width / (layoutOptions.viewportWidth || layoutOptions.containerWidth)) * 100)}vw`; } @@ -48,8 +52,8 @@ const srcSetAndSizes = ({ }; const DefaultPhotoRenderer = ({ imageProps }: PhotoProps) => { - const { src, alt, ...rest } = imageProps; - return {alt}; + const { src, alt, srcSet, sizes, ...rest } = imageProps; + return {alt}; }; type PhotoRendererProps = { @@ -83,17 +87,14 @@ const PhotoRenderer = (props: PhotoRendererProps) => } : undefined; - const { srcSet, sizes } = srcSetAndSizes({ photo, layout, layoutOptions }); - const imageProps = { src: photo.src, alt: photo.alt ?? "", title: photo.title, onClick: handleClick, style, - sizes, - srcSet, className: "react-photo-album--photo", + ...srcSetAndSizes(photo, layout, layoutOptions), }; const Component = renderPhoto || DefaultPhotoRenderer; diff --git a/src/types.ts b/src/types.ts index 339b9c17..576fde51 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,6 +16,18 @@ export type ResponsiveParameterProvider = (containerWidth: number) => number; export type ResponsiveParameter = number | ResponsiveParameterProvider; +export type ResponsiveSizes = { + /** default size e.g. 100vw or calc(100vw - 200px) */ + size: string; + /** array of sizes at various breakpoint */ + sizes?: { + /** viewport size media query e.g. (max-width: 600px) */ + viewport: string; + /** photo album width at given viewport size e.g. calc(100vw - 50px) */ + size: string; + }[]; +}; + export interface Image { /** image source */ src: string; @@ -72,6 +84,8 @@ export type PhotoAlbumProps = { padding?: ResponsiveParameter; /** target row height in 'rows' layout */ targetRowHeight?: ResponsiveParameter; + /** photo album size at various viewport sizes */ + sizes?: ResponsiveSizes; /** photo click handler */ onClick?: ClickHandler; /** default container width to be used in SSR render */ @@ -103,6 +117,8 @@ export type GenericLayoutOptions = { viewportWidth?: number; /** photo click handler */ onClick?: ClickHandler; + /** photo album size at various viewport sizes */ + sizes?: ResponsiveSizes; }; export type RowsLayoutOptions = GenericLayoutOptions & { diff --git a/test/PhotoAlbum.test.tsx b/test/PhotoAlbum.test.tsx index 5f2aaa88..2fd8a611 100644 --- a/test/PhotoAlbum.test.tsx +++ b/test/PhotoAlbum.test.tsx @@ -244,6 +244,26 @@ describe("PhotoAlbum", () => { expect(onClick.mock.calls[0][1]).toBe(testPhotos[0]); }); + it("supports sizes attribute", () => { + whenAskedToRender(); + + whenAskedToRender( + + ); + + whenAskedToRender( + + ); + }); + it("supports global ResizeObserver", () => { const resizeObserverRef = global.ResizeObserver; try { diff --git a/test/__snapshots__/PhotoAlbum.test.tsx.snap b/test/__snapshots__/PhotoAlbum.test.tsx.snap index 92eba5a1..c9b3660c 100644 --- a/test/__snapshots__/PhotoAlbum.test.tsx.snap +++ b/test/__snapshots__/PhotoAlbum.test.tsx.snap @@ -16489,6 +16489,1224 @@ exports[`PhotoAlbum supports responsive parameters 2`] = ` `; +exports[`PhotoAlbum supports sizes attribute 1`] = ` +
+
+ + + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + + +
+
+ +
+
+ + + +
+
+`; + +exports[`PhotoAlbum supports sizes attribute 2`] = ` +
+
+ + + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + + +
+
+ +
+
+ + + +
+
+`; + +exports[`PhotoAlbum supports sizes attribute 3`] = ` +
+
+ + + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + + +
+
+ +
+
+ + + +
+
+`; + exports[`PhotoAlbum supports srcset and sizes 1`] = `