{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "mark-searched-phrase", "title": "Mark Searched Phrase", "description": "Text element that applies style to text if provided value was found within it.", "files": [ { "path": "components/ui/mark-searched-phrase.tsx", "content": "import { cn } from \"@/lib/utils\";\r\nimport { useMemo, type ComponentPropsWithoutRef, type ElementType, type ReactNode } from \"react\";\r\n\r\ntype MarkSearchedPhraseProps = {\r\n text: string;\r\n searchTerm: string | string[];\r\n className?: string;\r\n markClassName?: string;\r\n as?: T;\r\n caseSensitive?: boolean;\r\n renderMatch?: (text: string, index: number) => ReactNode;\r\n} & Omit, 'as' | 'text' | 'searchTerm' | 'className' | 'markClassName' | 'caseSensitive' | 'renderMatch'>;\r\n\r\nexport function MarkSearchedPhrase({\r\n text,\r\n searchTerm,\r\n className,\r\n markClassName,\r\n as,\r\n caseSensitive = false,\r\n renderMatch,\r\n ...props\r\n}: MarkSearchedPhraseProps) {\r\n const Component = (as || 'p') as ElementType;\r\n\r\n // Normalize searchTerm to array\r\n const searchTerms = useMemo(\r\n () => Array.isArray(searchTerm) ? searchTerm.filter(term => term !== '') : searchTerm !== '' ? [searchTerm] : [],\r\n [searchTerm]\r\n );\r\n\r\n // Memoize regex for performance\r\n const regex = useMemo(() => {\r\n if (searchTerms.length === 0) return null;\r\n\r\n const escapedTerms = searchTerms.map(term =>\r\n term.replace(/[.*+?^${}()|[\\]\\\\]/g, String.raw`\\$&`)\r\n );\r\n const pattern = `(${escapedTerms.join('|')})`;\r\n const flags = caseSensitive ? 'g' : 'gi';\r\n return new RegExp(pattern, flags);\r\n }, [searchTerms, caseSensitive]);\r\n\r\n // Early return if no search terms\r\n if (!regex) {\r\n return {text};\r\n }\r\n\r\n // Check if text contains any search term\r\n const hasMatch = caseSensitive\r\n ? searchTerms.some(term => text.includes(term))\r\n : searchTerms.some(term => text.toLowerCase().includes(term.toLowerCase()));\r\n\r\n if (!hasMatch) {\r\n return {text};\r\n }\r\n\r\n const parts = text.split(regex);\r\n\r\n return (\r\n \r\n {parts.map((part, index) => {\r\n // Check if this part matches any search term\r\n const isMatch = searchTerms.some(term =>\r\n caseSensitive\r\n ? part === term\r\n : part.toLowerCase() === term.toLowerCase()\r\n );\r\n\r\n if (isMatch) {\r\n // Use custom render function if provided\r\n if (renderMatch) {\r\n return {renderMatch(part, index)};\r\n }\r\n\r\n // Use semantic element for accessibility\r\n return (\r\n \r\n {part}\r\n \r\n );\r\n }\r\n\r\n return {part};\r\n })}\r\n \r\n );\r\n}", "type": "registry:ui" } ], "type": "registry:ui" }