"use client"; import React, { useState, useEffect, useRef } from "react"; import classNames from "classnames"; interface FlapProps { value: string; animated?: boolean; final?: boolean; hinge?: boolean; children?: string; bottom?: boolean; half?: boolean; isHovered?: boolean; color?: string; // New color prop hoverDuration?: number; // Duration of hover animation in ms } export const Flap = React.memo( ({ value, animated, final, hinge, children, bottom, half, isHovered, color, hoverDuration = 300, // Default animation duration }) => { const displayValue = children || value; const [animating, setAnimating] = useState(false); const animationTimer = useRef(null); // Handle hover animation completion useEffect(() => { // Start animation when hovered if (isHovered && !animating && bottom && half) { setAnimating(true); } // If animation is in progress and mouse leaves, let it complete if (animating && !isHovered) { if (animationTimer.current) { clearTimeout(animationTimer.current); } // Set a timer to complete the animation cycle animationTimer.current = setTimeout(() => { setAnimating(false); }, hoverDuration); } // If mouse is still hovering after animation completes, keep animating if (animating && isHovered) { if (animationTimer.current) { clearTimeout(animationTimer.current); animationTimer.current = null; } } // Cleanup return () => { if (animationTimer.current) { clearTimeout(animationTimer.current); } }; }, [isHovered, animating, bottom, half, hoverDuration]); // Base flap classes const flapBaseClasses = `absolute h-full w-full origin-center z-20 rounded-sm`; // Top flap classes const topClasses = classNames( flapBaseClasses, "clip-path-[polygon(0_50%,100%_50%,100%_0,0_0)]", // clip-path for top "shadow-inner-top bg-gradient-to-b from-[rgba(255,255,255,0.03)] from-0% to-transparent to-60%", // 3D effect for top { "animate-flapDownTop z-20": animated && final, "rotate-x-50 opacity-40 z-20": animated && !final, } ); // Bottom flap classes const bottomClasses = classNames( flapBaseClasses, "clip-path-[polygon(0_100%,100%_100%,100%_50%,0_50%)]", // clip-path for bottom "shadow-inner-bottom bg-gradient-to-t from-[rgba(0,0,0,0.07)] from-0% to-transparent to-30%", // 3D effect for bottom "transition-transform duration-200", // Add smooth transition for all states { "animate-flapDownBottom z-20": animated && final, } ); const bottomHalfClasses = classNames( flapBaseClasses, "clip-path-[polygon(0_120%,100%_120%,100%_50%,0_50%)]", // clip-path for bottom "bg-gradient-to-t from-[rgba(0,0,0,0.07)] from-0% to-transparent to-30%", // 3D effect for bottom "shadow-outer-bottom" ); // Hinge classes const hingeClasses = classNames( "w-full absolute left-0 top-1/2 -translate-y-1/2 z-30 h-[0.02em] bg-black", "before:content-[''] before:absolute before:left-[20%] before:bg-black before:shadow-[0.5px_0_1px_rgba(0,0,0,0.15)]", "after:content-[''] after:absolute after:left-[80%] after:bg-black after:shadow-[0.5px_0_1px_rgba(0,0,0,0.15)]", { "sm:before:w-[2px] sm:before:h-[16px] sm:after:w-[2px] sm:after:h-[16px] sm:before:top-[-6px] sm:after:top-[-6px]": true, // Default size for larger screens "before:w-[1px] before:h-[8px] after:w-[1px] after:h-[8px] after:top-[-3px] before:top-[-3px]": true, // Smaller size for mobile view } ); // Base style including color const baseStyle = { backgroundColor: color || "#1a1a1a", // Use provided color or default color: color ? "#ffffff" : "#e1e1e1", // Use white text for colored backgrounds }; return ( <> {!bottom && (
{displayValue}
)} {hinge &&
} {bottom && !half ? (
{displayValue}
) : null} {bottom && half ? (
{displayValue}
) : null} ); } );