Skip to content

Instantly share code, notes, and snippets.

@izakfilmalter
Created June 19, 2020 18:43
Show Gist options
  • Save izakfilmalter/a99305f88797e06e631ad3a6d09a4e2f to your computer and use it in GitHub Desktop.
Save izakfilmalter/a99305f88797e06e631ad3a6d09a4e2f to your computer and use it in GitHub Desktop.
import css from '@emotion/css'
import { SerializedStyles } from '@emotion/serialize'
import styled from '@emotion/styled'
import { CircularProgress } from '@material-ui/core'
import React, { FC, ReactNode, useLayoutEffect, useState } from 'react'
import { ease, visible } from 'Helpers/animation'
import { BorderRadius, getBorderRadius } from 'Helpers/borderRadius'
import { Colors, getColor } from 'Helpers/colors'
import { flexFlow } from 'Helpers/flex'
import { isNotNil } from 'Helpers/typeGuards'
import { getZIndex, ZIndexNodeName } from 'Helpers/zIndex'
const ViewLoaderContainer = styled.div<{
transparent: boolean
}>(
({ transparent }) => css`
${flexFlow('column')};
${getBorderRadius(BorderRadius.Large)};
background: ${transparent ? 'transparent' : getColor(Colors.Black)};
position: relative; // pos:rel because the loader has to pos:abs
flex: 1;
overflow: hidden;
`,
)
const EmptyStateContainer = styled.div<{ showEmptyState: boolean }>(
({ showEmptyState }) => css`
${visible(showEmptyState)};
transform: scale(${showEmptyState ? 1 : 0.97})
translateY(${showEmptyState ? 0 : 16}px);
transition: all ${ease(showEmptyState)} 500ms;
`,
)
const ChildrenContainer = styled.div<{ showChildren: boolean }>(
({ showChildren }) => css`
${visible(showChildren)};
${flexFlow('column')};
flex: 1;
overflow: hidden;
transition: all ${ease(showChildren)} 32ms;
`,
)
export type ViewLoaderProps = Omit<LoadingListProps, 'transparent'> & {
EmptyState?: ReactNode
blackoutCss?: SerializedStyles
isEmpty: boolean
transparent?: boolean
}
export const ViewLoader: FC<ViewLoaderProps> = props => {
const {
children,
isLoaded,
isEmpty,
EmptyState,
blackoutCss,
transparent = false,
...domProps
} = props
const showEmptyState = isEmpty && isLoaded
const showChildren = isLoaded && !isEmpty
return (
<ViewLoaderContainer transparent={transparent} {...domProps}>
<BlackOut
isLoaded={isLoaded}
css={blackoutCss}
transparent={transparent}
/>
<EmptyStateContainer showEmptyState={showEmptyState}>
{showEmptyState && isNotNil(EmptyState) ? EmptyState : <></>}
</EmptyStateContainer>
<ChildrenContainer showChildren={showChildren}>
{children}
</ChildrenContainer>
</ViewLoaderContainer>
)
}
type BlackOutContainerProps = {
isVisible: boolean
transparent: boolean
}
const BlackOutContainer = styled.div<BlackOutContainerProps>(
({ isVisible, transparent }) => css`
${flexFlow('column')};
${getZIndex(ZIndexNodeName.BlackOut)};
background: ${transparent
? 'transparent'
: getColor(Colors.Black, isVisible ? 0.9999 : 0)};
border-radius: inherit;
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
pointer-events: none;
transition: all ${ease(isVisible)} 32ms;
`,
)
export type LoadingListProps = {
isLoaded: boolean
transparent: boolean
}
export const BlackOut: FC<LoadingListProps> = props => {
const { isLoaded, transparent, ...domProps } = props
const [showLoader, setShowLoader] = useState(false)
useLayoutEffect(() => {
setTimeout(() => {
if (!isLoaded) {
setShowLoader(true)
}
// Using 32 to here because we are running at 60fps and wanna wait for two frames.
}, 32)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useLayoutEffect(() => {
if (isLoaded && showLoader) {
setShowLoader(false)
}
}, [isLoaded, showLoader])
return (
<BlackOutContainer
isVisible={!isLoaded}
transparent={transparent}
{...domProps}
>
<CircularProgress
css={css`
${visible(showLoader)};
margin: auto;
transition: all ${ease(showLoader)} 32ms;
`}
/>
</BlackOutContainer>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment