diff --git a/surfsense_web/components/tool-ui/citation/citation-list.tsx b/surfsense_web/components/tool-ui/citation/citation-list.tsx deleted file mode 100644 index 94737f142..000000000 --- a/surfsense_web/components/tool-ui/citation/citation-list.tsx +++ /dev/null @@ -1,387 +0,0 @@ -"use client"; - -import { ExternalLink, Globe } from "lucide-react"; -import NextImage from "next/image"; -import * as React from "react"; -import { openSafeNavigationHref, resolveSafeNavigationHref } from "../shared/media"; -import { cn, Popover, PopoverContent, PopoverTrigger } from "./_adapter"; -import { Citation } from "./citation"; -import type { CitationVariant, SerializableCitation } from "./schema"; -import { TYPE_ICONS } from "./type-icons"; - -function useHoverPopover(delay = 100) { - const [open, setOpen] = React.useState(false); - const timeoutRef = React.useRef | null>(null); - const containerRef = React.useRef(null); - - const handleMouseEnter = React.useCallback(() => { - if (timeoutRef.current) clearTimeout(timeoutRef.current); - timeoutRef.current = setTimeout(() => setOpen(true), delay); - }, [delay]); - - const handleMouseLeave = React.useCallback(() => { - if (timeoutRef.current) clearTimeout(timeoutRef.current); - timeoutRef.current = setTimeout(() => setOpen(false), delay); - }, [delay]); - - const handleFocus = React.useCallback(() => { - if (timeoutRef.current) clearTimeout(timeoutRef.current); - setOpen(true); - }, []); - - const handleBlur = React.useCallback( - (e: React.FocusEvent) => { - const relatedTarget = e.relatedTarget as HTMLElement | null; - if (containerRef.current?.contains(relatedTarget)) { - return; - } - if (relatedTarget?.closest("[data-radix-popper-content-wrapper]")) { - return; - } - if (timeoutRef.current) clearTimeout(timeoutRef.current); - timeoutRef.current = setTimeout(() => setOpen(false), delay); - }, - [delay] - ); - - React.useEffect(() => { - return () => { - if (timeoutRef.current) clearTimeout(timeoutRef.current); - }; - }, []); - - return { - open, - setOpen, - containerRef, - handleMouseEnter, - handleMouseLeave, - handleFocus, - handleBlur, - }; -} - -export interface CitationListProps { - id: string; - citations: SerializableCitation[]; - variant?: CitationVariant; - maxVisible?: number; - className?: string; - onNavigate?: (href: string, citation: SerializableCitation) => void; -} - -export function CitationList(props: CitationListProps) { - const { id, citations, variant = "default", maxVisible, className, onNavigate } = props; - - const shouldTruncate = maxVisible !== undefined && citations.length > maxVisible; - const visibleCitations = shouldTruncate ? citations.slice(0, maxVisible) : citations; - const overflowCitations = shouldTruncate ? citations.slice(maxVisible) : []; - const overflowCount = overflowCitations.length; - - const wrapperClass = - variant === "inline" ? "flex flex-wrap items-center gap-1.5" : "flex flex-col gap-2"; - - // Stacked variant: overlapping favicons with popover - if (variant === "stacked") { - return ( - - ); - } - - if (variant === "default") { - return ( -
- {visibleCitations.map((citation) => ( - - ))} - {shouldTruncate && ( - - )} -
- ); - } - - return ( -
- {visibleCitations.map((citation) => ( - - ))} - {shouldTruncate && ( - - )} -
- ); -} - -interface OverflowIndicatorProps { - citations: SerializableCitation[]; - count: number; - variant: CitationVariant; - onNavigate?: (href: string, citation: SerializableCitation) => void; -} - -function OverflowIndicator({ citations, count, variant, onNavigate }: OverflowIndicatorProps) { - const { open, handleMouseEnter, handleMouseLeave } = useHoverPopover(); - - const handleClick = (citation: SerializableCitation) => { - const href = resolveSafeNavigationHref(citation.href); - if (!href) return; - if (onNavigate) { - onNavigate(href, citation); - } else { - openSafeNavigationHref(href); - } - }; - - const popoverContent = ( -
- {citations.map((citation) => ( - handleClick(citation)} /> - ))} -
- ); - - if (variant === "inline") { - return ( - - - - - e.preventDefault()} - > - {popoverContent} - - - ); - } - - // Default variant - return ( - - - - - e.preventDefault()} - > - {popoverContent} - - - ); -} - -interface OverflowItemProps { - citation: SerializableCitation; - onClick: () => void; -} - -function OverflowItem({ citation, onClick }: OverflowItemProps) { - const TypeIcon = TYPE_ICONS[citation.type ?? "webpage"] ?? Globe; - - return ( - - ); -} - -interface StackedCitationsProps { - id: string; - citations: SerializableCitation[]; - className?: string; - onNavigate?: (href: string, citation: SerializableCitation) => void; -} - -function StackedCitations({ id, citations, className, onNavigate }: StackedCitationsProps) { - const { open, setOpen, containerRef, handleMouseEnter, handleMouseLeave, handleBlur } = - useHoverPopover(); - const maxIcons = 4; - const visibleCitations = citations.slice(0, maxIcons); - const remainingCount = Math.max(0, citations.length - maxIcons); - - const handleClick = (citation: SerializableCitation) => { - const href = resolveSafeNavigationHref(citation.href); - if (!href) return; - if (onNavigate) { - onNavigate(href, citation); - } else { - openSafeNavigationHref(href); - } - }; - - return ( - // biome-ignore lint/a11y/noStaticElementInteractions: blur boundary for popover focus management -
- - - - - setOpen(false)} - > -
- {citations.map((citation) => ( - handleClick(citation)} - /> - ))} -
-
-
-
- ); -} diff --git a/surfsense_web/components/tool-ui/citation/index.ts b/surfsense_web/components/tool-ui/citation/index.ts index 2abcc5746..8a6415903 100644 --- a/surfsense_web/components/tool-ui/citation/index.ts +++ b/surfsense_web/components/tool-ui/citation/index.ts @@ -1,7 +1,5 @@ export type { CitationProps } from "./citation"; export { Citation } from "./citation"; -export type { CitationListProps } from "./citation-list"; -export { CitationList } from "./citation-list"; export type { CitationType, CitationVariant,