New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

react-photo-album

Package Overview
Dependencies
Maintainers
0
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-photo-album - npm Package Compare versions

Comparing version 2.4.1 to 3.0.0-rc.1

dist/client/aggregate.d.ts

239

dist/index.d.ts

@@ -1,228 +0,11 @@

import * as React from 'react';
type LayoutType = "columns" | "rows" | "masonry";
type PhotoAlbumProps<T extends Photo = Photo> = {
/** An array of photos to display in the photo album. */
photos: Array<T>;
/** Photo album layout type. */
layout: LayoutType;
/** A number of columns in the `columns` or `masonry` layout. */
columns?: ResponsiveParameter;
/** Spacing between images. */
spacing?: ResponsiveParameter;
/** Padding around each image in the photo album. */
padding?: ResponsiveParameter;
/** Target row height in the 'rows' layout. */
targetRowHeight?: ResponsiveParameter;
/** Additional row constraints in the `rows` layout. */
rowConstraints?: ResponsiveParameter<RowConstraints>;
/** Photo album container width at various viewport sizes. */
sizes?: ResponsiveSizes;
/** Photo click callback function. */
onClick?: ClickHandler<T>;
/** Responsive breakpoints. */
breakpoints?: number[];
/** Default container width in SSR. */
defaultContainerWidth?: number;
/** Additional HTML attributes to be passed to the rendered elements. */
componentsProps?: ComponentsPropsParameter;
/** Custom photo rendering function. */
renderPhoto?: RenderPhoto<T>;
/** Custom container rendering function. */
renderContainer?: RenderContainer;
/** Custom row container rendering function. */
renderRowContainer?: RenderRowContainer<T>;
/** Custom column container rendering function. */
renderColumnContainer?: RenderColumnContainer<T>;
};
interface Image {
/** Image source. */
src: string;
/** Image width in pixels. */
width: number;
/** Image height in pixels. */
height: number;
}
interface Photo extends Image {
/** Optional `key` attribute. */
key?: string;
/** Optional image `alt` attribute. */
alt?: string;
/** Optional image `title` attribute. */
title?: string;
/** @deprecated - use `srcSet` instead */
images?: Image[];
/** Optional array of alternative images to be included in the `srcset` attribute. */
srcSet?: Image[];
}
type RenderPhotoProps<T extends Photo = Photo> = {
/** photo object */
photo: T;
/** computed photo layout */
layout: PhotoLayout;
/** photo album layout options */
layoutOptions: LayoutOptions<T>;
/** pre-populated 'img' element attributes */
imageProps: NonOptional<ImageElementAttributes, "src" | "alt" | "style">;
/** A callback to render the default photo implementation. If `wrapped` is `true`, the image is styled with `width`
* and `height` set to 100%. Use this option when rendering image wrapper styled with wrapperStyle. */
renderDefaultPhoto: RenderFunction<{
wrapped?: boolean;
} | void>;
/** CSS styles to properly size image wrapper (i.e. <div> wrapper) */
wrapperStyle: React.CSSProperties;
};
type RenderPhoto<T extends Photo = Photo> = RenderFunction<RenderPhotoProps<T>>;
type ClickHandlerProps<T extends Photo = Photo> = {
event: React.MouseEvent;
photo: T;
index: number;
};
type ClickHandler<T extends Photo = Photo> = (props: ClickHandlerProps<T>) => void;
type ResponsiveParameterProvider<T = number> = (containerWidth: number) => T;
type ResponsiveParameter<T = number> = T | ResponsiveParameterProvider<T>;
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;
}[];
};
type PhotoLayout = {
/** rendered photo width */
width: number;
/** rendered photo height */
height: number;
/** photo index in the original `photos` array */
index: number;
/** photo index in a given row/column */
photoIndex: number;
/** number of photos in a given row/column */
photosCount: number;
};
type GenericLayoutOptions<T extends Photo = Photo> = {
/** layout spacing (gaps between photos) */
spacing: number;
/** padding around each photo */
padding: number;
/** current photo album container width */
containerWidth: number;
/** photo click handler */
onClick?: ClickHandler<T>;
/** photo album size at various viewport sizes */
sizes?: ResponsiveSizes;
};
type RowsLayoutOptions<T extends Photo = Photo> = GenericLayoutOptions<T> & {
/** layout type */
layout: Extract<LayoutType, "rows">;
/** target row height in 'rows' layout */
targetRowHeight: number;
/** Additional row constraints */
rowConstraints?: RowConstraints;
};
type ColumnsLayoutOptions<T extends Photo = Photo> = GenericLayoutOptions<T> & {
/** layout type */
layout: Extract<LayoutType, "columns" | "masonry">;
/** number of columns in 'columns' or 'masonry' layout */
columns: number;
};
type LayoutOptions<T extends Photo = Photo> = ColumnsLayoutOptions<T> | RowsLayoutOptions<T>;
type RowConstraints = {
/** minimum number of photos per row in 'rows' layout */
minPhotos?: number;
/** maximum number of photos per row in 'rows' layout */
maxPhotos?: number;
/** maximum row height when there is not enough photos to fill more than one row */
singleRowMaxHeight?: number;
};
type ComponentsProps = {
/** Additional HTML attributes to be passed to the outer container `div` element */
containerProps?: DivElementAttributes;
/** Additional HTML attributes to be passed to the row container `div` element */
rowContainerProps?: DivElementAttributes;
/** Additional HTML attributes to be passed to the column container `div` element */
columnContainerProps?: DivElementAttributes;
/** Additional HTML attributes to be passed to the photo `img` element */
imageProps?: ImageElementAttributes;
};
type ComponentsPropsParameter = ComponentsProps | ((containerWidth?: number) => ComponentsProps);
type RenderContainerProps = React.PropsWithChildren<{
/** layout type */
layout: LayoutType;
/** pre-populated default container attributes */
containerProps: DivElementAttributes;
/** container ref callback */
containerRef: React.RefCallback<HTMLDivElement>;
}>;
type RenderContainer = RenderFunction<RenderContainerProps>;
type RenderRowContainerProps<T extends Photo = Photo> = React.PropsWithChildren<{
/** layout options */
layoutOptions: RowsLayoutOptions<T>;
/** row number */
rowIndex: number;
/** total number of rows */
rowsCount: number;
/** pre-populated default row container attributes */
rowContainerProps: DivElementAttributes;
}>;
type RenderRowContainer<T extends Photo = Photo> = RenderFunction<RenderRowContainerProps<T>>;
type RenderColumnContainerProps<T extends Photo = Photo> = React.PropsWithChildren<{
layoutOptions: ColumnsLayoutOptions<T>;
/** column number */
columnIndex: number;
/** total number of columns */
columnsCount: number;
/** sum of spacings and paddings in each column */
columnsGaps?: number[];
/** width adjustment ratios of each column */
columnsRatios?: number[];
/** pre-populated default column container attributes */
columnContainerProps: DivElementAttributes;
}>;
type RenderColumnContainer<T extends Photo = Photo> = RenderFunction<RenderColumnContainerProps<T>>;
type RenderFunction<T = void> = (props: T) => React.ReactNode;
type DivElementAttributes = React.HTMLAttributes<HTMLDivElement>;
type ImageElementAttributes = React.ImgHTMLAttributes<HTMLImageElement>;
type Optional<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
type NonOptional<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;
declare function PhotoAlbum<T extends Photo>(props: PhotoAlbumProps<T>): React.JSX.Element | null;
type RowsLayoutModel<T extends Photo = Photo> = {
photo: T;
layout: PhotoLayout;
}[][] | undefined;
declare function computeRowsLayout<T extends Photo = Photo>({ photos, layoutOptions, }: {
photos: T[];
layoutOptions: RowsLayoutOptions<T>;
}): RowsLayoutModel<T>;
type ComputeColumnsLayoutProps<T extends Photo = Photo> = {
photos: T[];
layoutOptions: ColumnsLayoutOptions<T>;
};
type ColumnsLayoutModel<T extends Photo = Photo> = {
columnsModel: {
photo: T;
layout: PhotoLayout;
}[][];
columnsRatios: number[];
columnsGaps: number[];
} | undefined;
declare function computeColumnsLayout<T extends Photo = Photo>({ photos, layoutOptions, }: ComputeColumnsLayoutProps<T>): ColumnsLayoutModel<T>;
type ComputeMasonryLayoutProps<T extends Photo = Photo> = {
photos: T[];
layoutOptions: ColumnsLayoutOptions<T>;
};
type MasonryColumnsModel<T extends Photo = Photo> = {
photo: T;
layout: PhotoLayout;
}[][] | undefined;
declare function computeMasonryLayout<T extends Photo = Photo>(props: ComputeMasonryLayoutProps<T>): MasonryColumnsModel<T>;
export { type ClickHandler, type ClickHandlerProps, type ColumnsLayoutOptions, type ComponentsProps, type ComponentsPropsParameter, type DivElementAttributes, type GenericLayoutOptions, type Image, type ImageElementAttributes, type LayoutOptions, type LayoutType, type NonOptional, type Optional, type Photo, PhotoAlbum, type PhotoAlbumProps, type PhotoLayout, type RenderColumnContainer, type RenderColumnContainerProps, type RenderContainer, type RenderContainerProps, type RenderFunction, type RenderPhoto, type RenderPhotoProps, type RenderRowContainer, type RenderRowContainerProps, type ResponsiveParameter, type ResponsiveParameterProvider, type ResponsiveSizes, type RowConstraints, type RowsLayoutOptions, PhotoAlbum as default, computeColumnsLayout as unstable_computeColumnsLayout, computeMasonryLayout as unstable_computeMasonryLayout, computeRowsLayout as unstable_computeRowsLayout };
export { ClickHandlerProps, ColumnsPhotoAlbumProps, CommonPhotoAlbumProps, ComponentsProps, ContextAware, Image, LayoutModel, LayoutVariables, MasonryPhotoAlbumProps, NonOptional, Photo, Render, RenderButtonContext, RenderButtonProps, RenderContainerProps, RenderFunction, RenderImageContext, RenderImageProps, RenderLinkContext, RenderLinkProps, RenderPhotoContext, RenderPhotoProps, RenderTrackProps, RenderWrapperContext, RenderWrapperProps, ResponsiveParameter, ResponsiveSizes, RowConstraints, RowsPhotoAlbumProps } from './types.js';
export { default } from './client/aggregate.js';
export { default as RowsPhotoAlbum } from './client/rows.js';
export { default as ColumnsPhotoAlbum } from './client/columns.js';
export { default as MasonryPhotoAlbum } from './client/masonry.js';
export { default as UnstableStaticPhotoAlbum } from './core/static.js';
export { default as unstable_computeRowsLayout } from './layouts/rows.js';
export { default as unstable_computeColumnsLayout } from './layouts/columns.js';
export { default as unstable_computeMasonryLayout } from './layouts/masonry.js';
import 'react';
import 'react/jsx-runtime';

