feat: add stagger animations to board list and board cards

This commit is contained in:
Your Name
2026-02-15 20:58:03 +02:00
parent 0c5a23ad9b
commit 767bf4714b
2 changed files with 27 additions and 11 deletions

View File

@@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { formatDistanceToNow } from "date-fns"; import { formatDistanceToNow } from "date-fns";
import { motion, useReducedMotion } from "framer-motion"; import { motion, useReducedMotion } from "framer-motion";
import { fadeSlideUp, springs, subtleHover } from "@/lib/motion";
import { Trash2, Copy } from "lucide-react"; import { Trash2, Copy } from "lucide-react";
import { import {
ContextMenu, ContextMenu,
@@ -26,10 +27,9 @@ import { deleteBoard, loadBoard, saveBoard } from "@/lib/storage";
interface BoardCardProps { interface BoardCardProps {
board: BoardMeta; board: BoardMeta;
index?: number;
} }
export function BoardCard({ board, index = 0 }: BoardCardProps) { export function BoardCard({ board }: BoardCardProps) {
const [confirmDelete, setConfirmDelete] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false);
const prefersReducedMotion = useReducedMotion(); const prefersReducedMotion = useReducedMotion();
const addToast = useToastStore((s) => s.addToast); const addToast = useToastStore((s) => s.addToast);
@@ -74,9 +74,12 @@ export function BoardCard({ board, index = 0 }: BoardCardProps) {
return ( return (
<motion.div <motion.div
initial={prefersReducedMotion ? false : { opacity: 0, y: 10 }} variants={fadeSlideUp}
animate={{ opacity: 1, y: 0 }} initial={prefersReducedMotion ? false : "hidden"}
transition={{ type: "spring", stiffness: 300, damping: 25, delay: index * 0.05 }} animate="visible"
transition={springs.bouncy}
whileHover={subtleHover.hover}
whileTap={subtleHover.tap}
> >
<ContextMenu> <ContextMenu>
<ContextMenuTrigger asChild> <ContextMenuTrigger asChild>

View File

@@ -1,5 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { motion } from "framer-motion";
import { staggerContainer, scaleIn, springs } from "@/lib/motion";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useAppStore } from "@/stores/app-store"; import { useAppStore } from "@/stores/app-store";
import { BoardCard } from "@/components/boards/BoardCard"; import { BoardCard } from "@/components/boards/BoardCard";
@@ -24,7 +26,13 @@ export function BoardList() {
if (boards.length === 0) { if (boards.length === 0) {
return ( return (
<> <>
<div className="flex h-full flex-col items-center justify-center gap-6"> <motion.div
className="flex h-full flex-col items-center justify-center gap-6"
variants={scaleIn}
initial="hidden"
animate="visible"
transition={springs.gentle}
>
<div className="text-center"> <div className="text-center">
<h2 className="font-heading text-2xl text-pylon-text"> <h2 className="font-heading text-2xl text-pylon-text">
Welcome to OpenPylon Welcome to OpenPylon
@@ -41,7 +49,7 @@ export function BoardList() {
</Button> </Button>
<ImportExportButtons /> <ImportExportButtons />
</div> </div>
</div> </motion.div>
<NewBoardDialog open={dialogOpen} onOpenChange={setDialogOpen} /> <NewBoardDialog open={dialogOpen} onOpenChange={setDialogOpen} />
</> </>
); );
@@ -65,11 +73,16 @@ export function BoardList() {
</div> </div>
{/* Board grid */} {/* Board grid */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> <motion.div
{boards.map((board, index) => ( className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
<BoardCard key={board.id} board={board} index={index} /> variants={staggerContainer(0.05)}
initial="hidden"
animate="visible"
>
{boards.map((board) => (
<BoardCard key={board.id} board={board} />
))} ))}
</div> </motion.div>
</div> </div>
<NewBoardDialog open={dialogOpen} onOpenChange={setDialogOpen} /> <NewBoardDialog open={dialogOpen} onOpenChange={setDialogOpen} />