import React, { ReactNode, useLayoutEffect, useState } from 'react' import debounce from 'lodash.debounce' import { responsiveBreakpoints } from '../branding' export enum MediaSize { SMALL = 'small', LARGE = 'large', } const DEFAULT_MEDIA_SIZE = MediaSize.SMALL export type MediaSizeProviderProps = Readonly<{ children: ReactNode serverSideMediaSize?: MediaSize mediaSizeForTestsOnly?: MediaSize }> export const MediaSizeContext = React.createContext(DEFAULT_MEDIA_SIZE) const DEBOUNCE_VALUE = 500 export const useIsSmallMediaSize = (): boolean => { const mediaSize = React.useContext(MediaSizeContext) return mediaSize === MediaSize.SMALL } export const useIsLargeMediaSize = (): boolean => { const mediaSize = React.useContext(MediaSizeContext) return mediaSize === MediaSize.LARGE } const computeMediaSizeFromViewport = (): MediaSize => { const isSmall = window.innerWidth <= parseInt(responsiveBreakpoints.small, 10) return isSmall ? MediaSize.SMALL : MediaSize.LARGE } // Provides a media size based on the client viewport size to nested components. // Use dedicated hooks useIsSmallMediaSize and/or useIsLargeMediaSize to get this media size. // If the MediaSizeProvider is rendered server side, no viewport is available and a default media // size will be sent. This default server side media size can be changed with serverSideMediaSize // props. export const MediaSizeProvider = (props: MediaSizeProviderProps) => { const { children, serverSideMediaSize, mediaSizeForTestsOnly } = props const [mediaSize, setMediaSize] = useState(serverSideMediaSize || DEFAULT_MEDIA_SIZE) const updateMediaSize = () => { setMediaSize(computeMediaSizeFromViewport()) } useLayoutEffect(() => { if (!window) { // Server side rendering, no need to execute the client-side effects. return () => {} } // Initial client-side update. updateMediaSize() // Register listener for screen resize events. const debouncedHandleResize = debounce(updateMediaSize, DEBOUNCE_VALUE) window.addEventListener('resize', debouncedHandleResize) return () => { window.removeEventListener('resize', debouncedHandleResize) } }, []) if (mediaSizeForTestsOnly) { // When mediaSizeForTestsOnly is set for tests, override any previous logic. return ( {children} ) } return {children} }