f1f1be21c7
Celebration micro-interactions: - SuccessToast: auto-dismissing pill toast (success/info/warning variants) - ConfettiBurst: pure CSS 20-particle confetti on project creation - Project wizard: confetti + toast on successful creation - Vacation approval/rejection: contextual toasts - Allocation status change: success toast - Button: active:scale-[0.97] press feedback on all variants Collapsible sidebar + responsive: - Desktop: toggle collapse (72px icons-only mode) with localStorage persistence - NavTooltip: hover labels on collapsed icons - Mobile: hamburger menu + slide-in overlay with backdrop - Auto-close sidebar on mobile navigation - Scroll-to-top on route change (smooth behavior) Hover polish + accessibility: - Table rows: animated left-border accent + hover-lift - Stat cards + widgets: hover elevation + border glow - Timeline blocks: scale(1.02) + shadow-md on hover - Smooth scroll globally with prefers-reduced-motion fallback - Filter chips: framer-motion scale+fade enter/exit - Dropdowns: scaleY origin-top reveal animation - Preferences modal: scale+fade entrance - Link underline: animated ::after width expansion on hover Co-Authored-By: claude-flow <ruv@ruv.net>
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "framer-motion";
|
|
|
|
interface WidgetContainerProps {
|
|
title: string;
|
|
onRemove: () => void;
|
|
children: React.ReactNode;
|
|
isDragging?: boolean;
|
|
}
|
|
|
|
export function WidgetContainer({ title, onRemove, children, isDragging }: WidgetContainerProps) {
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 16 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.35, ease: "easeOut" }}
|
|
className={`flex flex-col h-full bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden transition-colors duration-200 ${
|
|
isDragging ? "shadow-lg border-brand-300" : "hover:border-brand-200 dark:hover:border-brand-800"
|
|
}`}
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between px-4 py-2.5 border-b border-gray-100 bg-gray-50/50 shrink-0 cursor-grab active:cursor-grabbing widget-drag-handle">
|
|
<span className="text-sm font-semibold text-gray-700 truncate">{title}</span>
|
|
<button
|
|
type="button"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
onRemove();
|
|
}}
|
|
className="ml-2 p-1 text-gray-400 hover:text-gray-700 hover:bg-gray-100 rounded transition-colors shrink-0"
|
|
title="Remove widget"
|
|
>
|
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Body */}
|
|
<div className="flex-1 overflow-auto p-4">{children}</div>
|
|
</motion.div>
|
|
);
|
|
}
|