feat: add column stagger + card stagger + card hover/tap animations

This commit is contained in:
Your Name
2026-02-15 20:58:53 +02:00
parent 767bf4714b
commit 03a22d4e6a
3 changed files with 34 additions and 11 deletions

View File

@@ -1,5 +1,7 @@
import React, { useState, useRef, useEffect, useCallback } from "react"; import React, { useState, useRef, useEffect, useCallback } from "react";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { motion } from "framer-motion";
import { staggerContainer } from "@/lib/motion";
import { import {
DndContext, DndContext,
DragOverlay, DragOverlay,
@@ -320,7 +322,13 @@ export function BoardView() {
items={columnIds} items={columnIds}
strategy={horizontalListSortingStrategy} strategy={horizontalListSortingStrategy}
> >
<div className="flex h-full overflow-x-auto" style={{ gap: `calc(1.5rem * var(--density-factor))`, padding: `calc(1.5rem * var(--density-factor))`, ...getBoardBackground(board) }}> <motion.div
className="flex h-full overflow-x-auto"
style={{ gap: `calc(1.5rem * var(--density-factor))`, padding: `calc(1.5rem * var(--density-factor))`, ...getBoardBackground(board) }}
variants={staggerContainer(0.06)}
initial="hidden"
animate="visible"
>
{board.columns.map((column) => ( {board.columns.map((column) => (
<KanbanColumn <KanbanColumn
key={column.id} key={column.id}
@@ -375,7 +383,7 @@ export function BoardView() {
</Button> </Button>
)} )}
</div> </div>
</div> </motion.div>
</SortableContext> </SortableContext>
{/* Drag overlay - renders a styled copy of the dragged item */} {/* Drag overlay - renders a styled copy of the dragged item */}

View File

@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { format, isPast, isToday } from "date-fns"; import { format, isPast, isToday } from "date-fns";
import { motion, useReducedMotion } from "framer-motion"; import { motion, useReducedMotion } from "framer-motion";
import { fadeSlideUp, springs } from "@/lib/motion";
import { useSortable } from "@dnd-kit/sortable"; import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities"; import { CSS } from "@dnd-kit/utilities";
import type { Card, Label } from "@/types/board"; import type { Card, Label } from "@/types/board";
@@ -50,10 +51,14 @@ export function CardThumbnail({ card, boardLabels, columnId, onCardClick }: Card
ref={setNodeRef} ref={setNodeRef}
style={style} style={style}
onClick={handleClick} onClick={handleClick}
className="w-full rounded-lg bg-pylon-surface shadow-sm text-left transition-all duration-200 hover:-translate-y-px hover:shadow-md" className="w-full rounded-lg bg-pylon-surface shadow-sm text-left"
initial={prefersReducedMotion ? false : { opacity: 0, y: -8 }} variants={fadeSlideUp}
animate={{ opacity: 1, y: 0 }} initial={prefersReducedMotion ? false : "hidden"}
transition={{ type: "spring", stiffness: 300, damping: 25 }} animate="visible"
whileHover={{ scale: 1.02, y: -2, boxShadow: "0 4px 12px oklch(0% 0 0 / 10%)" }}
whileTap={{ scale: 0.98 }}
transition={springs.bouncy}
layout
{...attributes} {...attributes}
{...listeners} {...listeners}
role="article" role="article"

View File

@@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { motion, useReducedMotion } from "framer-motion"; import { motion, useReducedMotion } from "framer-motion";
import { fadeSlideUp, springs, staggerContainer } from "@/lib/motion";
import { useDroppable } from "@dnd-kit/core"; import { useDroppable } from "@dnd-kit/core";
import { import {
SortableContext, SortableContext,
@@ -68,9 +69,11 @@ export function KanbanColumn({ column, onCardClick }: KanbanColumnProps) {
style={style} style={style}
className="group/column flex shrink-0 flex-col rounded-lg bg-pylon-column" className="group/column flex shrink-0 flex-col rounded-lg bg-pylon-column"
aria-label={`${column.title} column, ${cardCount} card${cardCount !== 1 ? "s" : ""}`} aria-label={`${column.title} column, ${cardCount} card${cardCount !== 1 ? "s" : ""}`}
initial={prefersReducedMotion ? false : { opacity: 0, x: 20 }} variants={fadeSlideUp}
animate={{ opacity: 1, x: 0 }} initial={prefersReducedMotion ? false : "hidden"}
transition={{ type: "spring", stiffness: 300, damping: 25 }} animate="visible"
transition={springs.bouncy}
layout
{...attributes} {...attributes}
> >
{/* The column header is the drag handle for column reordering */} {/* The column header is the drag handle for column reordering */}
@@ -84,7 +87,14 @@ export function KanbanColumn({ column, onCardClick }: KanbanColumnProps) {
strategy={verticalListSortingStrategy} strategy={verticalListSortingStrategy}
> >
<ScrollArea className="flex-1 overflow-y-auto"> <ScrollArea className="flex-1 overflow-y-auto">
<ul ref={setDroppableNodeRef} className="flex min-h-[40px] list-none flex-col" style={{ gap: `calc(0.5rem * var(--density-factor))`, padding: `calc(0.5rem * var(--density-factor))` }}> <motion.ul
ref={setDroppableNodeRef}
className="flex min-h-[40px] list-none flex-col"
style={{ gap: `calc(0.5rem * var(--density-factor))`, padding: `calc(0.5rem * var(--density-factor))` }}
variants={staggerContainer(0.03)}
initial="hidden"
animate="visible"
>
{column.cardIds.map((cardId) => { {column.cardIds.map((cardId) => {
const card = board?.cards[cardId]; const card = board?.cards[cardId];
if (!card) return null; if (!card) return null;
@@ -104,7 +114,7 @@ export function KanbanColumn({ column, onCardClick }: KanbanColumnProps) {
Drop or add a card Drop or add a card
</li> </li>
)} )}
</ul> </motion.ul>
</ScrollArea> </ScrollArea>
</SortableContext> </SortableContext>