Update src/components/sections/about/AboutMetric.tsx

This commit is contained in:
2026-02-27 12:35:20 +00:00
parent 49b525cf30
commit 50c5ec0cf2

View File

@@ -1,106 +1,116 @@
"use client"; 'use client';
import TextAnimation from "@/components/text/TextAnimation"; import React, { useState } from 'react';
import { cls, shouldUseInvertedText } from "@/lib/utils"; import { LucideIcon } from 'lucide-react';
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
import { useButtonAnimation } from "@/components/hooks/useButtonAnimation";
import type { LucideIcon } from "lucide-react";
import type { ButtonAnimationType } from "@/types/button";
interface Metric { interface Metric {
icon: LucideIcon; icon: LucideIcon;
label: string; label: string;
value: string; value: string;
} }
interface AboutMetricProps { interface AboutMetricProps {
title: string; title: string;
metrics: Metric[]; metrics: Metric[];
metricsAnimation: ButtonAnimationType; metricsAnimation?: string;
useInvertedBackground: boolean; useInvertedBackground?: boolean;
ariaLabel?: string;
className?: string;
containerClassName?: string;
titleClassName?: string;
metricsContainerClassName?: string;
metricCardClassName?: string;
metricIconClassName?: string;
metricLabelClassName?: string;
metricValueClassName?: string;
} }
const AboutMetric = ({ export default function AboutMetric({
title, title,
metrics, metrics,
metricsAnimation, metricsAnimation = 'slide-up',
useInvertedBackground, useInvertedBackground = false,
ariaLabel = "About metrics section", }: AboutMetricProps) {
className = "", const [flipped, setFlipped] = useState<{ [key: number]: boolean }>({});
containerClassName = "",
titleClassName = "",
metricsContainerClassName = "",
metricCardClassName = "",
metricIconClassName = "",
metricLabelClassName = "",
metricValueClassName = "",
}: AboutMetricProps) => {
const theme = useTheme();
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
const { containerRef: metricsContainerRef } = useButtonAnimation({ animationType: metricsAnimation });
const gridColsMap = { const toggleFlip = (index: number) => {
2: "md:grid-cols-2", setFlipped((prev) => ({
3: "md:grid-cols-3", ...prev,
4: "md:grid-cols-4", [index]: !prev[index],
}; }));
const gridCols = gridColsMap[metrics.length as keyof typeof gridColsMap] || "md:grid-cols-4"; };
return ( const bgClass = useInvertedBackground ? 'bg-slate-900' : 'bg-white';
<section const textClass = useInvertedBackground ? 'text-white' : 'text-slate-900';
aria-label={ariaLabel}
className={cls("relative py-20 w-full", useInvertedBackground && "bg-foreground", className)}
>
<div className={cls("w-content-width mx-auto flex flex-col gap-8", containerClassName)}>
<TextAnimation
type={theme.defaultTextAnimation}
text={title}
variant="words-trigger"
className={cls("text-2xl md:text-5xl font-medium leading-[1.175]", useInvertedBackground && "text-background", titleClassName)}
/>
<div ref={metricsContainerRef} className={cls("grid grid-cols-1 gap-6", gridCols, metricsContainerClassName)}> const getAnimationDelay = (index: number) => {
{metrics.map((metric, index) => { if (metricsAnimation === 'slide-up') {
const Icon = metric.icon; return {
return ( transitionDelay: `${index * 100}ms`,
<div };
key={index} }
className={cls( return {};
"h-fit card rounded-theme-capped px-6 py-8 md:py-10 flex flex-col items-center justify-center gap-3", };
metricCardClassName
)} return (
> <section className={`${bgClass} py-16 px-4 sm:px-6 lg:px-8`}>
<div className="relative z-1 w-full flex items-center justify-center gap-2"> <div className="max-w-6xl mx-auto">
<div className={cls("h-8 primary-button aspect-square rounded-theme flex items-center justify-center", metricIconClassName)}> <div className="mb-12">
<Icon className="h-4/10 text-primary-cta-text" strokeWidth={1.5} /> <h2 className={`text-3xl sm:text-4xl font-bold ${textClass} text-center`}>
</div> {title}
<h3 className={cls("text-xl truncate", shouldUseLightText && "text-background", metricLabelClassName)}> </h2>
{metric.label} </div>
</h3>
</div> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
<div className="relative z-1 w-full flex items-center justify-center"> {metrics.map((metric, index) => {
<h4 className={cls("text-6xl font-medium truncate", shouldUseLightText && "text-background", metricValueClassName)}> const Icon = metric.icon;
{metric.value} const isFlipped = flipped[index] || false;
</h4>
</div> return (
</div> <div
); key={index}
})} className="h-64 cursor-pointer perspective"
onClick={() => toggleFlip(index)}
style={getAnimationDelay(index)}
>
<div
className="relative w-full h-full transition-transform duration-500 transform-gpu"
style={{
transformStyle: 'preserve-3d',
transform: isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)',
}}
>
{/* Front Side */}
<div
className={`absolute w-full h-full ${bgClass} rounded-lg shadow-lg p-6 flex flex-col items-center justify-center border border-slate-200`}
style={{
backfaceVisibility: 'hidden',
}}
>
<Icon className={`w-12 h-12 ${useInvertedBackground ? 'text-blue-400' : 'text-blue-600'} mb-4`} />
<p className={`text-sm font-semibold ${textClass} text-center mb-2`}>
{metric.label}
</p>
<p className={`text-3xl font-bold ${useInvertedBackground ? 'text-blue-400' : 'text-blue-600'}`}>
{metric.value}
</p>
<p className={`text-xs ${useInvertedBackground ? 'text-slate-400' : 'text-slate-500'} mt-4 text-center`}>
Click to learn more
</p>
</div>
{/* Back Side */}
<div
className={`absolute w-full h-full ${useInvertedBackground ? 'bg-slate-800' : 'bg-slate-100'} rounded-lg shadow-lg p-6 flex flex-col items-center justify-center border border-slate-200`}
style={{
backfaceVisibility: 'hidden',
transform: 'rotateY(180deg)',
}}
>
<p className={`text-sm ${textClass} text-center leading-relaxed`}>
{metric.label}: {metric.value} represents our commitment to excellence and continuous growth in this area.
</p>
<p className={`text-xs ${useInvertedBackground ? 'text-slate-400' : 'text-slate-500'} mt-4 text-center`}>
Click to flip back
</p>
</div>
</div> </div>
</div> </div>
</section> );
); })}
}; </div>
</div>
AboutMetric.displayName = "AboutMetric"; </section>
);
export default AboutMetric; }