|
import React, { |
|
Children, |
|
Fragment, |
|
ReactNode, |
|
useEffect, |
|
useMemo, |
|
useRef, |
|
useState, |
|
} from 'react'; |
|
|
|
interface FlexWrapProps { |
|
children: ReactNode; |
|
separator: ReactNode; |
|
} |
|
|
|
export default function FlexWrap({ children, separator }: FlexWrapProps) { |
|
const containerRef = useRef<HTMLDivElement>(null); |
|
const itemRefs = useRef<(HTMLDivElement | null)[]>([]); |
|
const separatorRef = useRef<HTMLDivElement>(null); |
|
const [rows, setRows] = useState<Array<Array<number>>>([]); |
|
|
|
const childArray = useMemo(() => Children.toArray(children), [children]); |
|
|
|
useEffect(() => { |
|
function calculateRows() { |
|
if (!containerRef.current || itemRefs.current.length === 0) return; |
|
|
|
const containerWidth = containerRef.current.offsetWidth; |
|
const items = itemRefs.current.filter( |
|
(ref) => ref !== null, |
|
) as HTMLDivElement[]; |
|
|
|
if (items.length === 0) return; |
|
|
|
// Get separator width |
|
const separatorWidth = separatorRef.current?.offsetWidth || 0; |
|
|
|
// Calculate rows by manually checking widths |
|
const newRows: Array<Array<number>> = []; |
|
let currentRow: Array<number> = []; |
|
let currentRowWidth = 0; |
|
|
|
items.forEach((item, index) => { |
|
const itemWidth = item.offsetWidth; |
|
const separatorNeeded = currentRow.length > 0 ? separatorWidth : 0; |
|
const totalWidthNeeded = currentRowWidth + separatorNeeded + itemWidth; |
|
|
|
if (currentRow.length === 0 || totalWidthNeeded <= containerWidth) { |
|
// Item fits on current row |
|
currentRow.push(index); |
|
currentRowWidth = totalWidthNeeded; |
|
} else { |
|
// Item doesn't fit, start new row |
|
if (currentRow.length > 0) { |
|
newRows.push([...currentRow]); |
|
} |
|
currentRow = [index]; |
|
currentRowWidth = itemWidth; |
|
} |
|
}); |
|
|
|
// Add the last row if it has items |
|
if (currentRow.length > 0) { |
|
newRows.push(currentRow); |
|
} |
|
|
|
setRows(newRows); |
|
} |
|
|
|
calculateRows(); |
|
|
|
const resizeObserver = new ResizeObserver(calculateRows); |
|
if (containerRef.current) { |
|
resizeObserver.observe(containerRef.current); |
|
} |
|
|
|
return () => resizeObserver.disconnect(); |
|
}, [children, childArray.length]); |
|
|
|
return ( |
|
<div ref={containerRef} className="w-full"> |
|
{/* Hidden measurement elements */} |
|
<div className="invisible absolute -top-full" aria-hidden="true"> |
|
{childArray.map((child, index) => ( |
|
<div |
|
key={index} |
|
ref={(el) => { |
|
itemRefs.current[index] = el; |
|
}} |
|
className="inline-block" |
|
> |
|
{child} |
|
</div> |
|
))} |
|
<div ref={separatorRef} className="inline-block"> |
|
{separator} |
|
</div> |
|
</div> |
|
|
|
{/* Actual render - organized into explicit rows */} |
|
<div className="flex w-full flex-col"> |
|
{rows.map((row, rowIndex) => ( |
|
<div key={rowIndex} className="flex items-center overflow-hidden"> |
|
{row.map((childIndex, posInRow) => ( |
|
<Fragment key={childIndex}> |
|
<div className="flex flex-shrink-0 items-center"> |
|
{childArray[childIndex]} |
|
</div> |
|
{posInRow < row.length - 1 && ( |
|
<div className="flex flex-shrink-0 items-center"> |
|
{separator} |
|
</div> |
|
)} |
|
</Fragment> |
|
))} |
|
</div> |
|
))} |
|
</div> |
|
</div> |
|
); |
|
} |
Uh oh!
There was an error while loading. Please reload this page.