File size: 3,829 Bytes
f56a29b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddfb1af
f56a29b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118


import { useMemo } from 'react';
import { useSceneSelector } from '@/lib/contexts/scene-context';
import { useCanvasStore } from '@/lib/store/canvas';
import type { SlideContent } from '@/lib/types/stage';
import type { PPTElement } from '@/lib/types/slides';

/**
 * Highlight overlay component
 *
 * Features:
 * - Overlays highlight effects on top of elements
 * - Does not modify element properties
 * - Supports highlighting multiple elements simultaneously
 * - Supports animation effects (breathing, blinking, etc.)
 *
 * Implementation:
 * - Creates overlay divs at element positions
 * - Uses box-shadow for glow effects
 * - Uses CSS animation for animated effects
 */
export function HighlightOverlay() {
  const highlightedElementIds = useCanvasStore.use.highlightedElementIds();
  const highlightOptions = useCanvasStore.use.highlightOptions();

  // Get the element list of the current scene
  const elements = useSceneSelector<SlideContent, PPTElement[]>(
    (content) => content.canvas.elements,
  );

  // Find all elements to highlight (exclude line elements as they have no height property)
  const highlightedElements = useMemo(() => {
    if (!highlightedElementIds.length) return [];
    return elements.filter((el) => highlightedElementIds.includes(el.id) && el.type !== 'line');
  }, [elements, highlightedElementIds]);

  // Skip rendering if no highlighted elements
  if (!highlightedElements.length || !highlightOptions) {
    return null;
  }

  const { color = '#ff6b6b', opacity = 0.3, borderWidth = 3, animated = true } = highlightOptions;

  return (
    <>
      {highlightedElements.map((element) => {
        // Type guard: line elements are already filtered out above
        // Use 'in' operator for runtime checks to satisfy TypeScript
        const height = 'height' in element ? element.height : 0;
        const rotate = 'rotate' in element ? element.rotate : 0;
        return (
          <div
            key={element.id}
            className="highlight-overlay absolute pointer-events-none"
            style={{
              left: `${element.left}px`,
              top: `${element.top}px`,
              width: `${element.width}px`,
              height: `${height}px`,
              transform: `rotate(${rotate || 0}deg)`,
              transformOrigin: 'center',
              zIndex: 999,
              transition: 'all 0.3s ease-in-out',
            }}
          >
            {/* Highlight border */}
            <div
              className={`absolute inset-0 rounded ${animated ? 'animate-pulse' : ''}`}
              style={{
                border: `${borderWidth}px solid ${color}`,
                boxShadow: `
                0 0 ${borderWidth * 3}px ${color},
                inset 0 0 ${borderWidth * 2}px rgba(255,255,255,${opacity * 0.5})
              `,
                backgroundColor: `${color}${Math.round(opacity * 255)
                  .toString(16)
                  .padStart(2, '0')}`,
              }}
            />

            {/* Glow effect */}
            {animated && (
              <div
                className="absolute inset-0 rounded animate-ping"
                style={{
                  border: `${borderWidth}px solid ${color}`,
                  opacity: 0.5,
                  animationDuration: '2s',
                }}
              />
            )}
          </div>
        );
      })}

      {/* CSS animation (breathing light effect) */}
      <style>{`
        @keyframes breathe {
          0%,
          100% {
            opacity: 0.6;
            transform: scale(1);
          }
          50% {
            opacity: 1;
            transform: scale(1.02);
          }
        }

        .highlight-overlay.animate-pulse {
          animation: breathe 2s ease-in-out infinite;
        }
      `}</style>
    </>
  );
}