45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
|
|
import { useMemo, Fragment } from 'react'
|
|||
|
|
|
|||
|
|
interface HighlighterProps {
|
|||
|
|
/** Vurgulanacak kelimeler; boş/whitespace olanlar yok sayılır. */
|
|||
|
|
searchWords: string[]
|
|||
|
|
textToHighlight: string
|
|||
|
|
highlightClassName?: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* react-highlight-words yerine kullanılan yerel bileşen.
|
|||
|
|
* Kullanılan tek senaryo: arama kelimelerini büyük/küçük harf duyarsız
|
|||
|
|
* eşleyip <mark> ile işaretlemek (paketteki `autoEscape` davranışı varsayılan).
|
|||
|
|
*/
|
|||
|
|
const Highlighter = ({ searchWords, textToHighlight, highlightClassName }: HighlighterProps) => {
|
|||
|
|
const parts = useMemo(() => {
|
|||
|
|
const words = searchWords.map((word) => word?.trim()).filter(Boolean)
|
|||
|
|
if (words.length === 0 || !textToHighlight) return null
|
|||
|
|
|
|||
|
|
const pattern = new RegExp(`(${words.map(escapeRegExp).join('|')})`, 'gi')
|
|||
|
|
return textToHighlight.split(pattern)
|
|||
|
|
}, [searchWords, textToHighlight])
|
|||
|
|
|
|||
|
|
if (!parts) return <>{textToHighlight}</>
|
|||
|
|
|
|||
|
|
// split içindeki tek sayılı indeksler yakalanan gruplardır.
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
{parts.map((part, index) =>
|
|||
|
|
index % 2 === 1 ? (
|
|||
|
|
<mark key={index} className={highlightClassName}>
|
|||
|
|
{part}
|
|||
|
|
</mark>
|
|||
|
|
) : (
|
|||
|
|
<Fragment key={index}>{part}</Fragment>
|
|||
|
|
),
|
|||
|
|
)}
|
|||
|
|
</>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default Highlighter
|