Skip to content

Commit

Permalink
feat(HeightAnimation): adjust height with animation when content chan…
Browse files Browse the repository at this point in the history
…ges (#1569)
  • Loading branch information
tujoworker authored Sep 23, 2022
1 parent 8077fdd commit f0779c2
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,48 @@ export function HeightAnimationDefault() {
/* jsx */ `
const Example = () => {
const [openState, setOpenState] = React.useState(false)
const [isOpen, setIsOpen] = React.useState(openState)
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)
}}
right
>
Open/close
</ToggleButton>
<ToggleButton
checked={contentState || !openState}
disabled={!isOpen}
onChange={({ checked }) => {
setContentState(checked)
}}
space={{ top: true, bottom: true }}
>
Change height inside
</ToggleButton>
<Section style_type="lavender">
<Section style_type="lavender" top>
<HeightAnimation
open={openState}
onOpen={setIsOpen}
>
<P className="content-element" top="large" bottom="large">
Your content
</P>
<Section spacing style_type="lavender">
<P space={0}>Your content</P>
</Section>
{contentState && <P space={0}>More content</P>}
</HeightAnimation>
</Section>
<P top>Look at me 👀</P>
</>
)
}
Expand All @@ -49,27 +71,47 @@ export function HeightAnimationKeepInDOM() {
{
/* jsx */ `
const Example = () => {
const [openState, setOpenState] = React.useState(false)
const [openState, setOpenState] = React.useState(true)
const [isOpen, setIsOpen] = React.useState(openState)
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)
}}
right
>
Open/close
</ToggleButton>
<ToggleButton
checked={contentState || !openState}
disabled={!isOpen}
onChange={({ checked }) => {
setContentState(checked)
}}
space={{ top: true, bottom: true }}
>
Change height inside
</ToggleButton>
<StyledSection style_type="lavender">
<StyledSection style_type="lavender" top>
<HeightAnimation
open={openState}
keepInDOM={true}
duration={1000}
onOpen={setIsOpen}
>
<P className="content-element" space={0}>
Your content
</P>
<Section spacing style_type="lavender">
<P space={0}>Your content</P>
</Section>
{contentState && <P space={0}>More content</P>}
</HeightAnimation>
</StyledSection>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ showTabs: true

## Events

No events are supported at the moment.
| Events | Description |
| -------- | ----------------------------------------------------------------------------------------------------- |
| `onOpen` | _(optional)_ Is called when fully opened or closed. Returns `true` or `false` depending on the state. |
| `onOpen` | _(optional)_ Is called when fully opened or closed. Returns `true` or `false` depending on the state. |
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ showTabs: true

## Description

The HeightAnimation component is a helper component to animate from 0px to height:auto powered by CSS. It calculates the height on the fly.
The HeightAnimation component is a helper component to animate from `0` to `height: auto` powered by CSS. It calculates the height on the fly.

When the animation is done, it sets the element's height to `auto`.

The component can be used as an opt-int replacement instead of vanilla HTML Elements.

The element animation is done with a CSS transition and a `400ms` duration:
The element animation is done with a CSS transition with `400ms` in duration.

It also re-calculates and changes the height, when the given content changes.

## Accessibility

It is important to never animate from 0 to e.g. 64px – because the content may differ based on the viewport width (screen size), the content itself, or the user may even have a larger `font-size`.
It is important to never animate from 0 to e.g. 64px – because;

- the content may differ based on the viewport width (screen size)
- the content itself may change
- the user may have a larger `font-size`
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { useState } from 'react'
import { Box, Wrapper } from 'storybook-utils/helpers'
import {
Skeleton,
ToggleButton,
// ToggleButton,
// Button,
} from '../../'
Expand Down Expand Up @@ -44,19 +45,24 @@ const breadcrumbItems: BreadcrumbItemProps[] = [
]

export const Multiple = () => {
// const [showHome, makeHomeVisible] = React.useState(false)
const list = [...breadcrumbItems]
const [removeLast, setRemoveLast] = React.useState(false)
if (removeLast) {
list.pop()
}
return (
<Provider>
{/* <ToggleButton
on_change={() => {
makeHomeVisible((s) => !s)
}}
>
Toggle Home
</ToggleButton>
<br /> */}
<Skeleton>
<Breadcrumb data={breadcrumbItems} />
<ToggleButton
bottom
on_change={() => {
setRemoveLast((s) => !s)
}}
>
Toggle last item
</ToggleButton>

<Breadcrumb data={list} />
</Skeleton>
</Provider>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ export type HeightAnimationProps = {
*/
innerRef?: React.RefObject<HTMLElement>

/**
* Is called when fully opened or closed
* Default: null
*/
onOpen?: (isOpen: boolean) => void

/**
* Is called when animation is done and the full height has reached
* Default: null
*/
onAnimationEnd?: () => void

className?: React.ReactNode
children?: React.ReactNode | HTMLElement
}
Expand All @@ -54,17 +66,20 @@ export default function HeightAnimation({
className,
innerRef,
children,
onOpen = null,
onAnimationEnd = null,
...props
}: 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,
onOpen,
onAnimationEnd,
})

if (!isInDOM && !keepInDOM) {
return null
Expand All @@ -84,6 +99,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 @@ -33,6 +33,7 @@ describe('HeightAnimation', () => {
open = false,
animate = true,
element = 'div',
children,
...props
}: Partial<HeightAnimationProps>) => {
const [openState, setOpenState] = React.useState(open)
Expand All @@ -58,7 +59,7 @@ describe('HeightAnimation', () => {
animate={animate} // Optional
{...props}
>
<p className="content-element">Your content</p>
<p className="content-element">Your content {children}</p>
</HeightAnimation>
</section>
</>
Expand Down Expand Up @@ -115,6 +116,50 @@ describe('HeightAnimation', () => {
})
})

it('should adjust height when content changes', async () => {
const { rerender } = render(<Component />)

expect(document.querySelector('.dnb-height-animation')).toBeFalsy()

rerender(<Component open />)

await act(async () => {
const element = document.querySelector('.dnb-height-animation')

simulateAnimationEnd()

expect(
document
.querySelector('.dnb-height-animation')
.getAttribute('style')
).toBe('height: auto;')

rerender(<Component open>123</Component>)

await wait(1)

expect(
document
.querySelector('.dnb-height-animation')
.getAttribute('style')
).toBe('height: 0px;')

jest
.spyOn(element, 'clientHeight', 'get')
.mockImplementationOnce(() => 100)

rerender(<Component open>456</Component>)

await wait(1)

expect(
document
.querySelector('.dnb-height-animation')
.getAttribute('style')
).toBe('height: 100px;')
})
})

it('should have content in DOM when keepInDOM is true', async () => {
const { rerender } = render(<Component keepInDOM />)

Expand Down Expand Up @@ -178,6 +223,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 +233,7 @@ describe('HeightAnimation', () => {
'dnb-height-animation',
'dnb-height-animation--is-in-dom',
'dnb-height-animation--is-visible',
'dnb-height-animation--animating',
])

simulateAnimationEnd()
Expand Down
Loading

0 comments on commit f0779c2

Please sign in to comment.