refactor: add animations

This commit is contained in:
2026-03-18 12:56:39 +05:30
parent c650da161e
commit f35389ab69
14 changed files with 480 additions and 206 deletions

View File

@@ -3,7 +3,7 @@ import * as React from "react";
import {
LayoutDashboard,
Plus,
FolderPlus,
Layers,
Package,
MapPin,
} from "lucide-react";
@@ -45,7 +45,7 @@ const data = {
{
title: "Project",
url: ROUTES.PROJECT,
icon: FolderPlus,
icon: Layers,
},
{
title: "Package",

View File

@@ -2,6 +2,7 @@
import { Card, CardContent } from "@/components/ui/card"
import { LucideIcon } from "lucide-react"
import { motion } from "motion/react"
interface StatsCardProps {
title: string
@@ -19,34 +20,41 @@ export function StatsCard({
isLoading = false
}: StatsCardProps) {
return (
<Card className="py-6 px-6 hover:scale-105 transition-all duration-300 ease-in-out">
<CardContent className="p-0 flex items-center justify-between gap-6">
<div className="flex flex-col gap-1 min-w-0">
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wider">
{title}
</h3>
<div className="flex flex-col">
{isLoading ? (
<div className="h-10 w-24 bg-muted rounded-md mt-2 animate-pulse" />
) : (
<>
<p className="text-3xl font-bold">
{value}
</p>
{subtitle && (
<p className="text-xs text-muted-foreground mt-1">
{subtitle}
<motion.div
whileHover={{ y: -4, scale: 1.02 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
className="h-full"
>
<Card className="h-full py-6 px-4 transition-colors cursor-pointer">
<CardContent className="p-0 flex items-center justify-between gap-6">
<div className="flex flex-col gap-1 min-w-0">
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wider">
{title}
</h3>
<div className="flex flex-col">
{isLoading ? (
<div className="h-10 w-24 bg-muted rounded-md mt-2 animate-pulse" />
) : (
<>
<p className="text-3xl font-bold tracking-tight">
{value}
</p>
)}
</>
)}
{subtitle && (
<p className="text-xs text-muted-foreground mt-1 line-clamp-1">
{subtitle}
</p>
)}
</>
)}
</div>
</div>
</div>
<div className="p-3 rounded-md bg-secondary text-secondary-foreground">
<Icon className="h-6 w-6" />
</div>
</CardContent>
</Card>
<div className="p-3 rounded-xl bg-primary/10 text-primary shrink-0 group-hover:bg-primary group-hover:text-primary-foreground transition-colors">
<Icon className="h-6 w-6" />
</div>
</CardContent>
</Card>
</motion.div>
)
}

View File

@@ -1,6 +1,7 @@
"use client"
import { LucideIcon } from "lucide-react"
import { Reveal } from "@/components/ui/reveal"
interface PageHeaderProps {
title: string
@@ -12,26 +13,29 @@ interface PageHeaderProps {
export function PageHeader({ title, description, icon: Icon, children, actions }: PageHeaderProps) {
return (
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-6">
{/* <div className="p-3.5 rounded-xl bg-primary text-primary-foreground flex items-center justify-center shadow-lg shadow-primary/5 ring-1 ring-white/10">
{Icon && <Icon className="h-7 w-7" />}
{children}
</div> */}
<div className="flex flex-col">
<h1 className="text-3xl font-extrabold tracking-tight ">
{title}
</h1>
<p className="text-muted-foreground mt-1 text-sm font-semibold tracking-wide opacity-80">
{description}
</p>
<Reveal direction="down" className="w-full">
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-6">
{/* <div className="p-3.5 rounded-xl bg-primary text-primary-foreground flex items-center justify-center shadow-lg shadow-primary/5 ring-1 ring-white/10">
{Icon && <Icon className="h-7 w-7" />}
{children}
</div> */}
<div className="flex flex-col">
<h1 className="text-3xl font-extrabold tracking-tight ">
{title}
</h1>
<p className="text-muted-foreground mt-1 text-sm font-semibold tracking-wide opacity-80">
{description}
</p>
</div>
</div>
{actions && (
<div className="flex items-center gap-4">
{actions}
</div>
)}
</div>
{actions && (
<div className="flex items-center gap-4">
{actions}
</div>
)}
</div>
</Reveal>
)
}

View File

@@ -47,7 +47,7 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
setLoadingProjects(true)
setError(null)
const data = await fetchProjects()
setProjects(data)
setProjects(data.items)
} catch (err) {
console.error("Failed to load projects:", err)
setError("Failed to load projects. Please check if the backend is running.")
@@ -74,7 +74,7 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
setSelectedLocation(null)
setLocations([])
const data = await fetchPackagesByProject(selectedProject.id)
setPackages(data)
setPackages(data.items)
} catch (err) {
console.error("Failed to load packages:", err)
setError("Failed to load packages for the selected project.")
@@ -99,7 +99,7 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
setError(null)
setSelectedLocation(null)
const data = await fetchLocationsByPackage(selectedPackage.id)
setLocations(data)
setLocations(data.items)
} catch (err) {
console.error("Failed to load locations:", err)
setError("Failed to load locations for the selected package.")

View File

@@ -0,0 +1,122 @@
"use client";
import { motion, type HTMLMotionProps } from "motion/react";
import React from "react";
interface RevealProps extends HTMLMotionProps<"div"> {
children: React.ReactNode;
delay?: number;
direction?: "up" | "down" | "left" | "right" | "none";
duration?: number;
distance?: number;
}
export function Reveal({
children,
delay = 0,
direction = "up",
duration = 0.5,
distance = 20,
className,
...props
}: RevealProps) {
const offsets = {
up: { y: distance },
down: { y: -distance },
left: { x: distance },
right: { x: -distance },
none: {},
};
return (
<motion.div
initial={{
opacity: 0,
...(direction !== "none" ? offsets[direction] : {})
}}
whileInView={{ opacity: 1, x: 0, y: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{
duration,
delay,
ease: [0.21, 0.47, 0.32, 0.98],
}}
className={className}
{...props}
>
{children}
</motion.div>
);
}
interface StaggerContainerProps extends HTMLMotionProps<"div"> {
children: React.ReactNode;
staggerChildren?: number;
delayChildren?: number;
}
export function StaggerContainer({
children,
staggerChildren = 0.1,
delayChildren = 0,
className,
...props
}: StaggerContainerProps) {
return (
<motion.div
initial="hidden"
whileInView="show"
viewport={{ once: true, margin: "-50px" }}
variants={{
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren,
delayChildren,
},
},
}}
className={className}
{...props}
>
{children}
</motion.div>
);
}
export function StaggerItem({
children,
direction = "up",
distance = 20,
className,
...props
}: RevealProps) {
const offsets = {
up: { y: distance },
down: { y: -distance },
left: { x: distance },
right: { x: -distance },
none: {},
};
return (
<motion.div
variants={{
hidden: {
opacity: 0,
...(direction !== "none" ? offsets[direction] : {})
},
show: { opacity: 1, x: 0, y: 0 },
}}
transition={{
duration: 0.5,
ease: [0.21, 0.47, 0.32, 0.98],
}}
className={className}
{...props}
>
{children}
</motion.div>
);
}