Animated Stack
paceui-gsap/animated-stackFreeComponent
A smooth stacked card component that expands on hover and collapses with animation.
Live preview
live · rendered by the registry
Rendered by PaceUI GSAP on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://gsap.paceui.com/r/animated-stack.jsonSource
1"use client";23import { Children, ReactNode, useCallback, useEffect, useRef, useState } from "react";45import { gsap } from "gsap";67type AnimatedStackProps = {8 children: ReactNode;9 visibleCount?: number;10 gap?: number;11 offset?: number;12 direction?: "up" | "down";13};1415export const AnimatedStack = ({16 children,17 visibleCount = 3,18 gap = 8,19 offset = 8,20 direction = "up",21}: AnimatedStackProps) => {22 const containerRef = useRef<HTMLDivElement>(null);23 const itemsRef = useRef<HTMLDivElement[]>([]);24 const prevItemCount = useRef(0);25 const timeoutRef = useRef<NodeJS.Timeout>(null);26 const lastItemHeight = useRef(0);27 const [expanded, setExpanded] = useState(false);2829 itemsRef.current = [];3031 const registerItemRef = (el: HTMLDivElement) => {32 if (el && !itemsRef.current.includes(el)) itemsRef.current.push(el);33 };3435 const calculatePositions = useCallback(36 (arr: number[]) =>37 arr38 .reduce((acc, val) => [...acc, acc.at(-1)! + val + gap], [0])39 .slice(0, -1)40 .reverse(),41 [gap],42 );4344 useEffect(() => {45 const items = itemsRef.current;46 const total = items.length;4748 const heights = items.map((el) => el.getBoundingClientRect().height);49 lastItemHeight.current = heights.at(-1) || 0;50 const positions = calculatePositions(heights);5152 items.forEach((el, i) => {53 const rev = total - 1 - i;54 let y = 0;55 let opacity = 1,56 scale = 1;5758 if (expanded) {59 y = (positions[i] || 0) * (direction == "down" ? 1 : -1);60 } else {61 if (rev >= visibleCount) {62 y = offset * (visibleCount - 1);63 opacity = 0;64 } else if (i !== total - 1) {65 y = rev * offset * (direction == "down" ? 1 : -1);66 scale = 1 - (total - i) * 0.015;67 }68 }6970 const isNew = total > prevItemCount.current && i === total - 1;7172 if (isNew) {73 gsap.fromTo(74 el,75 { opacity: 0, y: y + 20 * (direction == "down" ? -1 : 1) },76 { opacity: 1, y, duration: 0.8, ease: "power2.out" },77 );78 } else {79 gsap.to(el, { y, opacity, scaleX: scale, duration: 0.8, ease: "power4.inOut" });80 }81// … truncated
What it pulls in
npm packages
Files it writes
More from PaceUI GSAP
All 29 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Modal Ability Selectorai-modal-ability-selector | PaceUI GSAP | Components | Free | 2 deps | |
| Modal Selectorai-modal-selector | PaceUI GSAP | Components | Free | 2 deps | |
| Response Writerai-response-writer | PaceUI GSAP | Components | Free | 2 deps · 2 files | |
| AI Suggestionsai-suggestions | PaceUI GSAP | Components | Free | 2 deps | |
| Modal Ability Selectorai-token-counter | PaceUI GSAP | Components | Free | 2 deps | |
| Bouncing Textbouncing-text | PaceUI GSAP | Components | Free | 2 deps | |
| Dot Flowdot-flow | PaceUI GSAP | Components | Free | 2 deps | |
| Dot Loaderdot-loader | PaceUI GSAP | Components | Free | no deps |
