Retour au catalogue

Contact 2 Columns

Contact en 2 colonnes equilibrees : infos a gauche, formulaire a droite.

contactmedium Both Responsive a11y
minimalcorporateagencysaasuniversalsplit
Theme
"use client";

import { useState } from "react";
import { motion } from "framer-motion";
import { Mail, Phone, MapPin, ArrowRight } from "lucide-react";

interface ContactInfo {
  icon: string;
  label: string;
  value: string;
  href: string;
}

interface FormField {
  name: string;
  label: string;
  type: string;
  placeholder?: string;
  options?: string[];
  required: boolean;
}

interface Contact2ColProps {
  badge?: string;
  title?: string;
  subtitle?: string;
  leftColumn?: {
    heading: string;
    text: string;
    contacts: ContactInfo[];
  };
  rightColumn?: {
    fields: FormField[];
    ctaLabel: string;
  };
}

const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];

const iconMap: Record<string, React.ReactNode> = {
  mail: <Mail size={16} />,
  phone: <Phone size={16} />,
  map: <MapPin size={16} />,
};

export default function Contact2Col({
  badge = "Contact",
  title = "Discutons ensemble",
  subtitle = "",
  leftColumn,
  rightColumn,
}: Contact2ColProps) {
  const [sent, setSent] = useState(false);

  return (
    <section
      className="py-20 lg:py-28"
      style={{ background: "var(--color-background)" }}
    >
      <div className="mx-auto max-w-6xl px-6">
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          whileInView={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.6, ease }}
          viewport={{ once: true }}
          className="text-center mb-16"
        >
          {badge && (
            <span
              className="inline-block mb-4 text-xs font-medium tracking-widest uppercase"
              style={{ color: "var(--color-accent)" }}
            >
              {badge}
            </span>
          )}
          <h2
            className="text-3xl md:text-4xl lg:text-5xl font-bold"
            style={{ color: "var(--color-foreground)" }}
          >
            {title}
          </h2>
          {subtitle && (
            <p
              className="mt-3 text-base"
              style={{ color: "var(--color-foreground-muted)" }}
            >
              {subtitle}
            </p>
          )}
        </motion.div>

        <div className="grid lg:grid-cols-2 gap-14 lg:gap-20">
          {/* Left */}
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.6, ease, delay: 0.05 }}
            viewport={{ once: true }}
          >
            {leftColumn && (
              <>
                <h3
                  className="text-xl font-semibold mb-4"
                  style={{ color: "var(--color-foreground)" }}
                >
                  {leftColumn.heading}
                </h3>
                <p
                  className="text-sm leading-relaxed mb-10"
                  style={{ color: "var(--color-foreground-muted)" }}
                >
                  {leftColumn.text}
                </p>
                <div className="flex flex-col gap-6">
                  {leftColumn.contacts.map((c) => (
                    <div key={c.label} className="flex items-start gap-4">
                      <div
                        className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0"
                        style={{
                          background: "var(--color-background-alt)",
                          color: "var(--color-accent)",
                        }}
                      >
                        {iconMap[c.icon] ?? <Mail size={16} />}
                      </div>
                      <div>
                        <p
                          className="text-xs font-medium tracking-wide uppercase mb-1"
                          style={{ color: "var(--color-foreground-muted)" }}
                        >
                          {c.label}
                        </p>
                        {c.href ? (
                          <a
                            href={c.href}
                            className="text-sm hover:opacity-70 transition-opacity"
                            style={{ color: "var(--color-foreground)" }}
                          >
                            {c.value}
                          </a>
                        ) : (
                          <p
                            className="text-sm"
                            style={{ color: "var(--color-foreground)" }}
                          >
                            {c.value}
                          </p>
                        )}
                      </div>
                    </div>
                  ))}
                </div>
              </>
            )}
          </motion.div>

          {/* Right / Form */}
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            whileInView={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.6, ease, delay: 0.15 }}
            viewport={{ once: true }}
          >
            {sent ? (
              <div
                className="rounded-xl p-10 text-center"
                style={{
                  background: "var(--color-background-alt)",
                  border: "1px solid var(--color-border)",
                }}
              >
                <p
                  className="text-xl font-semibold"
                  style={{ color: "var(--color-foreground)" }}
                >
                  Merci !
                </p>
                <p
                  className="mt-2 text-sm"
                  style={{ color: "var(--color-foreground-muted)" }}
                >
                  Nous reviendrons vers vous rapidement.
                </p>
              </div>
            ) : (
              <form
                onSubmit={(e) => {
                  e.preventDefault();
                  setSent(true);
                }}
                className="flex flex-col gap-5 rounded-xl p-8"
                style={{
                  background: "var(--color-background-alt)",
                  border: "1px solid var(--color-border)",
                }}
              >
                {rightColumn?.fields.map((f) => (
                  <div key={f.name}>
                    <label
                      className="block mb-1.5 text-xs font-medium tracking-wide uppercase"
                      style={{ color: "var(--color-foreground-muted)" }}
                    >
                      {f.label}{f.required ? " *" : ""}
                    </label>
                    {f.type === "textarea" ? (
                      <textarea
                        name={f.name}
                        required={f.required}
                        placeholder={f.placeholder}
                        rows={4}
                        className="w-full px-4 py-3 rounded-lg text-sm resize-none outline-none"
                        style={{
                          background: "var(--color-background)",
                          color: "var(--color-foreground)",
                          border: "1px solid var(--color-border)",
                        }}
                      />
                    ) : f.type === "select" ? (
                      <select
                        name={f.name}
                        required={f.required}
                        className="w-full px-4 py-3 rounded-lg text-sm outline-none"
                        style={{
                          background: "var(--color-background)",
                          color: "var(--color-foreground)",
                          border: "1px solid var(--color-border)",
                        }}
                      >
                        <option value="">Choisir...</option>
                        {f.options?.map((o) => (
                          <option key={o} value={o}>
                            {o}
                          </option>
                        ))}
                      </select>
                    ) : (
                      <input
                        name={f.name}
                        type={f.type}
                        required={f.required}
                        placeholder={f.placeholder}
                        className="w-full px-4 py-3 rounded-lg text-sm outline-none"
                        style={{
                          background: "var(--color-background)",
                          color: "var(--color-foreground)",
                          border: "1px solid var(--color-border)",
                        }}
                      />
                    )}
                  </div>
                ))}
                <button
                  type="submit"
                  className="flex items-center justify-center gap-2 w-full py-3 rounded-lg text-sm font-semibold transition-opacity hover:opacity-90 mt-2"
                  style={{
                    background: "var(--color-accent)",
                    color: "var(--color-background)",
                  }}
                >
                  {rightColumn?.ctaLabel ?? "Envoyer"}
                  <ArrowRight size={14} />
                </button>
              </form>
            )}
          </motion.div>
        </div>
      </div>
    </section>
  );
}

Avis

Contact 2 Columns — React Contact Section — Incubator