import styled from '@emotion/styled' ; import { Children , isValidElement , useMemo } from 'react' ; import useWindowDimensions from '~/hooks/useWindowDimensions' ; import { Component , ComponentProps , ComponentChild } from '~/types' ; const MasonryGrid = styled . div `
  display: flex;
  flex-direction: row;
  justify-content: center;
  width: 100%;
  max-width: 100%;
  align-content: stretch;
  box-sizing: border-box;
` ; const MasonryColumn = styled ( MasonryGrid ) `
  flex-direction: column;
  justify-content: flex-start;
  flex: 1;
  width: 0;
  padding: 0.1rem 0;
` ; export type MasonryBreakpoints = { [ key ] : number ; } ; interface MasonryProps extends ComponentProps { breakpoints ? : MasonryBreakpoints ; gap ? : string | 0 ; } const defaultBreakpoints = { 0 : 1 } ; const mapColumnsBreakPoints = ( breakpoints ? ) : MasonryBreakpoints => { if ( ! breakpoints ) return { } ; const mappedBreakpoints = { } ; for ( const key of Object . keys ( breakpoints ) ) { const newKey = key . endsWith ( 'px' ) ? key . substring ( 0 , key . length - 2 ) : key ; mappedBreakpoints [ newKey ] = breakpoints [ key ] ; } return mappedBreakpoints ; } ; const buildMasonryColumns = ( children , columnsCount ) => { const columns = Array . from ( { length : columnsCount } , ( ) => [ ] ) ; Children . forEach ( children , ( child , index ) => { if ( child && isValidElement ( child ) ) { columns [ index % columnsCount ] . push ( child ) ; } } ) ; return columns ; } ; export const Masonry = ( props ) => { const { breakpoints = defaultBreakpoints , gap = 0 , children , className , style , } = props ; const childrenCount = Children . count ( children ) ; const { width : windowWidth } = useWindowDimensions ( ) ; const mappedBreakpoints = useMemo < MasonryBreakpoints > ( ( ) => { return mapColumnsBreakPoints ( breakpoints ) ; } , [ breakpoints ] ) ; const columnsCount = useMemo < number > ( ( ) => { const breakpoints = Object . keys ( mappedBreakpoints ) . sort ( ( a , b ) => Number ( a ) - Number ( b ) ) . map ( ( breakpoint ) => Number ( breakpoint ) ) ; let count = breakpoints . length > 0 ? mappedBreakpoints [ breakpoints [ 0 ] ] : 1 ; breakpoints . forEach ( ( breakpoint ) => { if ( breakpoint < windowWidth ) { count = mappedBreakpoints [ breakpoint ] ; } } ) ; return count ; } , [ windowWidth , mappedBreakpoints ] ) ; const columns = useMemo < Array < Array < ComponentChild > > > ( ( ) => { return buildMasonryColumns ( children , columnsCount ) ; } , [ children , columnsCount ] ) ; if ( ! childrenCount ) return null ; return ( < MasonryGrid className = { className } style = { { ... style , gap } } > { columns . map ( ( col , index ) => { return ( < MasonryColumn key = { `col-${ index }` } style = { { gap } } > { col . map ( ( element ) => element ) } < /MasonryColumn>); } ) } < /MasonryGrid>); } ;