Retour au catalogue

Integration Chat Widget

Widget de chat flottant avec bulles de conversation, input et bouton FAB.

integrationsmedium Both Responsive a11y
playfulminimalsaasecommerceuniversalstacked
Theme
"use client";

import { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { MessageCircle, X, Send } from "lucide-react";

interface ChatMessage {
  sender: "user" | "bot";
  text: string;
  time: string;
}

interface IntegrationChatWidgetProps {
  title?: string;
  subtitle?: string;
  messages?: ChatMessage[];
  placeholder?: string;
}

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

export default function IntegrationChatWidget({
  title = "Support",
  subtitle = "Nous repondons en quelques minutes",
  messages: initialMessages = [],
  placeholder = "Ecrivez votre message...",
}: IntegrationChatWidgetProps) {
  const [isOpen, setIsOpen] = useState(true);
  const [messages, setMessages] = useState(initialMessages);
  const [input, setInput] = useState("");

  const send = () => {
    if (!input.trim()) return;
    setMessages([...messages, { sender: "user", text: input, time: "A l'instant" }]);
    setInput("");
  };

  return (
    <div className="relative min-h-[500px] flex items-end justify-end p-6" style={{ background: "var(--color-background)" }}>
      <AnimatePresence>
        {isOpen && (
          <motion.div
            initial={{ opacity: 0, y: 20, scale: 0.95 }}
            animate={{ opacity: 1, y: 0, scale: 1 }}
            exit={{ opacity: 0, y: 20, scale: 0.95 }}
            transition={{ duration: 0.25, ease }}
            className="w-80 rounded-2xl overflow-hidden shadow-xl flex flex-col"
            style={{ background: "var(--color-background-card)", border: "1px solid var(--color-border)", maxHeight: 440 }}
          >
            <div className="px-4 py-3 flex items-center justify-between" style={{ background: "var(--color-accent)" }}>
              <div>
                <h4 className="text-sm font-semibold" style={{ color: "var(--color-background)" }}>{title}</h4>
                <p className="text-[10px]" style={{ color: "var(--color-background)", opacity: 0.8 }}>{subtitle}</p>
              </div>
              <button onClick={() => setIsOpen(false)}>
                <X size={16} style={{ color: "var(--color-background)" }} />
              </button>
            </div>

            <div className="flex-1 overflow-y-auto px-4 py-3 flex flex-col gap-3 min-h-[200px]">
              {messages.map((msg, i) => (
                <motion.div
                  key={i}
                  initial={{ opacity: 0, y: 8 }}
                  animate={{ opacity: 1, y: 0 }}
                  transition={{ delay: i * 0.05 }}
                  className={`flex ${msg.sender === "user" ? "justify-end" : "justify-start"}`}
                >
                  <div
                    className="max-w-[80%] px-3 py-2 rounded-xl text-sm"
                    style={{
                      background: msg.sender === "user" ? "var(--color-accent)" : "var(--color-background-alt)",
                      color: msg.sender === "user" ? "var(--color-background)" : "var(--color-foreground)",
                    }}
                  >
                    {msg.text}
                    <span className="block text-[9px] mt-1" style={{ opacity: 0.6 }}>{msg.time}</span>
                  </div>
                </motion.div>
              ))}
            </div>

            <div className="px-3 py-3" style={{ borderTop: "1px solid var(--color-border)" }}>
              <div className="flex items-center gap-2">
                <input
                  type="text"
                  value={input}
                  onChange={(e) => setInput(e.target.value)}
                  onKeyDown={(e) => e.key === "Enter" && send()}
                  placeholder={placeholder}
                  className="flex-1 bg-transparent text-sm outline-none"
                  style={{ color: "var(--color-foreground)" }}
                />
                <motion.button whileTap={{ scale: 0.9 }} onClick={send} className="p-2 rounded-lg" style={{ background: "var(--color-accent)" }}>
                  <Send size={14} style={{ color: "var(--color-background)" }} />
                </motion.button>
              </div>
            </div>
          </motion.div>
        )}
      </AnimatePresence>

      {!isOpen && (
        <motion.button
          whileHover={{ scale: 1.05 }}
          whileTap={{ scale: 0.95 }}
          onClick={() => setIsOpen(true)}
          className="w-14 h-14 rounded-full flex items-center justify-center shadow-lg"
          style={{ background: "var(--color-accent)" }}
        >
          <MessageCircle size={24} style={{ color: "var(--color-background)" }} />
        </motion.button>
      )}
    </div>
  );
}

Avis

Integration Chat Widget — React Integrations Section — Incubator