chore: add better documentation to a complicated hook (#6689)

* chore: add better documentation to a complicated hook

* input from eddie

* Update src/hooks/useUnmountingAnimation.ts

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>

* remove jsdoc types

---------

Co-authored-by: Zach Pomerantz <zzmp@uniswap.org>
This commit is contained in:
Jordan Frankfurt 2023-06-05 20:34:34 -05:00 committed by GitHub
parent f834af69fe
commit 1cdddd1321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,9 +1,29 @@
import { RefObject, useEffect } from 'react' import { RefObject, useEffect } from 'react'
function isAnimating(node?: Animatable | Document) { /**
* Checks whether a given node is currently animating.
*
* @param node - The node to check for ongoing animations.
* @returns - true if the node is animating; false otherwise.
*/
function isAnimating(node?: Animatable | Document): boolean {
return (node?.getAnimations?.().length ?? 0) > 0 return (node?.getAnimations?.().length ?? 0) > 0
} }
/**
* This hook runs an unmounting animation on a specified node.
*
* The hook will also run the animation on any additional elements specified in
* the `animatedElements` parameter. If no additional elements are specified,
* the animation will only run on the provided node.
*
* After any of the animated elements have completed their animation, `node` is removed from its parent.
*
* @param node - The node to animate and remove.
* @param getAnimatingClass - A function that returns the CSS class to add to the animating elements.
* @param animatedElements - Additional elements to animate.
* @param skip - Whether to skip the animation and remove the node immediately.
*/
export function useUnmountingAnimation( export function useUnmountingAnimation(
node: RefObject<HTMLElement>, node: RefObject<HTMLElement>,
getAnimatingClass: () => string, getAnimatingClass: () => string,
@ -12,16 +32,26 @@ export function useUnmountingAnimation(
) { ) {
useEffect(() => { useEffect(() => {
const current = node.current const current = node.current
// Gather all elements to animate, defaulting to the current node if none are specified.
const animated = animatedElements?.map((element) => element.current) ?? [current] const animated = animatedElements?.map((element) => element.current) ?? [current]
const parent = current?.parentElement const parent = current?.parentElement
const removeChild = parent?.removeChild const removeChild = parent?.removeChild
// If we can't remove the child or skipping is requested, stop here.
if (!(parent && removeChild) || skip) return if (!(parent && removeChild) || skip) return
// Override the parent's removeChild function to add our animation logic
parent.removeChild = function <T extends Node>(child: T) { parent.removeChild = function <T extends Node>(child: T) {
// If the current child is the one being removed and it's supposed to animate
if ((child as Node) === current && animated) { if ((child as Node) === current && animated) {
// Add animation class to all elements
animated.forEach((element) => element?.classList.add(getAnimatingClass())) animated.forEach((element) => element?.classList.add(getAnimatingClass()))
// Check if any of the animated elements is animating
const animating = animated.find((element) => isAnimating(element ?? undefined)) const animating = animated.find((element) => isAnimating(element ?? undefined))
if (animating) { if (animating) {
// If an element is animating, we wait for the animation to end before removing the child
animating?.addEventListener('animationend', (x) => { animating?.addEventListener('animationend', (x) => {
// This check is needed because the animationend event will fire for all animations on the // This check is needed because the animationend event will fire for all animations on the
// element or its children. // element or its children.
@ -30,13 +60,18 @@ export function useUnmountingAnimation(
} }
}) })
} else { } else {
// If no element is animating, we remove the child immediately
removeChild.call(parent, child) removeChild.call(parent, child)
} }
// We've handled the removal, so we return the child
return child return child
} else { } else {
// If the child isn't the one we're supposed to animate, remove it normally
return removeChild.call(parent, child) as T return removeChild.call(parent, child) as T
} }
} }
// Reset the removeChild function to its original value when the component is unmounted
return () => { return () => {
parent.removeChild = removeChild parent.removeChild = removeChild
} }