petersburg
v0.0.3
Published
React layout library implementing Hermitage-style 2D bin-packing for picture arrangement
Maintainers
Readme
Petersburg
A React layout library implementing Hermitage-style 2D bin-packing for picture arrangement.
Named after the Hermitage Museum in St. Petersburg, where curators arrange paintings tetris-style to maximize limited wall space.
Features
- MaxRects bin-packing algorithm — efficient 2D rectangle packing
- Multiple sort strategies — optimize for packing efficiency or preserve input order
- Auto-height measurement — items can omit height; Petersburg measures rendered content
- Responsive — auto-measures container and recalculates on resize
- Accessible — DOM order matches visual flow for proper tab navigation
- Lightweight — no dependencies beyond React
Installation
npm install petersburgBasic Usage
import { HermitageLayout } from 'petersburg';
const items = [
{ id: '1', width: 200, height: 150, content: <img src="..." /> },
{ id: '2', width: 100, height: 100, content: <img src="..." /> },
{ id: '3', width: 150, height: 200, content: <img src="..." /> },
];
function Gallery() {
return (
<HermitageLayout
items={items}
gap={8}
sortStrategy="ordered"
/>
);
}Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| items | LayoutItem[] | required | Array of items to layout |
| containerWidth | number | auto | Fixed width in pixels. If omitted, measures parent container. |
| gap | number | 8 | Gap between items in pixels |
| sortStrategy | SortStrategy | "none" | How to order items during packing |
| className | string | — | CSS class for the container |
Types
interface LayoutItem {
id: string;
width: number;
height?: number; // Optional — if omitted, auto-measured from content
content: ReactNode;
}
type SortStrategy =
| "none" // Keep input order
| "height-desc" // Sort by height descending (best packing)
| "ordered"; // Row 1 strict order, row 2+ flexibleSort Strategies
none
Items are placed in input order using the MaxRects algorithm. Good for when your input is already sorted (e.g., by date) and you want to preserve that order as much as possible.
height-desc
Items are sorted by height (tallest first) before packing. This typically produces the most compact layout with minimal wasted space.
ordered
A hybrid approach for galleries where order matters at the top but efficiency matters overall:
- Row 1: Items placed strictly left-to-right in input order
- Row 2: Next batch of items, can be reordered within the row to fill gaps
- Row 3+: Full algorithmic freedom for optimal packing
This is ideal for "newest items at top" layouts where the first row should show items 1, 2, 3... in order, but lower rows can be optimized.
Auto-Height Mode
For items with dynamic content (text cards, variable-length descriptions), omit the height property:
const items = [
{ id: '1', width: 200, content: <Card title="Short" /> },
{ id: '2', width: 200, content: <Card title="Much Longer Title Here" /> },
{ id: '3', width: 150, content: <Card title="Medium" description="With extra text" /> },
];
<HermitageLayout items={items} gap={8} />Petersburg will:
- Render items invisibly to measure their natural height
- Pack using the measured dimensions
- Display the final layout
This adds a brief measurement phase but avoids content overflow or fixed-height constraints.
Responsive Layouts
Omit containerWidth to enable responsive mode:
<div style={{ width: '100%' }}>
<HermitageLayout items={items} gap={8} />
</div>The component will measure its parent container and recalculate the layout when the container resizes.
Fixed Width
For fixed-width layouts, provide containerWidth:
<HermitageLayout
items={items}
containerWidth={800}
gap={8}
/>Styling Items
Each item is rendered in an absolutely-positioned wrapper. Style your content to fill it:
const items = [
{
id: '1',
width: 200,
height: 150,
content: (
<img
src="..."
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
),
},
];Animations
Petersburg intentionally doesn't include animations to stay lightweight. Add your own with CSS transitions:
.my-gallery img {
transition: transform 0.3s ease, opacity 0.3s ease;
}Or use your preferred animation library on the item content.
How It Works
Petersburg uses the MaxRects bin-packing algorithm:
- Start with the full container as free space
- For each item, find the best position (topmost, then leftmost)
- Place the item and split the remaining free space into new rectangles
- Prune redundant free rectangles
- Repeat until all items are placed
This produces efficient layouts where items fill gaps left by differently-sized neighbors.
License
MIT
