Skip to content

Commit

Permalink
fix(Accordion): replace internal animation with HeightAnimation compo…
Browse files Browse the repository at this point in the history
…nent
  • Loading branch information
tujoworker committed Oct 9, 2022
1 parent 24ece4a commit 60b3a04
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ These properties can send along with the `Accordion.Provider` or `Accordion.Grou
| `heading_level` | _(optional)_ if `heading` is set to `true`, you can provide a numeric value to define a different heading level. Defaults to `2`. |
| `disabled` | _(optional)_ if set to `true`, the accordion button will be disabled (dimmed). |
| `skeleton` | _(optional)_ if set to `true`, an overlaying skeleton with animation will be shown. |
| `contentRef` | _(optional)_ send along a custom React Ref for `.dnb-accordion__content`. |
| [Space](/uilib/components/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. |

| Accordion.Provider and Accordion.Group Properties | Description |
Expand Down
5 changes: 2 additions & 3 deletions packages/dnb-eufemia/src/components/accordion/Accordion.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ export default class Accordion extends React.PureComponent {
{(globalContext) => (
<AccordionContext.Consumer>
{(nestedContext) => {
const { no_animation } = this.state
let { expanded } = this.state

// use only the props from context, who are available here anyway
Expand Down Expand Up @@ -271,7 +270,7 @@ export default class Accordion extends React.PureComponent {
remember_state,
disabled,
skeleton,
no_animation: _no_animation, // eslint-disable-line
no_animation, // eslint-disable-line
expanded_ssr: _expanded_ssr, // eslint-disable-line
children,

Expand All @@ -289,6 +288,7 @@ export default class Accordion extends React.PureComponent {
on_state_update, // eslint-disable-line
custom_method, // eslint-disable-line
custom_element, // eslint-disable-line
contentRef, // eslint-disable-line

...rest
} = props
Expand All @@ -300,7 +300,6 @@ export default class Accordion extends React.PureComponent {
className: classnames(
'dnb-accordion',
expanded && 'dnb-accordion--expanded',
no_animation && 'dnb-accordion--no-animation',
variant && `dnb-accordion__variant--${variant}`,
isTrue(prerender) && 'dnb-accordion--prerender',
createSpacingClasses(props),
Expand Down
280 changes: 127 additions & 153 deletions packages/dnb-eufemia/src/components/accordion/AccordionContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,217 +5,191 @@

import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {
warn,
isTrue,
validateDOMAttributes,
processChildren,
getPreviousSibling,
} from '../../shared/component-helper'
import AnimateHeight from '../../shared/AnimateHeight'
import classnames from 'classnames'
import { useMediaQuery } from '../../shared'
import AccordionContext from './AccordionContext'
import {
spacingPropTypes,
createSpacingClasses,
} from '../space/SpacingHelper'
import HeightAnimation from '../height-animation/HeightAnimation'

export default class AccordionContent extends React.PureComponent {
static contextType = AccordionContext

static propTypes = {
instance: PropTypes.object,
...spacingPropTypes,
className: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
}

static defaultProps = {
instance: null,
className: null,
children: null,
}

static getContent(props) {
return processChildren(props)
}

constructor(props, context) {
super(props)
this._ref = React.createRef()

this.state = {
isInitial: !context.expanded,
isAnimating: false,
keepContentInDom: null,
}

this.anim = new AnimateHeight()
export default function AccordionContent(props) {
const context = React.useContext(AccordionContext)

this.anim.onStart(() => {
this.setState({
isAnimating: true,
})
})
const {
id,
expanded,
prerender,
prevent_rerender,
single_container,
disabled,
no_animation,
contentRef,
} = context

this.anim.onEnd(() => {
this.setState({
isAnimating: false,
})
const { className, children, instance, ...rest } = props

this.setState({
keepContentInDom: this.context.expanded,
})
})
let elementRef = React.useRef(null)
const cacheRef = React.useRef(null)

if (
props.instance &&
Object.prototype.hasOwnProperty.call(props.instance, 'current')
) {
props.instance.current = this
}
if (contentRef) {
elementRef = contentRef
}

componentDidMount() {
this.anim.setElement(
this._ref.current,
getPreviousSibling(
'dnb-accordion-group--single-container',
this._ref.current
)
)
}
const setContainerHeight = () => {
const { single_container } = context

componentWillUnmount() {
this.anim.remove()
}
if (single_container) {
const contentElem = elementRef.current
if (contentElem) {
try {
contentElem.style.height = ''

componentDidUpdate(prevProps) {
const { expanded, single_container } = this.context
if (expanded !== this.state._expanded) {
const isInitial = !expanded && this.state.isInitial
this.setState(
{
_expanded: expanded,
isInitial: false,
keepContentInDom: true,
},
() => {
if (expanded) {
this.anim.open({ animate: !isInitial })
} else {
this.anim.close({ animate: !isInitial })
const containerElement = getPreviousSibling(
'dnb-accordion-group--single-container',
contentElem
)

if (no_animation) {
containerElement.style.transitionDuration = '1ms'
}
}
)
}

if (
isTrue(single_container) &&
AccordionContent.getContent(prevProps) !==
AccordionContent.getContent(this.props)
) {
this.anim.setContainerHeight()
const minHeight =
(contentElem.offsetHeight + contentElem.offsetTop) / 16
containerElement.style.minHeight = `${minHeight}rem`
} catch (e) {
warn(e)
}
}
}
}

setContainerHeight() {
this.anim?.setContainerHeight()
}
const renderContent = () => {
const children = processChildren(props)

renderContent() {
const children = AccordionContent.getContent(this.props)
const {
expanded,
prerender,
prevent_rerender,
prevent_rerender_conditional,
} = this.context
} = context

let content = children

if (typeof content === 'string') {
content = <p className="dnb-p">{content}</p>
}

content =
expanded ||
prerender ||
this.state.keepContentInDom ||
this.state.isAnimating
? children
: null

if (isTrue(prevent_rerender)) {
/**
* Ensure we do not render, if it is not expanded
*/
if (!(expanded || prerender)) {
content = null
}

// update the cache if children is not the same anymore
if (
isTrue(prevent_rerender_conditional) &&
this._cache !== content
cacheRef.current !== content
) {
this._cache = content
cacheRef.current = content
}

if (this._cache) {
content = this._cache
if (cacheRef.current) {
content = cacheRef.current
} else {
this._cache = content
cacheRef.current = content
}
}

return content
}

render() {
const {
className,
instance, // eslint-disable-line
...rest
} = this.props
const { keepContentInDom, isAnimating } = this.state

const { id, expanded, disabled } = this.context

const content = this.renderContent()

const wrapperParams = {
className: classnames(
'dnb-accordion__content',
!expanded && !keepContentInDom && 'dnb-accordion__content--hidden',
isAnimating && 'dnb-accordion__content--is-animating',
className
),
...rest,
React.useEffect(() => {
if (expanded && isTrue(single_container)) {
setContainerHeight()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [children, expanded, single_container])

const innerParams = {
id: `${id}-content`,
role: 'region',
'aria-labelledby': `${id}-header`,
className: classnames(
'dnb-accordion__content__inner',
!expanded &&
!keepContentInDom &&
'dnb-accordion__content__inner--remove-content',
createSpacingClasses(rest)
),
React.useState(() => {
if (
instance &&
Object.prototype.hasOwnProperty.call(instance, 'current')
) {
instance.current = { setContainerHeight }
}
})

if (expanded) {
innerParams['aria-expanded'] = true
}
const isSmallScreen = useMediaQuery({
when: { max: 'small' },
})

if (!expanded || disabled) {
innerParams.disabled = true
innerParams['aria-hidden'] = true
}
const content = renderContent()

// to remove spacing props
validateDOMAttributes(this.props, wrapperParams)
validateDOMAttributes(null, innerParams)
const wrapperParams = {
className: classnames('dnb-accordion__content', className),
...rest,
}

const keepInDOM = prerender || prevent_rerender

const innerParams = {
id: `${id}-content`,
'aria-labelledby': `${id}-header`,
className: classnames(
'dnb-accordion__content__inner',
createSpacingClasses(rest)
),
}

if (expanded) {
innerParams['aria-expanded'] = true
}

return (
<div {...wrapperParams} ref={this._ref}>
<div {...innerParams}>{content}</div>
</div>
)
if (!expanded || disabled) {
innerParams.disabled = true
innerParams['aria-hidden'] = true
}

// to remove spacing props
validateDOMAttributes(props, wrapperParams)
validateDOMAttributes(null, innerParams)

const animate =
!no_animation && (single_container ? isSmallScreen : true)

return (
<HeightAnimation
{...wrapperParams}
open={expanded}
animate={animate}
keepInDOM={keepInDOM}
innerRef={elementRef}
>
<section {...innerParams}>{content}</section>
</HeightAnimation>
)
}

AccordionContent.propTypes = {
instance: PropTypes.object,
...spacingPropTypes,
className: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
}

AccordionContent.defaultProps = {
instance: null,
className: null,
children: null,
}
Loading

0 comments on commit 60b3a04

Please sign in to comment.