Draggable Stack
rivelle/draggable-stackFreeComponent
A spring-driven stack with threshold-based card promotion.
Install it
npx shadcn@latest add https://rivelle.dev/r/draggable-stack.jsonSource
1"use client";23import * as React from "react";4import { motion, useReducedMotion } from "motion/react";56import { cn } from "@/lib/utils";78type DraggableStackProps = React.ComponentProps<"div"> & {9 cardClassName?: string;10 onOrderChange?: (order: number[]) => void;11 threshold?: number;12};1314function DraggableStack({15 cardClassName,16 children,17 className,18 onOrderChange,19 threshold = 110,20 ...props21}: DraggableStackProps) {22 const cards = React.Children.toArray(children);23 const [order, setOrder] = React.useState(() =>24 cards.map((_, index) => index),25 );26 const reducedMotion = useReducedMotion();2728 React.useEffect(() => {29 setOrder(cards.map((_, index) => index));30 }, [cards.length]);3132 const sendToBack = () => {33 setOrder((current) => {34 const next = [...current.slice(1), current[0]];35 onOrderChange?.(next);36 return next;37 });38 };3940 return (41 <div42 className={cn("relative isolate min-h-80 w-full", className)}43 data-slot="draggable-stack"44 {...props}45 >46 {[...order].reverse().map((cardIndex, reverseIndex) => {47 const depth = order.length - 1 - reverseIndex;48 const isTop = depth === 0;49 return (50 <motion.div51 animate={{52 rotate: depth % 2 === 0 ? depth * -1.5 : depth * 1.5,53 scale: 1 - depth * 0.035,54 y: depth * 12,55 }}56 className={cn(57 "absolute inset-x-0 top-0 mx-auto w-[min(82%,340px)] cursor-grab touch-none overflow-hidden rounded-[1.75rem] border border-foreground/10 bg-card shadow-[0_28px_80px_-45px_var(--foreground)] active:cursor-grabbing",58 !isTop && "pointer-events-none",59 cardClassName,60 )}61 drag={isTop && !reducedMotion}62 dragConstraints={{ bottom: 150, left: -170, right: 170, top: -150 }}63 dragElastic={0.18}64 key={cardIndex}65 onDragEnd={(_, info) => {66 if (Math.hypot(info.offset.x, info.offset.y) >= threshold) {67 sendToBack();68 }69 }}70 style={{ zIndex: order.length - depth }}71 transition={{ damping: 24, stiffness: 280, type: "spring" }}72 whileDrag={{ rotate: 0, scale: 1.04, zIndex: order.length + 1 }}73 >74 {cards[cardIndex]}75 </motion.div>76 );77 })}78 </div>79 );80}8182export { DraggableStack };83export type { DraggableStackProps };
What it pulls in
npm packages
Other registry items
Files it writes
More from rivelle
All 83 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | rivelle | Components | Free | 2 deps | |
| Alertalert | rivelle | Components | Free | 1 dep | |
| Alert Dialogalert-dialog | rivelle | Components | Free | 1 dep | |
| Aspect Ratioaspect-ratio | rivelle | Components | Free | 1 dep | |
| Avataravatar | rivelle | Components | Free | 1 dep | |
| Badgebadge | rivelle | Components | Free | 2 deps | |
| Breadcrumbbreadcrumb | rivelle | Components | Free | 2 deps | |
| Buttonbutton | rivelle | Components | Free | 2 deps |
