salon.
a salon hang: a wall of images shown at their own aspect ratios, packed into justified rows that each fill the width (the standard greedy justified-gallery algorithm, with a sparse trailing row left-aligned at the target height instead of ballooned into a billboard). it isn't `gallery` (the uniform searchable card grid — every project the same card sized to a column), `shelf` (one horizontal scrolling row you skim), or `showcase` (a single framed preview): a salon keeps every piece's real proportions and makes the varied hang itself the point, so reach for it when the images are the content and their shapes carry meaning. the row-packing geometry ships as a separate tested module.
every piece keeps its own proportions; each row is justified to fill the width. narrow the window and the hang reflows. two pieces link out.
bunx @justin06lee/chrome@latest add salon| name | type | default | description |
|---|---|---|---|
| items * | SalonItem[] | - | the pieces to hang, each at its own aspect ratio: { src?: string; width: number; height: number; href?: string; alt?: string; title?: ReactNode; external?: boolean }[]. width/height are intrinsic pixels that set the aspect ratio, not the render size; omit src to render a typographic placard in the slot; a title shows as a placard on hover/focus; external (or an http(s) href) opens in a new tab as a plain <a>. |
| targetRowHeight | number | 260 | ideal row height in px before a row is justified to fill the width. |
| gap | number | 12 | gap between pieces and rows in px. |
| maxRowHeight | number | targetRowHeight * 1.5 | how tall a trailing, un-fillable row may grow before it stops stretching and left-aligns at the target height. |
| maxWidth | number | - | container max width in px. unbounded by default (fills its parent). |
| assumedWidth | number | 1040 | assumed width for the first (server) render, before the container is measured. |
| linkComponent | React.ElementType | - | router link for internal hrefs (e.g. next/link). defaults to a plain <a>; external hrefs always stay <a>. |
| ariaLabel | string | - | accessible name for the wall region. |
| className | string | - |