All files / lib/awareness/collective/components HUDLayerManager.tsx

100% Statements 68/68
100% Branches 4/4
100% Functions 1/1
100% Lines 68/68

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 941x   1x 1x 1x 1x 1x     1x           1x 6x   6x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 6x   6x   3x 3x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x   6x 6x 6x 6x 6x 6x 6x 6x 6x   6x 6x 6x         6x 6x 6x 6x 6x 6x 6x 6x 6x   6x               6x 6x  
"use client";
 
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import { AnimatePresence, motion } from "framer-motion";
import { AISummaryPanel } from "./AISummaryPanel";
import { NeuralVoiceHUD } from "./NeuralVoiceHUD";
import type { Insight } from "../analysis/insight";
 
export const HUDLayerManager: React.FC<{
  showAI: boolean;
  showVoice: boolean;
  onCloseAI: () => void;
  onCloseVoice: () => void;
  initialInsight?: Insight | null;
}> = ({ showAI, showVoice, onCloseAI, onCloseVoice, initialInsight }) => {
  const [rootEl, setRootEl] = useState<HTMLElement | null>(null);
 
  useEffect(() => {
    let el = document.getElementById("hud-global-root") as HTMLElement | null;
    if (!el) {
      el = document.createElement("div");
      el.id = "hud-global-root";
      Object.assign(el.style, {
        position: "fixed",
        top: "0",
        left: "0",
        width: "100vw",
        height: "100vh",
        zIndex: "1999",
        pointerEvents: "none",
      });
      document.body.appendChild(el);
    }
    setRootEl(el);
  }, []);
 
  if (!rootEl) return null;
 
  const overlay = (
    <AnimatePresence>
      {(showAI || showVoice) && (
        <motion.div
          key="hud-overlay"
          className="fixed inset-0 flex items-center justify-center"
          style={{
            backdropFilter: "blur(28px) saturate(180%)",
            background:
              "linear-gradient(135deg, rgba(0,8,20,0.7), rgba(0,10,25,0.85))",
            zIndex: 2000,
            pointerEvents: "auto",
          }}
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
        >
          {showAI && (
            <motion.div
              key="ai"
              className="relative pointer-events-auto"
              style={{ zIndex: 2001 }}
              initial={{ opacity: 0, scale: 0.9 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.9 }}
              transition={{ type: "spring", stiffness: 140, damping: 16 }}
            >
              <AISummaryPanel
                onClose={onCloseAI}
                initialInsight={initialInsight}
              />
            </motion.div>
          )}
 
          {showVoice && (
            <motion.div
              key="voice"
              className="relative pointer-events-auto"
              style={{ zIndex: 2001 }}
              initial={{ opacity: 0, scale: 0.9 }}
              animate={{ opacity: 1, scale: 1 }}
              exit={{ opacity: 0, scale: 0.9 }}
              transition={{ type: "spring", stiffness: 140, damping: 16 }}
            >
              <NeuralVoiceHUD onClose={onCloseVoice} />
            </motion.div>
          )}
        </motion.div>
      )}
    </AnimatePresence>
  );
 
  return ReactDOM.createPortal(overlay, rootEl);
};