Skip to content

Commit

Permalink
fix(HeightAnimation): only hide overflow during animation
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Sep 20, 2022
1 parent 72b1da5 commit 673e754
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,42 @@ export function HeightAnimationDefault() {
/* jsx */ `
const Example = () => {
const [openState, setOpenState] = React.useState(false)
const [contentState, setContentState] = React.useState(false)
const onChangeHandler = ({ checked }) => {
setOpenState(checked)
}
return (
<>
<ToggleButton checked={openState} onChange={onChangeHandler} bottom>
Toggle me
<ToggleButton
checked={openState}
onChange={({ checked }) => {
setOpenState(checked)
}}
>
Open/close
</ToggleButton>
<Section style_type="lavender">
<Section style_type="lavender" top>
<HeightAnimation
open={openState}
>
<P className="content-element" top="large" bottom="large">
Your content
</P>
<ToggleButton
checked={contentState}
onChange={({ checked }) => {
setContentState(checked)
}}
space={{ top: true, bottom: true }}
>
Change height as well
</ToggleButton>
{contentState && <P space={0}>Your content</P>}
</HeightAnimation>
</Section>
<P top>Look at me 👀</P>
</>
)
}
Expand All @@ -50,26 +66,40 @@ export function HeightAnimationKeepInDOM() {
/* jsx */ `
const Example = () => {
const [openState, setOpenState] = React.useState(false)
const [contentState, setContentState] = React.useState(false)
const onChangeHandler = ({ checked }) => {
setOpenState(checked)
}
return (
<>
<ToggleButton checked={openState} onChange={onChangeHandler} bottom>
Toggle me
<ToggleButton
checked={openState}
onChange={({ checked }) => {
setOpenState(checked)
}}
>
Open/close
</ToggleButton>
<StyledSection style_type="lavender">
<StyledSection style_type="lavender" top>
<HeightAnimation
open={openState}
keepInDOM={true}
duration={1000}
>
<P className="content-element" space={0}>
Your content
</P>
<ToggleButton
checked={contentState}
onChange={({ checked }) => {
setContentState(checked)
}}
space={{ top: true, bottom: true }}
>
Change height as well
</ToggleButton>
{contentState && <P space={0}>Your content</P>}
</HeightAnimation>
</StyledSection>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,12 @@ export default function HeightAnimation({
}: HeightAnimationProps & ISpacingProps) {
const ref = React.useRef<HTMLElement>()

const { isInDOM, isVisible, isVisibleParallax } = useHeightAnimation(
innerRef || ref,
{
const { isInDOM, isVisible, isVisibleParallax, isAnimating } =
useHeightAnimation(innerRef || ref, {
open,
animate,
}
)
children,
})

if (!isInDOM && !keepInDOM) {
return null
Expand All @@ -84,6 +83,7 @@ export default function HeightAnimation({
isInDOM && 'dnb-height-animation--is-in-dom',
isVisible && 'dnb-height-animation--is-visible',
isVisibleParallax && 'dnb-height-animation--parallax',
isAnimating && 'dnb-height-animation--animating',
className
)}
style={style}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ describe('HeightAnimation', () => {
'dnb-height-animation--is-in-dom',
'dnb-height-animation--is-visible',
'dnb-height-animation--parallax',
'dnb-height-animation--animating',
])

fireEvent.click(document.querySelector('button'))
Expand All @@ -187,6 +188,7 @@ describe('HeightAnimation', () => {
'dnb-height-animation',
'dnb-height-animation--is-in-dom',
'dnb-height-animation--is-visible',
'dnb-height-animation--animating',
])

simulateAnimationEnd()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,60 @@ import styled from '@emotion/styled'
import React from 'react'
import { P } from '../../../elements'
import Section from '../../section/Section'
import ToggleButton from '../../toggle-button/ToggleButton'
import { ToggleButton, Button } from '../../'
import HeightAnimation from '../HeightAnimation'

export default {
title: 'Eufemia/Components/HeightAnimation',
}

export const HeightAnimationSandbox = () => {
const [openState, setOpenState] = React.useState(false)

const onChangeHandler = ({ checked }) => {
setOpenState(checked)
}
const [count, setCount] = React.useState(0)
const [openState, setOpenState] = React.useState(true)
const [contentState, setContentState] = React.useState(false)

return (
<>
<ToggleButton checked={openState} onChange={onChangeHandler}>
Toggle me
<ToggleButton
checked={openState}
onChange={({ checked }) => {
setOpenState(checked)
}}
>
Open/close
</ToggleButton>

<StyledSection style_type="lavender">
<Button
onClick={() => {
setCount(count + 1)
}}
>
{count}
</Button>

<StyledSection style_type="lavender" top>
<HeightAnimation
open={openState}
element="div" // Optional
animate={true} // Optional
keepInDOM={true} // Optional
// duration={1000}
>
<P className="content-element" space={0}>
Your content
</P>
<ToggleButton
checked={contentState}
onChange={({ checked }) => {
setContentState(checked)
}}
space={{ top: true, bottom: true }}
>
Change height as well
</ToggleButton>

{contentState && <P>Your content</P>}
</HeightAnimation>
</StyledSection>

<P top>Look at me 👀</P>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/

.dnb-height-animation {
overflow: hidden;
transition: height var(--duration, 400ms) var(--easing-default);

&--animating {
overflow: hidden;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,43 @@ import React from 'react'
import AnimateHeight from '../../shared/AnimateHeight'

type useHeightAnimationOptions = {
/**
* Set to `true` when the view should animate from 0px to auto.
* Default: false
*/
open?: boolean

/**
* Set to `false` to omit the animation.
* Default: true
*/
animate?: boolean

/**
* In order to let the Hook know when children has changed
*/
children?: React.ReactNode
}

export type HeightAnimationOnStartTypes = 'opening' | 'closing'
export type HeightAnimationOnEndTypes = 'opened' | 'closed'
export type HeightAnimationOnStartTypes =
| 'opening'
| 'closing'
| 'adjusting'

export type HeightAnimationOnEndTypes = 'opened' | 'closed' | 'adjusted'

export function useHeightAnimation(
targetRef: React.RefObject<HTMLElement>,
{ open = null, animate = true }: useHeightAnimationOptions = {}
{
open = null,
animate = true,
children = null,
}: useHeightAnimationOptions = {}
) {
const animRef = React.useRef(null)
const [isOpen, setIsOpen] = React.useState(open)
const [isVisible, setIsVisible] = React.useState(false)
const [isAnimating, setIsAnimating] = React.useState(false)
const [isVisibleParallax, setParallax] = React.useState(open)
const [isInitialRender, setIsMounted] = React.useState(true)

Expand All @@ -30,28 +53,42 @@ export function useHeightAnimation(

if (animate) {
animRef.current.onStart((state: HeightAnimationOnStartTypes) => {
// console.log('onStart', state)
switch (state) {
case 'opening':
setIsVisible(true)
setParallax(true)
setIsAnimating(true)
break

case 'closing':
setParallax(false)
setIsAnimating(true)
break

case 'adjusting':
setIsAnimating(true)
break
}
})

animRef.current.onEnd((state: HeightAnimationOnEndTypes) => {
// console.log('onEnd', state)
switch (state) {
case 'opened':
setIsOpen(true)
setIsAnimating(false)
break

case 'closed':
setIsVisible(false)
setIsOpen(false)
setParallax(false)
setIsAnimating(false)
break

case 'adjusted':
setIsAnimating(false)
break
}
})
Expand All @@ -60,6 +97,20 @@ export function useHeightAnimation(
return () => animRef.current?.remove()
}, [animate])

useOpenClose({ open, animRef, targetRef, isInitialRender })
useAdjust({ children, animRef, isInitialRender })

return {
open,
isOpen,
isInDOM: open || isVisible,
isVisible,
isVisibleParallax,
isAnimating,
}
}

function useOpenClose({ open, animRef, targetRef, isInitialRender }) {
React.useLayoutEffect(() => {
if (!targetRef.current) {
return // stop here
Expand All @@ -77,12 +128,53 @@ export function useHeightAnimation(
anim.close()
}
}, [open, targetRef, animRef]) // eslint-disable-line
}

return {
open,
isOpen,
isInDOM: open || isVisible,
isVisible,
isVisibleParallax,
}
function useAdjust({ children, animRef, isInitialRender }) {
const fromHeight = React.useRef(0)

React.useMemo(() => {
if (!animRef.current || isInitialRender) {
return // stop here
}

const { state } = animRef.current

switch (state) {
case 'adjusting':
fromHeight.current = animRef.current?.getHeight()
break

case 'opened':
case 'adjusted':
fromHeight.current = animRef.current?.getUnknownHeight()
break
}
}, [children]) // eslint-disable-line react-hooks/exhaustive-deps

React.useLayoutEffect(() => {
if (!animRef.current || isInitialRender) {
return // stop here
}

const { state } = animRef.current

switch (state) {
case 'opened':
case 'adjusted':
case 'adjusting':
/**
* Ensure we don't have height, while we get the "toHeight" again
* We may move this inside of the AnimateHeight class later,
* but the GlobalStatus is currently re
*/
animRef.current.elem.style.height = ''

animRef.current.adjustTo(
fromHeight.current,
animRef.current.getHeight() // use getHeight instead of getUnknownHeight because of the additional, disturbing DOM manipupation
)
break
}
}, [children]) // eslint-disable-line react-hooks/exhaustive-deps
}
Loading

0 comments on commit 673e754

Please sign in to comment.