@@ -1,888 +0,18 @@

"use client";
"use strict";
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const React = require("react");
function _interopNamespaceDefault(e) {
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
if (e) {
for (const k in e) {
if (k !== "default") {
const d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: () => e[k]
});
}
}
}
n.default = e;
return Object.freeze(n);
}
const React__namespace = /* @__PURE__ */ _interopNamespaceDefault(React);
function ratio({ width, height }) {
return width / height;
}
function round(value, decimals = 0) {
const factor = 10 ** decimals;
return Math.round((value + Number.EPSILON) * factor) / factor;
}
function rankingFunctionComparator(rank) {
return (a, b) => rank(b) - rank(a);
}
class MinHeap {
constructor(comparator) {
this.comparator = comparator;
this.heap = [];
this.n = 0;
}
greater(i, j) {
return this.comparator(this.heap[i], this.heap[j]) < 0;
}
swap(i, j) {
const temp = this.heap[i];
this.heap[i] = this.heap[j];
this.heap[j] = temp;
}
swim(i) {
let k = i;
let k2 = Math.floor(k / 2);
while (k > 1 && this.greater(k2, k)) {
this.swap(k2, k);
k = k2;
k2 = Math.floor(k / 2);
}
}
sink(i) {
let k = i;
let k2 = k * 2;
while (k2 <= this.n) {
if (k2 < this.n && this.greater(k2, k2 + 1)) k2 += 1;
if (!this.greater(k, k2)) break;
this.swap(k, k2);
k = k2;
k2 = k * 2;
}
}
push(element) {
this.n += 1;
this.heap[this.n] = element;
this.swim(this.n);
}
pop() {
if (this.n === 0) return void 0;
this.swap(1, this.n);
this.n -= 1;
const max = this.heap.pop();
this.sink(1);
return max;
}
size() {
return this.n;
}
}
function buildPrecedentsMap(graph, startNode, endNode) {
const precedentsMap = /* @__PURE__ */ new Map();
const visited = /* @__PURE__ */ new Set();
const storedShortestPaths = /* @__PURE__ */ new Map();
storedShortestPaths.set(startNode, 0);
const queue = new MinHeap(rankingFunctionComparator((el) => el.weight));
queue.push({ id: startNode, weight: 0 });
while (queue.size() > 0) {
const { id, weight } = queue.pop();
if (!visited.has(id)) {
const neighboringNodes = graph(id);
visited.add(id);
neighboringNodes.forEach((neighborWeight, neighbor) => {
const newWeight = weight + neighborWeight;
const currentId = precedentsMap.get(neighbor);
const currentWeight = storedShortestPaths.get(neighbor);
if (currentWeight === void 0 || currentWeight > newWeight && (currentWeight / newWeight > 1.005 || currentId !== void 0 && currentId < id)) {
storedShortestPaths.set(neighbor, newWeight);
queue.push({ id: neighbor, weight: newWeight });
precedentsMap.set(neighbor, id);
}
});
}
}
return storedShortestPaths.has(endNode) ? precedentsMap : void 0;
}
function getPathFromPrecedentsMap(precedentsMap, endNode) {
const nodes = [];
for (let node = endNode; node !== void 0; node = precedentsMap.get(node)) {
nodes.push(node);
}
return nodes.reverse();
}
function findShortestPath(graph, startNode, endNode) {
const precedentsMap = buildPrecedentsMap(graph, startNode, endNode);
return precedentsMap ? getPathFromPrecedentsMap(precedentsMap, endNode) : void 0;
}
function findIdealNodeSearch({
photos,
targetRowHeight,
containerWidth
}) {
const minRatio = photos.reduce((acc, photo) => Math.min(ratio(photo), acc), Number.MAX_VALUE);
return round(containerWidth / targetRowHeight / minRatio) + 2;
}
function getCommonHeight(row, containerWidth, spacing, padding) {
const rowWidth = containerWidth - (row.length - 1) * spacing - 2 * padding * row.length;
const totalAspectRatio = row.reduce((acc, photo) => acc + ratio(photo), 0);
return rowWidth / totalAspectRatio;
}
function cost(photos, i, j, width, targetRowHeight, spacing, padding) {
const row = photos.slice(i, j);
const commonHeight = getCommonHeight(row, width, spacing, padding);
return commonHeight > 0 ? (commonHeight - targetRowHeight) ** 2 * row.length : void 0;
}
function makeGetRowNeighbors({
photos,
layoutOptions,
targetRowHeight,
limitNodeSearch,
rowConstraints
}) {
return (node) => {
var _a, _b;
const { containerWidth, spacing, padding } = layoutOptions;
const results = /* @__PURE__ */ new Map();
results.set(node, 0);
const startOffset = (_a = rowConstraints == null ? void 0 : rowConstraints.minPhotos) != null ? _a : 1;
const endOffset = Math.min(limitNodeSearch, (_b = rowConstraints == null ? void 0 : rowConstraints.maxPhotos) != null ? _b : Infinity);
for (let i = node + startOffset; i < photos.length + 1; i += 1) {
if (i - node > endOffset) break;
const currentCost = cost(photos, node, i, containerWidth, targetRowHeight, spacing, padding);
if (currentCost === void 0) break;
results.set(i, currentCost);
}
return results;
};
}
function computeRowsLayout({
photos,
layoutOptions
}) {
const { spacing, padding, containerWidth, targetRowHeight, rowConstraints } = layoutOptions;
const limitNodeSearch = findIdealNodeSearch({ photos, containerWidth, targetRowHeight });
const getNeighbors = makeGetRowNeighbors({
photos,
layoutOptions,
targetRowHeight,
limitNodeSearch,
rowConstraints
});
const path = findShortestPath(getNeighbors, 0, photos.length);
if (path === void 0) return void 0;
const layout = [];
for (let i = 1; i < path.length; i += 1) {
const row = photos.map((photo, index) => ({ photo, index })).slice(path[i - 1], path[i]);
const height = getCommonHeight(
row.map(({ photo }) => photo),
containerWidth,
spacing,
padding
);
layout.push(
row.map(({ photo, index }, photoIndex) => ({
photo,
layout: {
height,
width: height * ratio(photo),
index,
photoIndex,
photosCount: row.length
}
}))
);
}
return layout;
}
function clsx(...classes) {
return [...classes].filter((cls) => Boolean(cls)).join(" ");
}
function calcWidth(base, photoLayout, layoutOptions) {
const { width, photosCount } = photoLayout;
const { layout, spacing, padding, containerWidth } = layoutOptions;
const count = layout === "rows" ? photosCount : layoutOptions.columns;
const gaps = spacing * (count - 1) + 2 * padding * count;
return `calc((${base} - ${gaps}px) / ${round((containerWidth - gaps) / width, 5)})`;
}
function cssPhotoWidth(layout, layoutOptions) {
return layoutOptions.layout !== "rows" ? `calc(100% - ${2 * layoutOptions.padding}px)` : calcWidth("100%", layout, layoutOptions);
}
function calculateSizesValue(size, layout, layoutOptions) {
var _a, _b;
return calcWidth((_b = (_a = size.match(/^\s*calc\((.*)\)\s*$/)) == null ? void 0 : _a[1]) != null ? _b : size, layout, layoutOptions);
}
function srcSetAndSizes(photo, layout, layoutOptions) {
var _a;
let srcSet;
let sizes;
const images = photo.srcSet || photo.images;
if (images && images.length > 0) {
srcSet = images.concat(
!images.find(({ width }) => width === photo.width) ? [{ src: photo.src, width: photo.width, height: photo.height }] : []
).sort((first, second) => first.width - second.width).map((image) => `${image.src} ${image.width}w`).join(", ");
}
if ((_a = layoutOptions.sizes) == null ? void 0 : _a.size) {
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.containerWidth * 100)}vw`;
}
return { srcSet, sizes };
}
function PhotoRenderer(props) {
var _a, _b;
const { photo, layout, layoutOptions, imageProps: { style, className, ...restImageProps } = {}, renderPhoto } = props;
const { onClick } = layoutOptions;
const imageStyle = {
display: "block",
boxSizing: "content-box",
width: cssPhotoWidth(layout, layoutOptions),
height: "auto",
aspectRatio: `${photo.width} / ${photo.height}`,
...layoutOptions.padding ? { padding: `${layoutOptions.padding}px` } : null,
...(layoutOptions.layout === "columns" || layoutOptions.layout === "masonry") && layout.photoIndex < layout.photosCount - 1 ? { marginBottom: `${layoutOptions.spacing}px` } : null,
...onClick ? { cursor: "pointer" } : null,
...style
};
const handleClick = onClick ? (event) => {
onClick({ event, photo, index: layout.index });
} : void 0;
const imageProps = {
src: photo.src,
alt: (_a = photo.alt) != null ? _a : "",
title: photo.title,
onClick: handleClick,
style: imageStyle,
className: clsx("react-photo-album--photo", className),
loading: "lazy",
decoding: "async",
...srcSetAndSizes(photo, layout, layoutOptions),
...restImageProps
};
const renderDefaultPhoto = (options) => {
const { src, alt, srcSet, sizes, style: unwrappedStyle, ...rest } = imageProps;
return React__namespace.createElement(
"img",
{
alt,
...srcSet ? { srcSet, sizes } : null,
src,
style: (options == null ? void 0 : options.wrapped) ? { display: "block", width: "100%", height: "100%" } : unwrappedStyle,
...rest
}
);
};
const wrapperStyle = (({ display, boxSizing, width, aspectRatio, padding, marginBottom, cursor }) => ({
display,
boxSizing,
width,
aspectRatio,
padding,
marginBottom,
cursor
}))(imageStyle);
return React__namespace.createElement(React__namespace.Fragment, null, (_b = renderPhoto == null ? void 0 : renderPhoto({
photo,
layout,
layoutOptions,
imageProps,
renderDefaultPhoto,
wrapperStyle
})) != null ? _b : renderDefaultPhoto());
}
function defaultRenderRowContainer({
rowContainerProps,
children
}) {
return React__namespace.createElement("div", { ...rowContainerProps }, children);
}
function RowContainerRenderer(props) {
const {
layoutOptions,
rowIndex,
rowsCount,
renderRowContainer,
rowContainerProps: { style, className, ...restRowContainerProps } = {},
children
} = props;
const rowContainerProps = {
className: clsx("react-photo-album--row", className),
style: {
display: "flex",
flexDirection: "row",
flexWrap: "nowrap",
alignItems: "flex-start",
justifyContent: "space-between",
...rowIndex < rowsCount - 1 ? { marginBottom: `${layoutOptions.spacing}px` } : null,
...style
},
...restRowContainerProps
};
return React__namespace.createElement(React__namespace.Fragment, null, (renderRowContainer != null ? renderRowContainer : defaultRenderRowContainer)({
layoutOptions,
rowIndex,
rowsCount,
rowContainerProps,
children
}));
}
function RowsLayout(props) {
const {
photos,
layoutOptions,
renderPhoto,
renderRowContainer,
componentsProps: { imageProps, rowContainerProps }
} = props;
const rowsLayout = computeRowsLayout({ photos, layoutOptions });
if (!rowsLayout) return null;
return React__namespace.createElement(React__namespace.Fragment, null, rowsLayout.map((row, rowIndex) => React__namespace.createElement(
RowContainerRenderer,
{
key: `row-${rowIndex}`,
layoutOptions,
rowIndex,
rowsCount: rowsLayout.length,
renderRowContainer,
rowContainerProps
},
row.map(({ photo, layout }) => React__namespace.createElement(
PhotoRenderer,
{
key: photo.key || photo.src,
photo,
layout,
layoutOptions,
renderPhoto,
imageProps
}
))
)));
}
function computeShortestPath(graph, pathLength, startNode, endNode) {
const matrix = /* @__PURE__ */ new Map();
const queue = /* @__PURE__ */ new Set();
queue.add(startNode);
for (let length = 0; length < pathLength; length += 1) {
const currentQueue = [...queue.keys()];
queue.clear();
currentQueue.forEach((node) => {
const accumulatedWeight = length > 0 ? matrix.get(node)[length].weight : 0;
graph(node).forEach(({ neighbor, weight }) => {
let paths = matrix.get(neighbor);
if (!paths) {
paths = [];
matrix.set(neighbor, paths);
}
const newWeight = accumulatedWeight + weight;
const nextPath = paths[length + 1];
if (!nextPath || nextPath.weight > newWeight && (nextPath.weight / newWeight > 1.0001 || node < nextPath.node)) {
paths[length + 1] = { node, weight: newWeight };
}
if (length < pathLength - 1 && neighbor !== endNode) {
queue.add(neighbor);
}
});
});
}
return matrix;
}
function reconstructShortestPath(matrix, pathLength, endNode) {
const path = [endNode];
for (let node = endNode, length = pathLength; length > 0; length -= 1) {
node = matrix.get(node)[length].node;
path.push(node);
}
return path.reverse();
}
function findShortestPathLengthN(graph, pathLength, startNode, endNode) {
return reconstructShortestPath(computeShortestPath(graph, pathLength, startNode, endNode), pathLength, endNode);
}
function makeGetColumnNeighbors({
photos,
spacing,
padding,
targetColumnWidth,
targetColumnHeight
}) {
return (node) => {
const results = [];
const cutOffHeight = targetColumnHeight * 1.5;
let height = targetColumnWidth / ratio(photos[node]) + 2 * padding;
for (let i = node + 1; i < photos.length + 1; i += 1) {
results.push({ neighbor: i, weight: (targetColumnHeight - height) ** 2 });
if (height > cutOffHeight || i === photos.length) {
break;
}
height += targetColumnWidth / ratio(photos[i]) + spacing + 2 * padding;
}
return results;
};
}
function buildColumnsModel({
path,
photos,
containerWidth,
columnsGaps,
columnsRatios,
spacing,
padding
}) {
const columnsModel = [];
const totalRatio = columnsRatios.reduce((total, columnRatio) => total + columnRatio, 0);
for (let i = 0; i < path.length - 1; i += 1) {
const column = photos.map((photo, index) => ({ photo, index })).slice(path[i], path[i + 1]);
const totalAdjustedGaps = columnsRatios.reduce(
(total, columnRatio, index) => total + (columnsGaps[i] - columnsGaps[index]) * columnRatio,
0
);
const columnWidth = (containerWidth - (path.length - 2) * spacing - 2 * (path.length - 1) * padding - totalAdjustedGaps) * columnsRatios[i] / totalRatio;
columnsModel.push(
column.map(({ photo, index }, photoIndex) => ({
photo,
layout: {
width: columnWidth,
height: columnWidth / ratio(photo),
index,
photoIndex,
photosCount: column.length
}
}))
);
}
return columnsModel;
}
function computeColumnsModel({
photos,
layoutOptions,
targetColumnWidth
}) {
const { columns, spacing, padding, containerWidth } = layoutOptions;
const columnsGaps = [];
const columnsRatios = [];
if (photos.length <= columns) {
const averageRatio = photos.length > 0 ? photos.reduce((acc, photo) => acc + ratio(photo), 0) / photos.length : 1;
for (let i = 0; i < columns; i += 1) {
columnsGaps[i] = 2 * padding;
columnsRatios[i] = i < photos.length ? ratio(photos[i]) : averageRatio;
}
const columnsModel2 = buildColumnsModel({
path: Array.from({ length: columns + 1 }).map((_, index) => Math.min(index, photos.length)),
photos,
columnsRatios,
columnsGaps,
containerWidth,
spacing,
padding
});
return { columnsGaps, columnsRatios, columnsModel: columnsModel2 };
}
const targetColumnHeight = (photos.reduce((acc, photo) => acc + targetColumnWidth / ratio(photo), 0) + spacing * (photos.length - columns) + 2 * padding * photos.length) / columns;
const getNeighbors = makeGetColumnNeighbors({
photos,
targetColumnWidth,
targetColumnHeight,
spacing,
padding
});
const path = findShortestPathLengthN(getNeighbors, columns, 0, photos.length);
for (let i = 0; i < path.length - 1; i += 1) {
const column = photos.slice(path[i], path[i + 1]);
columnsGaps[i] = spacing * (column.length - 1) + 2 * padding * column.length;
columnsRatios[i] = 1 / column.reduce((acc, photo) => acc + 1 / ratio(photo), 0);
}
const columnsModel = buildColumnsModel({
path,
photos,
columnsRatios,
columnsGaps,
containerWidth,
spacing,
padding
});
return { columnsGaps, columnsRatios, columnsModel };
}
function computeLayout(props) {
const { photos, layoutOptions } = props;
const { columns, spacing, padding, containerWidth } = layoutOptions;
const targetColumnWidth = (containerWidth - spacing * (columns - 1) - 2 * padding * columns) / columns;
const { columnsGaps, columnsRatios, columnsModel } = computeColumnsModel({
photos,
layoutOptions,
targetColumnWidth
});
if (columnsModel.findIndex(
(columnModel) => columnModel.findIndex(({ layout: { width, height } }) => width < 0 || height < 0) >= 0
) >= 0) {
if (columns > 1) {
return computeLayout({ photos, layoutOptions: { ...layoutOptions, columns: columns - 1 } });
}
return void 0;
}
return { columnsModel, columnsGaps, columnsRatios };
}
function computeColumnsLayout({
photos,
layoutOptions
}) {
return computeLayout({ photos, layoutOptions });
}
function defaultRenderColumnContainer({
columnContainerProps,
children
}) {
return React__namespace.createElement("div", { ...columnContainerProps }, children);
}
function cssColumnWidth(props) {
const { layoutOptions, columnIndex, columnsCount, columnsGaps, columnsRatios } = props;
const { layout, spacing, padding } = layoutOptions;
if (layout === "masonry" || !columnsGaps || !columnsRatios) {
return `calc((100% - ${spacing * (columnsCount - 1)}px) / ${columnsCount})`;
}
const totalRatio = columnsRatios.reduce((acc, ratio2) => acc + ratio2, 0);
const totalAdjustedGaps = columnsRatios.reduce(
(acc, ratio2, index) => acc + (columnsGaps[columnIndex] - columnsGaps[index]) * ratio2,
0
);
return `calc((100% - ${round(
(columnsCount - 1) * spacing + 2 * columnsCount * padding + totalAdjustedGaps,
3
)}px) * ${round(columnsRatios[columnIndex] / totalRatio, 5)} + ${2 * padding}px)`;
}
function ColumnContainerRenderer(props) {
const {
layoutOptions,
renderColumnContainer,
children,
columnContainerProps: { style, className, ...restColumnContainerProps } = {},
...rest
} = props;
const columnContainerProps = {
className: clsx("react-photo-album--column", className),
style: {
display: "flex",
flexDirection: "column",
flexWrap: "nowrap",
alignItems: "flex-start",
width: cssColumnWidth(props),
justifyContent: layoutOptions.layout === "columns" ? "space-between" : "flex-start",
...style
},
...restColumnContainerProps
};
return React__namespace.createElement(React__namespace.Fragment, null, (renderColumnContainer != null ? renderColumnContainer : defaultRenderColumnContainer)({
layoutOptions,
columnContainerProps,
children,
...rest
}));
}
function ColumnsLayout(props) {
const {
photos,
layoutOptions,
renderPhoto,
renderColumnContainer,
componentsProps: { imageProps, columnContainerProps }
} = props;
const columnsLayout = computeColumnsLayout({ photos, layoutOptions });
if (!columnsLayout) return null;
const { columnsModel, columnsRatios, columnsGaps } = columnsLayout;
return React__namespace.createElement(React__namespace.Fragment, null, columnsModel.map((column, columnIndex) => React__namespace.createElement(
ColumnContainerRenderer,
{
key: `column-${columnIndex}`,
layoutOptions,
columnIndex,
columnsCount: columnsModel.length,
columnsGaps,
columnsRatios,
renderColumnContainer,
columnContainerProps
},
column.map(({ photo, layout }) => React__namespace.createElement(
PhotoRenderer,
{
key: photo.key || photo.src,
photo,
layout,
layoutOptions,
renderPhoto,
imageProps
}
))
)));
}
function computeMasonryLayout(props) {
const { photos, layoutOptions } = props;
const { columns, spacing, padding, containerWidth } = layoutOptions;
const columnWidth = (containerWidth - spacing * (columns - 1) - 2 * padding * columns) / columns;
if (columnWidth <= 0) {
return columns > 1 ? computeMasonryLayout({
...props,
layoutOptions: { ...layoutOptions, columns: columns - 1 }
}) : void 0;
}
const columnsCurrentTopPositions = [];
for (let i = 0; i < columns; i += 1) {
columnsCurrentTopPositions[i] = 0;
}
const columnsModel = photos.reduce(
(model, photo, index) => {
const shortestColumn = columnsCurrentTopPositions.reduce(
(currentShortestColumn, item, i) => item < columnsCurrentTopPositions[currentShortestColumn] - 1 ? i : currentShortestColumn,
0
);
columnsCurrentTopPositions[shortestColumn] = columnsCurrentTopPositions[shortestColumn] + columnWidth / ratio(photo) + spacing + 2 * padding;
model[shortestColumn].push({ photo, index });
return model;
},
Array.from({ length: columns }).map(() => [])
);
return columnsModel.map(
(column) => column.map(({ photo, index }, photoIndex) => ({
photo,
layout: {
width: columnWidth,
height: columnWidth / ratio(photo),
index,
photoIndex,
photosCount: column.length
}
}))
);
}
function MasonryLayout(props) {
const {
photos,
layoutOptions,
renderPhoto,
renderColumnContainer,
componentsProps: { imageProps, columnContainerProps }
} = props;
const masonryLayout = computeMasonryLayout({ photos, layoutOptions });
if (!masonryLayout) return null;
return React__namespace.createElement(React__namespace.Fragment, null, masonryLayout.map((column, columnIndex) => React__namespace.createElement(
ColumnContainerRenderer,
{
key: `masonry-column-${columnIndex}`,
layoutOptions,
columnsCount: masonryLayout.length,
columnIndex,
renderColumnContainer,
columnContainerProps
},
column.map(({ photo, layout }) => React__namespace.createElement(
PhotoRenderer,
{
key: photo.key || photo.src,
photo,
layout,
layoutOptions,
renderPhoto,
imageProps
}
))
)));
}
function defaultRenderContainer({ containerProps, children, containerRef }) {
return React__namespace.createElement("div", { ref: containerRef, ...containerProps }, children);
}
function ContainerRenderer(props) {
const {
layout,
renderContainer,
children,
containerRef,
containerProps: { style, className, ...restContainerProps } = {}
} = props;
const containerProps = {
className: clsx("react-photo-album", `react-photo-album--${layout}`, className),
style: {
display: "flex",
flexWrap: "nowrap",
justifyContent: "space-between",
flexDirection: layout === "rows" ? "column" : "row",
...style
},
...restContainerProps
};
return React__namespace.createElement(React__namespace.Fragment, null, (renderContainer != null ? renderContainer : defaultRenderContainer)({
containerProps,
containerRef,
layout,
children
}));
}
function useArray(array) {
const ref = React__namespace.useRef(array);
if (!array || !ref.current || array.join() !== ref.current.join()) {
ref.current = array;
}
return ref.current;
}
function containerWidthReducer(state, { newContainerWidth, newScrollbarWidth }) {
const { containerWidth, scrollbarWidth } = state;
if (containerWidth !== void 0 && scrollbarWidth !== void 0 && newContainerWidth !== void 0 && newScrollbarWidth !== void 0 && newContainerWidth > containerWidth && newContainerWidth - containerWidth <= 20 && newScrollbarWidth < scrollbarWidth) {
return { containerWidth, scrollbarWidth: newScrollbarWidth };
}
return containerWidth !== newContainerWidth || scrollbarWidth !== newScrollbarWidth ? { containerWidth: newContainerWidth, scrollbarWidth: newScrollbarWidth } : state;
}
function resolveContainerWidth(el, breakpoints2) {
let width = el == null ? void 0 : el.clientWidth;
if (width !== void 0 && breakpoints2 && breakpoints2.length > 0) {
const sorted = [...breakpoints2.filter((x) => x > 0)].sort((a, b) => b - a);
sorted.push(Math.floor(sorted[sorted.length - 1] / 2));
const threshold = width;
width = sorted.find((breakpoint, index) => breakpoint <= threshold || index === sorted.length - 1);
}
return width;
}
function useContainerWidth(breakpoints2, defaultContainerWidth) {
const [{ containerWidth }, dispatch] = React__namespace.useReducer(containerWidthReducer, {
containerWidth: defaultContainerWidth
});
const ref = React__namespace.useRef(null);
const observerRef = React__namespace.useRef();
const containerRef = React__namespace.useCallback(
(node) => {
var _a;
(_a = observerRef.current) == null ? void 0 : _a.disconnect();
observerRef.current = void 0;
ref.current = node;
const updateWidth = () => dispatch({
newContainerWidth: resolveContainerWidth(ref.current, breakpoints2),
newScrollbarWidth: window.innerWidth - document.documentElement.clientWidth
});
updateWidth();
if (node && typeof ResizeObserver !== "undefined") {
observerRef.current = new ResizeObserver(updateWidth);
observerRef.current.observe(node);
}
},
[breakpoints2]
);
return { containerRef, containerWidth };
}
const breakpoints = Object.freeze([1200, 600, 300, 0]);
function unwrap(value, arg) {
return typeof value === "function" ? value(arg) : value;
}
function unwrapParameter(value, containerWidth) {
return typeof value !== "undefined" ? unwrap(value, containerWidth) : void 0;
}
function selectResponsiveValue(values, containerWidth) {
const index = breakpoints.findIndex((breakpoint) => breakpoint <= containerWidth);
return unwrap(values[index >= 0 ? index : 0], containerWidth);
}
function resolveResponsiveParameter(parameter, containerWidth, values, minValue = 0) {
const value = unwrapParameter(parameter, containerWidth);
return Math.round(Math.max(value === void 0 ? selectResponsiveValue(values, containerWidth) : value, minValue));
}
function resolveLayoutOptions({
layout,
onClick,
containerWidth,
targetRowHeight,
rowConstraints,
columns,
spacing,
padding,
sizes
}) {
return {
layout,
onClick,
containerWidth,
columns: resolveResponsiveParameter(columns, containerWidth, [5, 4, 3, 2], 1),
spacing: resolveResponsiveParameter(spacing, containerWidth, [20, 15, 10, 5]),
padding: resolveResponsiveParameter(padding, containerWidth, [0, 0, 0, 0, 0]),
targetRowHeight: resolveResponsiveParameter(targetRowHeight, containerWidth, [
(w) => w / 5,
(w) => w / 4,
(w) => w / 3,
(w) => w / 2
]),
rowConstraints: unwrapParameter(rowConstraints, containerWidth),
sizes
};
}
function resolveComponentsProps(props, containerWidth, layoutOptions) {
const { photos, componentsProps: componentsPropsProp } = props;
const componentsProps = unwrap(componentsPropsProp, containerWidth) || {};
if (layoutOptions) {
const { layout, spacing, padding, rowConstraints } = layoutOptions;
if (layout === "rows") {
const { singleRowMaxHeight } = rowConstraints || {};
if (singleRowMaxHeight) {
const maxWidth = Math.floor(
photos.reduce(
(acc, { width, height }) => acc + width / height * singleRowMaxHeight - 2 * padding,
padding * photos.length * 2 + spacing * (photos.length - 1)
)
);
if (maxWidth > 0) {
componentsProps.containerProps = componentsProps.containerProps || {};
componentsProps.containerProps.style = { maxWidth, ...componentsProps.containerProps.style };
}
}
}
}
return componentsProps;
}
function renderLayout(props, componentsProps, layoutOptions) {
const { photos, layout, renderPhoto, renderRowContainer, renderColumnContainer } = props;
const commonLayoutProps = { photos, renderPhoto, componentsProps };
if (layout === "rows") {
return React__namespace.createElement(
RowsLayout,
{
layoutOptions,
renderRowContainer,
...commonLayoutProps
}
);
}
if (layout === "columns") {
return React__namespace.createElement(
ColumnsLayout,
{
layoutOptions,
renderColumnContainer,
...commonLayoutProps
}
);
}
return React__namespace.createElement(
MasonryLayout,
{
layoutOptions,
renderColumnContainer,
...commonLayoutProps
}
);
}
function PhotoAlbum(props) {
const { photos, layout, renderContainer, defaultContainerWidth, breakpoints: breakpoints2 } = props;
const { containerRef, containerWidth } = useContainerWidth(useArray(breakpoints2), defaultContainerWidth);
if (!layout || !["rows", "columns", "masonry"].includes(layout) || !Array.isArray(photos)) return null;
const layoutOptions = containerWidth ? resolveLayoutOptions({ containerWidth, ...props }) : void 0;
const componentsProps = resolveComponentsProps(props, containerWidth, layoutOptions);
return React__namespace.createElement(
ContainerRenderer,
{
layout,
containerRef,
renderContainer,
containerProps: componentsProps.containerProps
},
layoutOptions && renderLayout(props, componentsProps, layoutOptions)
);
}
exports.PhotoAlbum = PhotoAlbum;
exports.default = PhotoAlbum;
exports.unstable_computeColumnsLayout = computeColumnsLayout;
exports.unstable_computeMasonryLayout = computeMasonryLayout;
exports.unstable_computeRowsLayout = computeRowsLayout;
import { default as default2 } from "./client/aggregate.js";
import { default as default3 } from "./client/rows.js";
import { default as default4 } from "./client/columns.js";
import { default as default5 } from "./client/masonry.js";
import { default as default6 } from "./core/static.js";
import { default as default7 } from "./layouts/rows.js";
import { default as default8 } from "./layouts/columns.js";
import { default as default9 } from "./layouts/masonry.js";
export {
default4 as ColumnsPhotoAlbum,
default5 as MasonryPhotoAlbum,
default3 as RowsPhotoAlbum,
default6 as UnstableStaticPhotoAlbum,
default2 as default,
default8 as unstable_computeColumnsLayout,
default9 as unstable_computeMasonryLayout,
default7 as unstable_computeRowsLayout
};
{
"name": "react-photo-album",
"version": "2.4.1",
"version": "3.0.0-rc.1",
"description": "Responsive photo gallery component for React",
"author": "Igor Danchenko",
"license": "MIT",
"main": "dist/index.js",
"module": "dist/index.mjs",
"type": "module",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./styles.css": {
"types": "./dist/styles/styles.css.d.ts",
"default": "./dist/styles/styles.css"
},
"./rows.css": {
"types": "./dist/styles/rows.css.d.ts",
"default": "./dist/styles/rows.css"
},
"./columns.css": {
"types": "./dist/styles/columns.css.d.ts",
"default": "./dist/styles/columns.css"
},
"./masonry.css": {
"types": "./dist/styles/masonry.css.d.ts",
"default": "./dist/styles/masonry.css"
}

@@ -23,3 +35,5 @@ },

],
"sideEffects": false,
"sideEffects": [
"*.css"
],
"homepage": "https://react-photo-album.com",

@@ -34,3 +48,3 @@ "repository": {

"engines": {
"node": ">=12"
"node": ">=18"
},

@@ -42,4 +56,10 @@ "publishConfig": {

"peerDependencies": {
"react": ">=16.8.0"
"@types/react": ">=18",
"react": ">=18"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
},
"keywords": [

@@ -46,0 +66,0 @@ "react",

@@ -14,3 +14,3 @@ # React Photo Album

- **Built for React:** works with React 18, 17 and 16.8.0+
- **Built for React:** works with React 18+
- **SSR friendly:** produces server-side rendered markup that looks pixel

@@ -25,3 +25,3 @@ perfect on the client even before hydration

- **Performance:** it was built with performance in mind in order to support
large photo albums and silky smooth layout adjustments
large photo albums

@@ -60,14 +60,21 @@ ## Layouts

## Requirements
- React 18+
- Node 18+
- modern ESM-compatible bundler
## Minimal Setup Example
```tsx
import PhotoAlbum from "react-photo-album";
import { RowsPhotoAlbum } from "react-photo-album";
import "react-photo-album/rows.css";
const photos = [
{ src: "/images/image1.jpg", width: 800, height: 600 },
{ src: "/images/image2.jpg", width: 1600, height: 900 },
{ src: "/image1.jpg", width: 800, height: 600 },
{ src: "/image2.jpg", width: 1600, height: 900 },
];
export default function Gallery() {
return <PhotoAlbum layout="rows" photos={photos} />;
return <RowsPhotoAlbum photos={photos} />;
}

