Todos os componentes

Animated Notification List

Animated Notification List é um componente de dados para React e Tailwind CSS, da biblioteca Eldora UI, com licença MIT. Copia e cola no teu projeto.

Eldora UI MIT data

O que é

Animated Notification List é um componente de dados para React e Tailwind CSS, da biblioteca Eldora UI, com licença MIT. Copia e cola no teu projeto.

Como usar

Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.

Abrir no catálogo

Cinco cópias grátis por mês. Sem cartão.

Precisa de

npm i react

Licença

Este componente vem de Eldora UI e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.

Animated Notification List

registry/eldoraui/animated-list.tsx
"use client"

import React, { useEffect, useMemo, useState } from "react"
import { AnimatePresence, motion, useAnimationControls } from "motion/react"

import { cn } from "@/lib/utils"

type AnimationPhase = "idle" | "forming_column" | "scrolling_down" | "resetting"

type AnimatedListProps = {
  children: React.ReactNode
  className?: string
  stackGap?: number
  columnGap?: number
  scaleFactor?: number
  scrollDownDuration?: number
  formationDuration?: number
}

type AnimatedListItemProps = {
  children: React.ReactNode
  className?: string
  index: number
  listLength: number
  stackGap?: number
  columnGap?: number
  scaleFactor?: number
}

function InternalAnimatedListItem({
  children,
  className,
  index,
  listLength,
  animationPhase,
  onFormationComplete,
  stackGap = 10,
  columnGap = 100,
  scaleFactor = 0.1,
  formationDuration = 1,
  visibleItemsCount = 4,
  resetSpringStiffness = 120,
  resetSpringDamping = 20,
}: AnimatedListItemProps & {
  animationPhase: AnimationPhase
  onFormationComplete?: () => void
  formationDuration: number
  visibleItemsCount: number
  resetSpringStiffness: number
  resetSpringDamping: number
}) {
  const reverseIndex = listLength - 1 - index
  const isVisible = reverseIndex < visibleItemsCount
  const lastItemOffset = (listLength - 1) * columnGap
  const isLastItem = index === listLength - 1

  const itemVariants = {
    initial: {
      scale: 1 + index * scaleFactor,
      y: reverseIndex * stackGap,
      opacity: isVisible ? 1 : 0,
    },
    column: {
      scale: 1,
      y: index * columnGap - lastItemOffset,
      opacity: 1,
    },
  }

  const target =
    animationPhase === "idle" || animationPhase === "resetting"
      ? "initial"
      : "column"

  const getTransition = () => {
    if (animationPhase === "resetting") {
      return {
        type: "spring" as const,
        stiffness: resetSpringStiffness,
        damping: resetSpringDamping,
      }
    } else {
      return { duration: formationDuration, ease: [0.4, 0, 0.2, 1] as const }
    }
  }

  const handleAnimationComplete = (definition: string) => {
    if (
      isLastItem &&
      definition === "column" &&
      animationPhase === "forming_column"
    ) {
      onFormationComplete?.()
    }
  }

  return (
    <motion.div
      key={index}
      className={cn("absolute inset-x-0 flex w-full justify-center", className)}
      variants={itemVariants}
      initial="initial"
      animate={target}
      transition={getTransition()}
      onAnimationComplete={handleAnimationComplete}
    >
      {children}
    </motion.div>
  )
}

export function AnimatedList({
  children,
  className,
  stackGap = 20,
  columnGap = 85,
  scaleFactor = 0.05,
  scrollDownDuration = 5,
  formationDuration = 1,
}: AnimatedListProps) {
  const initialDelayValue = 500
  const loopPauseDurationValue = 100
  const listResetSpringStiffness = 100
  const listResetSpringDamping = 25
  const itemResetSpringStiffness = 120
  const itemResetSpringDamping = 20
  const visibleItemsCountValue = 4

  const [animationPhase, setAnimationPhase] = useState<AnimationPhase>("idle")
  const listControls = useAnimationControls()
  const childrenArray = useMemo(
    () => React.Children.toArray(children),
    [children]
  )
  const listLength = childrenArray.length
  const totalHeight = listLength * columnGap

  useEffect(() => {
    let timer: NodeJS.Timeout
    if (animationPhase === "idle") {
      timer = setTimeout(
        () => {
          setAnimationPhase("forming_column")
        },
        animationPhase === "idle" ? loopPauseDurationValue : initialDelayValue
      )
    }
    return () => clearTimeout(timer)
  }, [animationPhase, loopPauseDurationValue, initialDelayValue])

  const handleFormationComplete = () => {
    if (animationPhase === "forming_column") setAnimationPhase("scrolling_down")
  }
  const handleScrollDownComplete = () => {
    if (animationPhase === "scrolling_down") setAnimationPhase("resetting")
  }
  const handleScrollUpComplete = () => {
    if (animationPhase === "resetting") setAnimationPhase("idle")
  }

  useEffect(() => {
    if (animationPhase === "scrolling_down") {
      listControls.start({
        y: totalHeight,
        transition: {
          duration: scrollDownDuration,
          ease: [0.4, 0, 0.2, 1] as const,
        },
      })
    } else if (animationPhase === "resetting") {
      listControls.start({
        y: 0,
        transition: {
          type: "spring" as const,
          stiffness: listResetSpringStiffness,
          damping: listResetSpringDamping,
        },
      })
    } else {
      listControls.set({ y: 0 })
    }
  }, [
    animationPhase,
    listControls,
    totalHeight,
    scrollDownDuration,
    listResetSpringStiffness,
    listResetSpringDamping,
  ])

  const handleListAnimationComplete = (definition: { y?: number }) => {
    if (definition.y === totalHeight && animationPhase === "scrolling_down") {
      handleScrollDownComplete()
    } else if (definition.y === 0 && animationPhase === "resetting") {
      handleScrollUpComplete()
    }
  }

  return (
    <motion.div
      className={cn("relative flex h-full w-full items-center", className)}
      initial={{ y: 0 }}
      animate={listControls}
      onAnimationComplete={handleListAnimationComplete}
    >
      <AnimatePresence>
        {childrenArray.map((child, index) => (
          <InternalAnimatedListItem
            key={index}
            index={index}
            listLength={listLength}
            animationPhase={animationPhase}
            onFormationComplete={
              index === listLength - 1 ? handleFormationComplete : undefined
            }
            stackGap={stackGap}
            columnGap={columnGap}
            scaleFactor={scaleFactor}
            formationDuration={formationDuration}
            visibleItemsCount={visibleItemsCountValue}
            resetSpringStiffness={itemResetSpringStiffness}
            resetSpringDamping={itemResetSpringDamping}
          >
            {child}
          </InternalAnimatedListItem>
        ))}
      </AnimatePresence>
    </motion.div>
  )
}

Componentes parecidos