Retour au catalogue

Event Live Stream

Section d'embed de live stream avec chat lateral, indicateur en direct et infos du speaker.

eventmedium Both Responsive a11y
boldcorporateeventeducationsplit
Theme
"use client";

import { useState } from "react";
import { motion } from "framer-motion";
import { Radio, Users, MessageCircle, Send, Play } from "lucide-react";

interface ChatMessage {
  author: string;
  message: string;
  time: string;
}

interface Speaker {
  name: string;
  role: string;
  avatarUrl: string;
}

interface EventLiveStreamProps {
  title?: string;
  currentTalk?: string;
  speaker?: Speaker;
  viewerCount?: number;
  embedPlaceholder?: string;
  chatMessages?: ChatMessage[];
}

const EASE = [0.16, 1, 0.3, 1] as const;

export default function EventLiveStream({
  title = "En direct",
  currentTalk = "Presentation",
  speaker,
  viewerCount = 0,
  embedPlaceholder = "/api/placeholder/800/450",
  chatMessages = [],
}: EventLiveStreamProps) {
  const [chatInput, setChatInput] = useState("");

  return (
    <section
      style={{
        paddingTop: "var(--section-padding-y)",
        paddingBottom: "var(--section-padding-y)",
        background: "var(--color-background)",
      }}
    >
      <div
        style={{
          maxWidth: "var(--container-max-width)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x)",
        }}
      >
        {/* Header with live indicator */}
        <motion.div
          initial={{ opacity: 0, y: 16 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true }}
          transition={{ duration: 0.5, ease: EASE }}
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            flexWrap: "wrap",
            gap: "1rem",
            marginBottom: "1.5rem",
          }}
        >
          <div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
            <motion.div
              animate={{ opacity: [1, 0.4, 1] }}
              transition={{ duration: 1.5, repeat: Infinity }}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.375rem",
                padding: "0.375rem 0.75rem",
                borderRadius: "var(--radius-full)",
                background: "color-mix(in srgb, var(--color-accent) 15%, transparent)",
              }}
            >
              <Radio style={{ width: 14, height: 14, color: "var(--color-accent)" }} />
              <span
                style={{
                  fontSize: "0.75rem",
                  fontWeight: 700,
                  color: "var(--color-accent)",
                  textTransform: "uppercase",
                }}
              >
                {title}
              </span>
            </motion.div>
            <h2
              style={{
                fontSize: "clamp(1.125rem, 2vw, 1.5rem)",
                fontWeight: 700,
                color: "var(--color-foreground)",
              }}
            >
              {currentTalk}
            </h2>
          </div>

          <div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
            <Users style={{ width: 14, height: 14, color: "var(--color-foreground-muted)" }} />
            <span style={{ fontSize: "0.8125rem", color: "var(--color-foreground-muted)", fontVariantNumeric: "tabular-nums" }}>
              {viewerCount.toLocaleString("fr-FR")} spectateurs
            </span>
          </div>
        </motion.div>

        {/* Main content: Video + Chat */}
        <div
          style={{
            display: "grid",
            gridTemplateColumns: "1fr 320px",
            gap: "1.5rem",
          }}
        >
          {/* Video area */}
          <motion.div
            initial={{ opacity: 0, scale: 0.98 }}
            whileInView={{ opacity: 1, scale: 1 }}
            viewport={{ once: true }}
            transition={{ duration: 0.5, ease: EASE }}
          >
            <div
              style={{
                position: "relative",
                aspectRatio: "16/9",
                borderRadius: "var(--radius-lg)",
                overflow: "hidden",
                background: "var(--color-background-alt)",
                border: "1px solid var(--color-border)",
              }}
            >
              <img
                src={embedPlaceholder}
                alt="Stream"
                style={{ width: "100%", height: "100%", objectFit: "cover" }}
              />
              {/* Play overlay */}
              <div
                style={{
                  position: "absolute",
                  inset: 0,
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  background: "color-mix(in srgb, var(--color-foreground) 30%, transparent)",
                }}
              >
                <div
                  style={{
                    width: 64,
                    height: 64,
                    borderRadius: "50%",
                    background: "var(--color-accent)",
                    display: "flex",
                    alignItems: "center",
                    justifyContent: "center",
                    cursor: "pointer",
                  }}
                >
                  <Play
                    style={{
                      width: 28,
                      height: 28,
                      color: "var(--color-background)",
                      marginLeft: 4,
                    }}
                  />
                </div>
              </div>
            </div>

            {/* Speaker info */}
            {speaker && (
              <div
                style={{
                  display: "flex",
                  alignItems: "center",
                  gap: "0.75rem",
                  marginTop: "1rem",
                  padding: "1rem",
                  borderRadius: "var(--radius-md)",
                  background: "var(--color-background-card)",
                  border: "1px solid var(--color-border)",
                }}
              >
                <img
                  src={speaker.avatarUrl}
                  alt={speaker.name}
                  style={{
                    width: 40,
                    height: 40,
                    borderRadius: "50%",
                    objectFit: "cover",
                  }}
                />
                <div>
                  <p
                    style={{
                      fontSize: "0.875rem",
                      fontWeight: 700,
                      color: "var(--color-foreground)",
                    }}
                  >
                    {speaker.name}
                  </p>
                  <p
                    style={{
                      fontSize: "0.75rem",
                      color: "var(--color-foreground-muted)",
                    }}
                  >
                    {speaker.role}
                  </p>
                </div>
              </div>
            )}
          </motion.div>

          {/* Chat panel */}
          <motion.div
            initial={{ opacity: 0, x: 16 }}
            whileInView={{ opacity: 1, x: 0 }}
            viewport={{ once: true }}
            transition={{ duration: 0.5, delay: 0.1, ease: EASE }}
            style={{
              display: "flex",
              flexDirection: "column",
              borderRadius: "var(--radius-lg)",
              border: "1px solid var(--color-border)",
              background: "var(--color-background-card)",
              overflow: "hidden",
            }}
          >
            {/* Chat header */}
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.5rem",
                padding: "0.875rem 1rem",
                borderBottom: "1px solid var(--color-border)",
              }}
            >
              <MessageCircle style={{ width: 16, height: 16, color: "var(--color-accent)" }} />
              <span style={{ fontSize: "0.8125rem", fontWeight: 700, color: "var(--color-foreground)" }}>
                Chat en direct
              </span>
            </div>

            {/* Messages */}
            <div
              style={{
                flex: 1,
                overflow: "auto",
                padding: "0.75rem",
                display: "flex",
                flexDirection: "column",
                gap: "0.625rem",
                maxHeight: 400,
              }}
            >
              {chatMessages.map((msg, i) => (
                <motion.div
                  key={i}
                  initial={{ opacity: 0, x: -8 }}
                  animate={{ opacity: 1, x: 0 }}
                  transition={{ duration: 0.3, delay: 0.05 * i }}
                  style={{ display: "flex", gap: "0.5rem", alignItems: "flex-start" }}
                >
                  <div style={{ flex: 1 }}>
                    <div style={{ display: "flex", alignItems: "baseline", gap: "0.375rem" }}>
                      <span
                        style={{
                          fontSize: "0.75rem",
                          fontWeight: 700,
                          color: "var(--color-accent)",
                        }}
                      >
                        {msg.author}
                      </span>
                      <span
                        style={{
                          fontSize: "0.625rem",
                          color: "var(--color-foreground-muted)",
                        }}
                      >
                        {msg.time}
                      </span>
                    </div>
                    <p
                      style={{
                        fontSize: "0.8125rem",
                        color: "var(--color-foreground)",
                        lineHeight: 1.4,
                      }}
                    >
                      {msg.message}
                    </p>
                  </div>
                </motion.div>
              ))}
            </div>

            {/* Chat input */}
            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: "0.5rem",
                padding: "0.75rem",
                borderTop: "1px solid var(--color-border)",
              }}
            >
              <input
                value={chatInput}
                onChange={(e) => setChatInput(e.target.value)}
                placeholder="Envoyer un message..."
                style={{
                  flex: 1,
                  padding: "0.5rem 0.75rem",
                  borderRadius: "var(--radius-md)",
                  border: "1px solid var(--color-border)",
                  background: "var(--color-background)",
                  color: "var(--color-foreground)",
                  fontSize: "0.8125rem",
                }}
              />
              <button
                style={{
                  width: 36,
                  height: 36,
                  borderRadius: "var(--radius-md)",
                  background: "var(--color-accent)",
                  border: "none",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  cursor: "pointer",
                  flexShrink: 0,
                }}
              >
                <Send style={{ width: 14, height: 14, color: "var(--color-background)" }} />
              </button>
            </div>
          </motion.div>
        </div>
      </div>
    </section>
  );
}

Avis

Event Live Stream — React Event Section — Incubator