@@ -80,23 +87,21 @@ ```

Rows layout fills the rectangular container space by arranging photos into rows
that are similar in size, with the height of each row being as close to the
Rows layout fills the container space by arranging photos into rows that are
similar in height, with the height of each row being as close to the
`targetRowHeight` as possible. This layout uses an algorithm adapted from the
Knuth and Plass line breaking algorithm. To calculate the single best layout, it
uses Dijkstra's algorithm to find the shortest past in a graph where each photo
Knuth and Plass line-breaking algorithm. To calculate the optimal layout, it
uses Dijkstra's algorithm to find the shortest path in a graph where each photo
to break on represents a node, and each row represents an edge. The cost of each
edge is calculated as the squared deviation from the `targetRowHeight`. This
algorithm produces rows that are similar in height and photos that are not being
stretched or shrunken abnormally (as is what happens in a naive implementation).
It solves the issue of panoramas shrinking rows or having stragglers or
stretched images in the last row, instead creating a justified grid. The graph
is being built as the shortest path is being calculated to improve algorithm's
performance, so the entire adjacency list is not calculated ahead of time.
edge is calculated as a squared deviation from the `targetRowHeight`. This
algorithm produces rows that are similar in height and photos that are not
stretched or abnormally shrunk (as what happens in a naive implementation). It
solves the issue of panoramas shrinking rows or having stragglers or stretched
images in the last row.
### Columns Layout
Columns layout fills the rectangular container space by arranging photos into a
predefined number of columns, determined by the `columns` parameter. This layout
uses an algorithm very similar to the one described above, but instead of
Dijkstra's algorithm, it uses a dynamic programming algorithm to find the
shortest path of length N in a directed weighted graph.
Columns layout fills the container space by arranging photos into a predefined
number of columns, determined by the `columns` parameter. This layout uses an
algorithm very similar to the one described above, with the only difference
being that instead of Dijkstra's algorithm, it uses a dynamic programming
algorithm to find the shortest path of length N in a directed weighted graph.

@@ -106,4 +111,4 @@ ### Masonry Layout

Masonry layout arranges photos into columns of equal width by placing each photo
into the shortest column. This layout does not completely fill the rectangular
container space, but the columns end up being as close in height to each other
into the shortest column. This layout does not fill the container space flush to
its bottom edge, but the columns end up being as close in height to each other
as possible.

@@ -113,29 +118,32 @@

React Photo Album automatically generates `sizes` and `srcset` image attributes.
In the case of SSR, React Photo Album includes `sizes` and `srcset` image
attributes in the server-rendered markup, allowing browsers to pick images of
the most appropriate resolution depending on their viewport size. To enable
images with automatic resolution switching, simply provide smaller images in the
photo `srcSet` attribute.
React Photo Album can automatically produce `sizes` and `srcset` image
attributes. In the case of SSR, React Photo Album includes `sizes` and `srcset`
image attributes in the server-rendered markup, allowing browsers to pick images
of the most appropriate resolution depending on the end-user viewport size. To
utilize images with automatic resolution switching, provide images of different
resolutions in the photo `srcSet` attribute. To further improve app
responsiveness and bandwidth utilization, you can specify the `sizes` prop that
describes the width of the photo album in various viewports.
```tsx
import PhotoAlbum from "react-photo-album";
import { RowsPhotoAlbum } from "react-photo-album";
import "react-photo-album/rows.css";
const photos = [
{
src: "/images/image1_800x600.jpg",
src: "/image1_800x600.jpg",
width: 800,
height: 600,
srcSet: [
{ src: "/images/image1_400x300.jpg", width: 400, height: 300 },
{ src: "/images/image1_200x150.jpg", width: 200, height: 150 },
{ src: "/image1_400x300.jpg", width: 400, height: 300 },
{ src: "/image1_200x150.jpg", width: 200, height: 150 },
],
},
{
src: "/images/image2_1600x900.jpg",
src: "/image2_1600x900.jpg",
width: 1600,
height: 900,
srcSet: [
{ src: "/images/image2_800x450.jpg", width: 800, height: 450 },
{ src: "/images/image2_400x225.jpg", width: 400, height: 225 },
{ src: "/image2_800x450.jpg", width: 800, height: 450 },
{ src: "/image2_400x225.jpg", width: 400, height: 225 },
],

@@ -146,3 +154,16 @@ },

export default function Gallery() {
return <PhotoAlbum layout="rows" photos={photos} />;
return (
<RowsPhotoAlbum
photos={photos}
sizes={{
size: "1168px",
sizes: [
{
viewport: "(max-width: 1200px)",
size: "calc(100vw - 32px)",
},
],
}}
/>
);
}

@@ -153,10 +174,10 @@ ```

React Photo Album extensively uses CSS flexbox and CSS `calc` function to
calculate the dimensions of images on the client. Unlike its predecessor, React
Photo Album avoids setting the exact dimensions of images in pixels. Thanks to
this approach, server-side rendered markup looks pixel-perfect on the client
even before hydration (or even when JavaScript is completely disabled in the
browser). To enable server-side rendering, be sure to specify
`defaultContainerWidth` prop. Otherwise, React Photo Album produces empty markup
on the server and renders on the client only after hydration.
React Photo Album extensively uses CSS flexbox and CSS `calc` functions to
calculate images' dimensions on the client. Thanks to this approach, server-side
rendered markup looks pixel-perfect on the client even before hydration. To
enable server-side rendering, specify the `defaultContainerWidth` prop.
Otherwise, React Photo Album produces an empty markup on the server and renders
on the client only after hydration. Please note that unless your photo album is
of constant width that always matches the `defaultContainerWidth` value, you
will most likely see a layout shift immediately after hydration.

@@ -163,0 +184,0 @@ ## Credits

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc