Skip to content

Expandable Card Plugin Component (components/Editor/expandable-card.tsx)

Overview

A React component that displays cited evidence in expandable cards with text highlighting capabilities. The component manages text highlighting, card expansion states, and provides smooth transitions for interactive elements.

Features

  • Text highlighting
  • Card expansion
  • Evidence display
  • Color transitions
  • UUID management
  • Content preview
  • Overflow handling
  • Dynamic sizing
  • State management
  • Smooth animations

Implementation

type CitedEvidenceProps = {
  citedEvidence?: citedEvidence | null;
  isHighlighted?: boolean | null;
  uuids?: string[] | null;
  selectedUUIDIndex?: number | 0 | null;
};

export function ExpandableCardPlugin({
  citedEvidence,
  isHighlighted,
  uuids,
  selectedUUIDIndex
}: CitedEvidenceProps) {
  const [expandedCardIndex, setExpandedCardIndex] = useState<number | null>(null);
  const [isEditing, setIsEditing] = useState(false);
  const [isExpanded, setIsExpanded] = useState(false);
  const highlightedColour = '#EE2A7B';
  const transparentColour = 'white';
  const [selectedUUID, setSelectedUUID] = useState<string | null>(null);

  const getHighlightColor = useCallback((uuid: string) => {
    return uuid === selectedUUID ? highlightedColour : transparentColour;
  }, [selectedUUID, highlightedColour, transparentColour]);

  const highlightText = (beforeText: string, afterText: string | null, highlightedText: string, uuid: string) => {
    const color = getHighlightColor(uuid);
    return (
      <div>
        <span>{beforeText}</span>
        <span 
          className={uuid}
          style={{ 
            backgroundColor: color, 
            padding: '0.25rem', 
            borderRadius: '0.25rem', 
            transition: 'all 0.5s ease-in-out' 
          }}
        >
          {highlightedText}
        </span>
        {afterText && <span>{afterText}</span>}
      </div>
    );
  };

  const highlightSpecifiedText = (citedEvidence: citedEvidence) => {
    const allHighlightedNodes: JSX.Element[] = [];

    citedEvidence.forEach(evidence => {
      const textContent = evidence.evidence_text;
      const textContentArray = textContent.split(" ");
      const highlightedTextRanges: HighlightedTextNodes = [];

      evidence.citations.forEach((citation, index) => {
        const { uuid, evidence_start_index, evidence_end_index } = citation;
        highlightedTextRanges.push({
          index,
          uuid,
          startIndex: evidence_start_index,
          endIndex: evidence_end_index
        });
      });

      const highlightedTextRangesSorted = sortBasedOnStartIndex(highlightedTextRanges);
      const newNodes: JSX.Element[] = [];

      // Process text ranges and create highlighted nodes
      let lowerBound = 0;
      highlightedTextRangesSorted.forEach((node, i) => {
        if (lowerBound <= node.startIndex) {
          const beforeText = textContentArray.slice(lowerBound, node.startIndex - 1).join(" ");
          const highlightedText = textContentArray.slice(node.startIndex - 1, node.endIndex + 1).join(" ");
          const afterText = i === highlightedTextRangesSorted.length - 1
            ? textContentArray.slice(node.endIndex + 1).join(" ")
            : null;

          newNodes.push(highlightText(beforeText, afterText, highlightedText, node.uuid));
          lowerBound = node.endIndex + 1;
        }
      });

      allHighlightedNodes.push(<div key={evidence.uuid}>{newNodes}</div>);
    });

    return allHighlightedNodes;
  };

  return (
    <div className="ml-4 flex overflow-x-auto space-x-4">
      {(isHighlighted ? highlightSpecifiedText(citedEvidence) : citedEvidence.map(evidence => (
        <div key={evidence.uuid}>{evidence.evidence_text}</div>
      ))).map((highlightedContent, nodeIndex) => (
        <Card
          key={nodeIndex}
          className={`max-w-[500px] flex-shrink-0 mb-4 transition-all duration-300 ease-in-out ${
            isExpanded ? 'max-h-none' : 'max-h-[150px]'
          }`}
        >
          <CardContent className={`p-4 flex flex-col h-full ${!isExpanded && 'overflow-hidden'}`}>
            <div className="text-sm text-black flex-grow">
              {highlightedContent}
            </div>
          </CardContent>
        </Card>
      ))}
    </div>
  );
}

Dependencies

import { JSX, useState, useEffect, useCallback } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { Card, CardContent } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { citedEvidence } from "./citedEvidence"

Components Used

Notes

  • Manages text highlighting
  • Handles card expansion
  • Processes evidence data
  • Updates color states
  • Maintains UUID tracking
  • Implements transitions
  • Sorts text ranges
  • Preserves text content
  • Optimizes performance
  • Ensures accessibility