Code
Custom Search
Add powerful search capabilities to your PDF viewer with real-time highlighting
Loading...
Basic Usage
Create a PDF viewer with custom search functionality and result highlighting:
"use client";
import {
Root,
Pages,
Page,
CanvasLayer,
TextLayer,
HighlightLayer,
Search,
} from "@anaralabs/lector";
import { useState } from "react";
const ResultItem = ({ result }) => (
<div className="flex py-2 hover:bg-gray-50 cursor-pointer">
<div className="flex-1 min-w-0">
<p className="text-sm text-gray-900">{result.text}</p>
<p className="text-sm text-gray-500">Page {result.pageNumber}</p>
</div>
</div>
);
const SearchUI = () => {
const [searchText, setSearchText] = useState("");
return (
<div className="flex flex-col w-64 h-full">
<div className="px-4 py-4 border-b border-gray-200 bg-white">
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Search in document..."
className="w-full px-4 py-2 border rounded-lg"
/>
</div>
<div className="flex-1 overflow-y-auto px-4">
{/* Results will appear here */}
</div>
</div>
);
};
export default function CustomSearch() {
return (
<Root source="/pdf/large.pdf" className="flex bg-gray-50 h-[500px]">
<Search>
<SearchUI />
</Search>
<Pages className="p-4 w-full">
<Page>
<CanvasLayer />
<TextLayer />
<HighlightLayer className="bg-yellow-200/70" />
</Page>
</Pages>
</Root>
);
}
Advanced Implementation
For more advanced features like debouncing, fuzzy matching, and jumping to results, here's the complete implementation:
import { useDebounce } from "use-debounce";
import {
calculateHighlightRects,
SearchResult,
usePdf,
usePdfJump,
useSearch,
} from "@anaralabs/lector";
import { useEffect, useState } from "react";
interface ResultItemProps {
result: SearchResult;
}
const ResultItem = ({ result }: ResultItemProps) => {
const { jumpToHighlightRects } = usePdfJump();
const getPdfPageProxy = usePdf((state) => state.getPdfPageProxy);
const onClick = async () => {
const pageProxy = getPdfPageProxy(result.pageNumber);
const rects = await calculateHighlightRects(pageProxy, {
pageNumber: result.pageNumber,
text: result.text,
matchIndex: result.matchIndex,
});
jumpToHighlightRects(rects, "pixels");
};
return (
<div
className="flex py-2 hover:bg-gray-50 flex-col cursor-pointer"
onClick={onClick}
>
<div className="flex-1 min-w-0">
<p className="text-sm text-gray-900">{result.text}</p>
</div>
<div className="flex items-center gap-4 text-sm text-gray-500">
<span className="ml-auto">Page {result.pageNumber}</span>
</div>
</div>
);
};
interface ResultGroupProps {
title: string;
results: SearchResult[];
displayCount?: number;
}
const ResultGroup = ({ title, results, displayCount }: ResultGroupProps) => {
if (!results.length) return null;
const displayResults = displayCount
? results.slice(0, displayCount)
: results;
return (
<div className="space-y-2">
<h3 className="text-sm font-medium text-gray-700">{title}</h3>
<div className="divide-y divide-gray-100">
{displayResults.map((result) => (
<ResultItem
key={`${result.pageNumber}-${result.matchIndex}`}
result={result}
/>
))}
</div>
</div>
);
};
export function SearchUI() {
const [searchText, setSearchText] = useState("");
const [debouncedSearchText] = useDebounce(searchText, 500);
const [limit, setLimit] = useState(5);
const { searchResults: results, search } = useSearch();
useEffect(() => {
setLimit(5);
search(debouncedSearchText, { limit: 5 });
}, [debouncedSearchText]);
const handleLoadMore = async () => {
const newLimit = limit + 5;
await search(debouncedSearchText, { limit: newLimit });
setLimit(newLimit);
};
return (
<div className="flex flex-col w-80 h-full">
<div className="px-4 py-4 border-b border-gray-200 bg-white">
<input
type="text"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder="Search in document..."
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex-1 overflow-y-auto px-4">
<div className="py-4">
<SearchResults
searchText={searchText}
results={results}
onLoadMore={handleLoadMore}
/>
</div>
</div>
</div>
);
}
Features
- Real-time search with debouncing
- Result highlighting
- Page jumping to search results
- Load more functionality
Best Practices
- Use debouncing to prevent excessive searches
- Include proper page navigation
- Handle empty states gracefully
- Optimize search performance