feat: add stagger animations to board list and board cards
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
Reference in New Issue
Block a user