feat: UI interaction polish — exit animations, hover effects, directional transitions
- Add AnimatedPanel component with CSS transition-based enter/exit for Profile popover and Notification panel (140ms scale+fade) - Add nav-activate-pulse animation for floating-nav active indicator (320ms glow) - Add tool-panel-fade-in crossfade when switching ecommerce tools - Add carousel-card-label slide-up-in 260ms on active carousel card - Add feature-visual img hover scale(1.03)+brightness, experience-route hover translateY(-2px) - Add community-case-card--mosaic hover scale(1.02)+shadow lift - Add directional PageTransition: forward→slideX(20px), backward→slideX(-20px) - Move vite proxy target from hardcoded IP to VITE_DEV_PROXY env variable - Add .env.example for developer onboarding Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
|
||||
interface AnimatedPanelProps {
|
||||
open: boolean;
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
/** Duration in ms for the exit animation before unmounting. */
|
||||
exitDuration?: number;
|
||||
}
|
||||
|
||||
export function AnimatedPanel({ open, children, className, exitDuration = 140 }: AnimatedPanelProps) {
|
||||
const [mounted, setMounted] = useState(open);
|
||||
const [visible, setVisible] = useState(open);
|
||||
const timerRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (timerRef.current) {
|
||||
window.clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
setMounted(true);
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
setVisible(true);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setVisible(false);
|
||||
timerRef.current = window.setTimeout(() => {
|
||||
setMounted(false);
|
||||
timerRef.current = null;
|
||||
}, exitDuration);
|
||||
}
|
||||
}, [open, exitDuration]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timerRef.current) {
|
||||
window.clearTimeout(timerRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${className ?? ""} animated-panel${visible ? " is-visible" : ""}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user