SexyAnalysisReviewPill.tsx3.2 KBView on GitHub
/**
 * AnalysisReviewPill Component
 *
 * Pill-shaped sticky notification that appears at the bottom of the conversation canvas
 * when there are completed analyses ready for review. Clicking opens the analysis review modal.
 */

import { motion, AnimatePresence } from 'motion/react';
import { ChevronRight, FileText, X } from 'lucide-react';
import { useState } from 'react';

export interface AnalysisReviewPillProps {
  /** Number of completed analyses ready to review */
  pendingCount: number;
  /** Callback when the pill is clicked */
  onOpenReview: () => void;
  /** Callback to clear all analyses */
  onClear: () => void;
}

export function AnalysisReviewPill({ pendingCount, onOpenReview, onClear }: AnalysisReviewPillProps) {
  const [reviewHovered, setReviewHovered] = useState(false);
  const [clearHovered, setClearHovered] = useState(false);

  return (
    <AnimatePresence>
      {pendingCount > 0 && (
        <motion.div
          initial={{ y: 100, opacity: 0 }}
          animate={{ y: 0, opacity: 1 }}
          exit={{ y: 100, opacity: 0 }}
          transition={{ type: 'spring', damping: 25, stiffness: 300 }}
          className="absolute bottom-4 left-1/2 z-10 -translate-x-1/2"
        >
          <div className="flex items-center gap-2">
            {/* Clear pill */}
            <button
              onClick={onClear}
              onMouseEnter={() => setClearHovered(true)}
              onMouseLeave={() => setClearHovered(false)}
              className="flex cursor-pointer items-center gap-2.5 overflow-hidden rounded-full bg-foreground px-4 py-2 shadow-md text-sm font-medium text-white transition-all duration-200 hover:bg-red-400"
            >
              <motion.div
                animate={clearHovered ? { rotate: 90, scale: 1.2 } : { rotate: 0, scale: 1 }}
                transition={{ duration: 0.2, ease: 'easeInOut' }}
              >
                <X className="h-4 w-4" />
              </motion.div>
              Clear
            </button>

            {/* Main pill (primary CTA) */}
            <button
              onClick={onOpenReview}
              onMouseEnter={() => setReviewHovered(true)}
              onMouseLeave={() => setReviewHovered(false)}
              className="group flex cursor-pointer items-center gap-2.5 overflow-hidden rounded-full bg-foreground px-4 py-2 shadow-md transition-all duration-200 hover:bg-foreground/90"
            >
              <FileText className="h-4 w-4 text-white" />
              <span className="rounded-full bg-white/20 px-1.5 py-0.5 text-xs font-medium text-white">
                {pendingCount}
              </span>
              <span className="text-sm font-medium text-white">
                Review {pendingCount !== 1 ? 'Analyses' : 'Analysis'}
              </span>
              <motion.div
                animate={reviewHovered ? { x: 24, opacity: 0, scale: 1.4, color: '#ffffff' } : { x: 0, opacity: 1, scale: 1, color: 'rgba(255,255,255,0.7)' }}
                transition={{ duration: 0.18, ease: 'easeIn' }}
              >
                <ChevronRight className="h-4 w-4" />
              </motion.div>
            </button>
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}