import { IStyle , mergeStyles } from '@fluentui/react' ; import React , { useRef , useEffect , useState , useMemo } from 'react' ; import { BaseCustomStyles } from '../types' ; import { gridLayoutStyle } from './styles/GridLayout.styles' ; /**
 * Props for  GridLayout .
 *
 *  public export interface GridLayoutProps { children : React . ReactNode ; Example styles ? : BaseCustomStyles ; } /**
 *  GridLayout Component Styles.
 *  public export interface GridLayoutStyles extends BaseCustomStyles { /** Styles for each child of  GridLayout children ? : IStyle ; } public export const GridLayout = ( props ) : JSX . Element => { const { children , styles } = props ; const numberOfChildren = React . Children . count ( children ) ; const [ currentWidth , setCurrentWidth ] = useState ( 0 ) ; const [ currentHeight , setCurrentHeight ] = useState ( 0 ) ; const containerRef = useRef < HTMLDivElement > ( null ) ; const observer = useRef ( new ResizeObserver ( ( entries ) : void => { const { width , height } = entries [ 0 ] . contentRect ; setCurrentWidth ( width ) ; setCurrentHeight ( height ) ; } ) ) ; useEffect ( ( ) => { if ( containerRef . current ) { observer . current . observe ( containerRef . current ) ; } const currentObserver = observer . current ; return ( ) => currentObserver . disconnect ( ) ; } , [ observer , containerRef ] ) ; const gridProps = useMemo ( ( ) => { return calculateGridProps ( numberOfChildren , currentWidth , currentHeight ) ; } , [ numberOfChildren , currentWidth , currentHeight ] ) ; const cssGridStyles = useMemo ( ( ) => createGridStyles ( numberOfChildren , gridProps ) , [ numberOfChildren , gridProps ] ) ; return ( < div ref = { containerRef } className = { mergeStyles ( gridLayoutStyle , cssGridStyles , styles ?. root ) } > { children } < /div>); } ; /**
 * The cell aspect ratio we aim for in a grid
 */ const TARGET_CELL_ASPECT_RATIO = 16 / 9 ; /**
 * The minimum cell aspect ratio we allow
 */ const MINIMUM_CELL_ASPECT_RATIO_ALLOWED = 8 / 9 ; const isCloserThan = ( a , b , target ) : boolean => { return Math . abs ( target - a ) < Math . abs ( target - b ) ; } ; Example type GridProps = { fillDirection : FillDirection ; rows : number ; columns : number ; } ; type FillDirection = 'horizontal' | 'vertical' ; param numberOfItems param width param height returns export const calculateGridProps = ( numberOfItems , width , height ) : GridProps => { if ( numberOfItems <= 0 ) { return { fillDirection : 'horizontal' , rows : 0 , columns : 0 } ; } if ( width <= 0 || height <= 0 ) { return { fillDirection : 'horizontal' , rows : Math . ceil ( Math . sqrt ( numberOfItems ) ) , columns : Math . ceil ( Math . sqrt ( numberOfItems ) ) } ; } const aspectRatio = width / height ; let rows = Math . floor ( Math . sqrt ( ( TARGET_CELL_ASPECT_RATIO / aspectRatio ) * numberOfItems ) ) || 1 ; rows = Math . min ( rows , numberOfItems ) ; let columns = Math . ceil ( numberOfItems / rows ) ; let fillDirection = 'horizontal' ; while ( rows < numberOfItems ) { if ( ( rows / columns ) * aspectRatio < MINIMUM_CELL_ASPECT_RATIO_ALLOWED ) { rows += 1 ; columns = Math . ceil ( numberOfItems / rows ) ; continue ; } if ( numberOfItems < rows * columns ) { const canStretchVertically = numberOfItems >= rows + ( columns - 1 ) * ( rows - 1 ) ; const canStretchHorizontally = numberOfItems >= columns + ( rows - 1 ) * ( columns - 1 ) ; if ( ! canStretchVertically && ! canStretchHorizontally ) { rows += 1 ; columns = Math . ceil ( numberOfItems / rows ) ; continue ; } else if ( ! canStretchVertically ) { break ; } else if ( ! canStretchHorizontally ) { fillDirection = 'vertical' ; break ; } const horizontallyStretchedCellRatio = ( rows / ( columns - 1 ) ) * aspectRatio ; const verticallyStretchedCellRatio = ( ( rows - 1 ) / columns ) * aspectRatio ; if ( verticallyStretchedCellRatio >= MINIMUM_CELL_ASPECT_RATIO_ALLOWED ) { if ( isCloserThan ( verticallyStretchedCellRatio , horizontallyStretchedCellRatio , TARGET_CELL_ASPECT_RATIO ) ) { fillDirection = 'vertical' ; } } } break ; } return { fillDirection , rows , columns } ; } ; param numberOfItems param gridProps returns export const createGridStyles = ( numberOfItems , gridProps ) : string => { const isHorizontal = gridProps . fillDirection === 'horizontal' ; const blocks = isHorizontal ? gridProps . rows : gridProps . columns ; const smallCellsPerBlock = Math . ceil ( numberOfItems / blocks ) ; const bigCellsPerBlock = Math . floor ( numberOfItems / blocks ) ; const numBigCells = ( gridProps . rows * gridProps . columns - numberOfItems ) * bigCellsPerBlock ; const units = smallCellsPerBlock * bigCellsPerBlock ; const gridStyles = isHorizontal ? { gridTemplateColumns : `repeat(${ units }, minmax(0, 1fr))` , gridTemplateRows : `repeat(${ blocks }, minmax(0, 1fr))` , gridAutoFlow : 'row' } : { gridTemplateColumns : `repeat(${ blocks }, minmax(0, 1fr))` , gridTemplateRows : `repeat(${ units }, minmax(0, 1fr))` , gridAutoFlow : 'column' } ; const smallCellStyle = isHorizontal ? { '> *' : { gridColumn : `auto / span ${ units / smallCellsPerBlock }` } } : { '> *' : { gridRow : `auto / span ${ units / smallCellsPerBlock }` } } ; const bigCellStyle = numBigCells ? { [ `> *:nth-last-child(-n + ${ numBigCells })` ] : isHorizontal ? { gridColumn : `auto / span ${ units / bigCellsPerBlock }` } : { gridRow : `auto / span ${ units / bigCellsPerBlock }` } } : { } ; return mergeStyles ( gridStyles , smallCellStyle , bigCellStyle ) ; } ;