Retour au catalogue

Integration Calendar

Calendrier mensuel interactif avec evenements, selection de jour et navigation.

integrationsmedium Both Responsive a11y
minimalcorporatesaaseventuniversalcentered
Theme
"use client";

import { useState } from "react";
import { motion } from "framer-motion";
import { ChevronLeft, ChevronRight } from "lucide-react";

interface CalendarEvent {
  day: number;
  title: string;
  color?: string;
}

interface IntegrationCalendarProps {
  title?: string;
  month?: string;
  year?: number;
  events?: CalendarEvent[];
  daysInMonth?: number;
  firstDayOffset?: number;
}

export default function IntegrationCalendar({
  title = "Calendrier",
  month = "Mars",
  year = 2026,
  events = [],
  daysInMonth = 31,
  firstDayOffset = 0,
}: IntegrationCalendarProps) {
  const [selectedDay, setSelectedDay] = useState<number | null>(null);
  const days = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"];
  const blanks = Array.from({ length: firstDayOffset });
  const allDays = Array.from({ length: daysInMonth }, (_, i) => i + 1);

  const getEvents = (day: number) => events.filter((e) => e.day === day);

  return (
    <div className="py-12 px-6" style={{ background: "var(--color-background)" }}>
      <motion.div
        initial={{ opacity: 0, y: 16 }}
        animate={{ opacity: 1, y: 0 }}
        className="mx-auto max-w-md rounded-xl overflow-hidden"
        style={{ background: "var(--color-background-card)", border: "1px solid var(--color-border)" }}
      >
        <div className="flex items-center justify-between px-5 py-4" style={{ borderBottom: "1px solid var(--color-border)" }}>
          <button className="p-1"><ChevronLeft size={18} style={{ color: "var(--color-foreground-muted)" }} /></button>
          <h3 className="text-sm font-semibold" style={{ color: "var(--color-foreground)" }}>{month} {year}</h3>
          <button className="p-1"><ChevronRight size={18} style={{ color: "var(--color-foreground-muted)" }} /></button>
        </div>

        <div className="px-4 py-3">
          <div className="grid grid-cols-7 gap-1 mb-2">
            {days.map((d) => (
              <span key={d} className="text-center text-[10px] font-semibold py-1" style={{ color: "var(--color-foreground-light)" }}>{d}</span>
            ))}
          </div>

          <div className="grid grid-cols-7 gap-1">
            {blanks.map((_, i) => <div key={`b-${i}`} />)}
            {allDays.map((day) => {
              const dayEvents = getEvents(day);
              const isSelected = selectedDay === day;
              const today = day === 6;

              return (
                <button
                  key={day}
                  onClick={() => setSelectedDay(day)}
                  className="relative aspect-square flex flex-col items-center justify-center rounded-lg text-xs font-medium transition-colors"
                  style={{
                    background: isSelected ? "var(--color-accent)" : today ? "var(--color-background-alt)" : "transparent",
                    color: isSelected ? "var(--color-background)" : "var(--color-foreground)",
                  }}
                >
                  {day}
                  {dayEvents.length > 0 && (
                    <div className="absolute bottom-1 flex gap-0.5">
                      {dayEvents.slice(0, 3).map((_, ei) => (
                        <div key={ei} className="w-1 h-1 rounded-full" style={{ background: isSelected ? "var(--color-background)" : "var(--color-accent)" }} />
                      ))}
                    </div>
                  )}
                </button>
              );
            })}
          </div>
        </div>

        {selectedDay && getEvents(selectedDay).length > 0 && (
          <motion.div
            initial={{ height: 0 }}
            animate={{ height: "auto" }}
            className="px-4 pb-4 overflow-hidden"
          >
            <div className="pt-3" style={{ borderTop: "1px solid var(--color-border)" }}>
              {getEvents(selectedDay).map((e, i) => (
                <div key={i} className="flex items-center gap-2 py-1.5">
                  <div className="w-2 h-2 rounded-full" style={{ background: "var(--color-accent)" }} />
                  <span className="text-xs" style={{ color: "var(--color-foreground)" }}>{e.title}</span>
                </div>
              ))}
            </div>
          </motion.div>
        )}
      </motion.div>
    </div>
  );
}

Avis

Integration Calendar — React Integrations Section — Incubator