123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { RotateCcw, ArrowUpRight } from 'lucide-react';
|
|
import { motion, type Transition } from 'motion/react';
|
|
|
|
const notifications = [
|
|
{
|
|
id: 1,
|
|
title: 'NPM Install Complete',
|
|
subtitle: '1,227 packages added!',
|
|
time: 'just now',
|
|
count: 2,
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Build Succeeded',
|
|
subtitle: 'Build finished in 12.34s',
|
|
time: '1m 11s',
|
|
},
|
|
{
|
|
id: 3,
|
|
title: 'Lint Passed',
|
|
subtitle: 'No problems found',
|
|
time: '5m',
|
|
},
|
|
];
|
|
|
|
const transition: Transition = {
|
|
type: 'spring',
|
|
stiffness: 300,
|
|
damping: 26,
|
|
};
|
|
|
|
const getCardVariants = (i: number) => ({
|
|
collapsed: {
|
|
marginTop: i === 0 ? 0 : -44,
|
|
scaleX: 1 - i * 0.05,
|
|
},
|
|
expanded: {
|
|
marginTop: i === 0 ? 0 : 4,
|
|
scaleX: 1,
|
|
},
|
|
});
|
|
|
|
const textSwitchTransition: Transition = {
|
|
duration: 0.22,
|
|
ease: 'easeInOut',
|
|
};
|
|
|
|
const notificationTextVariants = {
|
|
collapsed: { opacity: 1, y: 0, pointerEvents: 'auto' },
|
|
expanded: { opacity: 0, y: -16, pointerEvents: 'none' },
|
|
};
|
|
|
|
const viewAllTextVariants = {
|
|
collapsed: { opacity: 0, y: 16, pointerEvents: 'none' },
|
|
expanded: { opacity: 1, y: 0, pointerEvents: 'auto' },
|
|
};
|
|
|
|
function NotificationList() {
|
|
return (
|
|
<motion.div
|
|
className="bg-neutral-200 dark:bg-neutral-900 p-3 rounded-3xl w-xs space-y-3 shadow-md"
|
|
initial="collapsed"
|
|
whileHover="expanded"
|
|
>
|
|
<div>
|
|
{notifications.map((notification, i) => (
|
|
<motion.div
|
|
key={notification.id}
|
|
className="bg-neutral-100 dark:bg-neutral-800 rounded-xl px-4 py-2 shadow-sm hover:shadow-lg transition-shadow duration-200 relative"
|
|
variants={getCardVariants(i)}
|
|
transition={transition}
|
|
style={{
|
|
zIndex: notifications.length - i,
|
|
}}
|
|
>
|
|
<div className="flex justify-between items-center">
|
|
<h1 className="text-sm font-medium">{notification.title}</h1>
|
|
{notification.count && (
|
|
<div className="flex items-center text-xs gap-0.5 font-medium text-neutral-500 dark:text-neutral-300">
|
|
<RotateCcw className="size-3" />
|
|
<span>{notification.count}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="text-xs text-neutral-500 font-medium">
|
|
<span>{notification.time}</span>
|
|
•
|
|
<span>{notification.subtitle}</span>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<div className="size-5 rounded-full bg-neutral-400 text-white text-xs flex items-center justify-center font-medium">
|
|
{notifications.length}
|
|
</div>
|
|
<span className="grid">
|
|
<motion.span
|
|
className="text-sm font-medium text-neutral-600 dark:text-neutral-300 row-start-1 col-start-1"
|
|
variants={notificationTextVariants}
|
|
transition={textSwitchTransition}
|
|
>
|
|
Notifications
|
|
</motion.span>
|
|
<motion.span
|
|
className="text-sm font-medium text-neutral-600 dark:text-neutral-300 flex items-center gap-1 cursor-pointer select-none row-start-1 col-start-1"
|
|
variants={viewAllTextVariants}
|
|
transition={textSwitchTransition}
|
|
>
|
|
View all <ArrowUpRight className="size-4" />
|
|
</motion.span>
|
|
</span>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|
|
|
|
export { NotificationList };
|