Retour au catalogue

Features Animated

Features avec animations avancees : compteurs, barres de progression, hover effects.

featurescomplex Both Responsive a11y
boldplayfuldarkuniversalsaasgrid
Theme
"use client";

import React from "react";
import { motion, useInView } from "framer-motion";
import * as LucideIcons from "lucide-react";

interface FeatureItem {
  id: string;
  title: string;
  description: string;
  icon?: string;
  metric?: string;
  progress?: number;
}

interface FeaturesAnimatedProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  features: FeatureItem[];
}

function getIcon(name?: string) {
  if (!name) return null;
  return (LucideIcons as unknown as Record<string, React.ElementType>)[name] || null;
}

function ProgressBar({ value }: { value: number }) {
  const ref = React.useRef<HTMLDivElement>(null);
  const isInView = useInView(ref, { once: true, margin: "-40px" });

  return (
    <div
      ref={ref}
      className="h-1.5 w-full rounded-full overflow-hidden"
      style={{ backgroundColor: "var(--color-border)" }}
    >
      <motion.div
        className="h-full rounded-full"
        style={{ backgroundColor: "var(--color-accent)" }}
        initial={{ width: 0 }}
        animate={isInView ? { width: `${value}%` } : { width: 0 }}
        transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1], delay: 0.2 }}
      />
    </div>
  );
}

export default function FeaturesAnimated({
  badge,
  title,
  subtitle,
  features,
}: FeaturesAnimatedProps) {
  return (
    <section
      className="py-[var(--section-padding-y,6rem)]"
      style={{ backgroundColor: "var(--color-background-dark)" }}
    >
      <div className="mx-auto max-w-6xl px-[var(--container-padding-x,1.5rem)]">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, margin: "-80px" }}
          transition={{ duration: 0.5 }}
          className="text-center max-w-2xl mx-auto"
        >
          {badge && (
            <span
              className="inline-block text-xs font-medium tracking-wider uppercase px-3 py-1 rounded-full border"
              style={{
                color: "var(--color-accent)",
                borderColor: "var(--color-border-dark)",
              }}
            >
              {badge}
            </span>
          )}
          {title && (
            <h2
              className="mt-4 text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl"
              style={{ color: "var(--color-foreground-on-dark)" }}
            >
              {title}
            </h2>
          )}
          {subtitle && (
            <p className="mt-4 text-base" style={{ color: "var(--color-foreground-light)" }}>
              {subtitle}
            </p>
          )}
        </motion.div>

        <div className="mt-16 grid grid-cols-1 gap-6 sm:grid-cols-2">
          {features.map((feature, i) => {
            const Icon = getIcon(feature.icon);
            return (
              <motion.div
                key={feature.id}
                initial={{ opacity: 0, y: 24 }}
                whileInView={{ opacity: 1, y: 0 }}
                viewport={{ once: true, margin: "-60px" }}
                transition={{ delay: i * 0.1, duration: 0.5, ease: [0.16, 1, 0.3, 1] }}
                className="group rounded-[var(--radius-xl,1.5rem)] border p-6 transition-all duration-300 hover:border-opacity-60"
                style={{
                  borderColor: "var(--color-border-dark)",
                  backgroundColor: "color-mix(in srgb, var(--color-background-dark) 80%, var(--color-background-card))",
                }}
              >
                <div className="flex items-start justify-between">
                  <div className="flex items-center gap-3">
                    {Icon && (
                      <Icon
                        className="h-5 w-5"
                        style={{ color: "var(--color-accent)" }}
                      />
                    )}
                    <div>
                      <h3
                        className="text-base font-semibold"
                        style={{ color: "var(--color-foreground-on-dark)" }}
                      >
                        {feature.title}
                      </h3>
                      <p
                        className="text-xs mt-0.5"
                        style={{ color: "var(--color-foreground-light)" }}
                      >
                        {feature.description}
                      </p>
                    </div>
                  </div>
                  {feature.metric && (
                    <span
                      className="text-2xl font-bold tracking-tight"
                      style={{ color: "var(--color-accent)" }}
                    >
                      {feature.metric}
                    </span>
                  )}
                </div>
                {feature.progress !== undefined && (
                  <div className="mt-5">
                    <ProgressBar value={feature.progress} />
                  </div>
                )}
              </motion.div>
            );
          })}
        </div>
      </div>
    </section>
  );
}

Avis

Features Animated — React Features Section — Incubator