Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(HeightAnimation): adjust height with animation when content changes #1569

Merged
merged 1 commit into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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