{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "highlight",
  "title": "Highlight",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/highlight/Highlight.tsx",
      "content": "import * as React from 'react'\nimport { cn } from '@/lib/utils'\n\nexport interface HighlightProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {\n  /** Text to search within. */\n  text: string\n  /** Query string or RegExp to highlight. */\n  query: string | RegExp\n  /** HTML tag used to wrap matched substrings. Default 'mark'. */\n  highlightTag?: 'mark' | 'span'\n  /** Class applied to each highlight wrapper. */\n  highlightClass?: React.HTMLAttributes<HTMLSpanElement>['className']\n  /** Inline style applied to each highlight wrapper. */\n  highlightStyle?: React.CSSProperties\n  /** Case-sensitive matching. Default false. */\n  caseSensitive?: boolean\n  /** Match whole words only. Default false. */\n  wholeWord?: boolean\n  /** Cap the number of highlights rendered. 0 = unlimited. Default 0. */\n  maxHighlights?: number\n  /** Fired with the number of highlights actually rendered (after maxHighlights cap). */\n  onMatchCount?: (count: number) => void\n  /** Fired with the total match count (before maxHighlights cap). */\n  onTotalMatchCount?: (count: number) => void\n}\n\ninterface Segment {\n  text: string\n  match: boolean\n}\n\nfunction buildPattern(\n  query: string | RegExp,\n  caseSensitive: boolean,\n  wholeWord: boolean,\n): RegExp | null {\n  if (query instanceof RegExp) {\n    const flags = query.flags.includes('g') ? query.flags : query.flags + 'g'\n    return new RegExp(query.source, flags)\n  }\n  if (!query) return null\n  const escaped = query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n  const body = wholeWord ? `\\\\b${escaped}\\\\b` : escaped\n  const flags = caseSensitive ? 'g' : 'gi'\n  return new RegExp(body, flags)\n}\n\nconst Highlight = React.forwardRef<HTMLSpanElement, HighlightProps>(\n  (\n    {\n      className,\n      text,\n      query,\n      highlightTag = 'mark',\n      highlightClass,\n      highlightStyle,\n      caseSensitive = false,\n      wholeWord = false,\n      maxHighlights = 0,\n      onMatchCount,\n      onTotalMatchCount,\n      ...props\n    },\n    ref,\n  ) => {\n    const segments = React.useMemo<Segment[]>(() => {\n      if (!text) return []\n      if (!query) return [{ text, match: false }]\n\n      const pattern = buildPattern(query, caseSensitive, wholeWord)\n      if (!pattern) return [{ text, match: false }]\n\n      const out: Segment[] = []\n      let last = 0\n      let count = 0\n      let m: RegExpExecArray | null\n      while ((m = pattern.exec(text)) !== null) {\n        if (m.index > last) out.push({ text: text.slice(last, m.index), match: false })\n        out.push({ text: m[0], match: true })\n        last = m.index + m[0].length\n        count++\n        if (maxHighlights > 0 && count >= maxHighlights) break\n        if (m[0] === '') pattern.lastIndex++\n      }\n      if (last < text.length) out.push({ text: text.slice(last), match: false })\n\n      return out\n    }, [text, query, caseSensitive, wholeWord, maxHighlights])\n\n    const totalMatchCount = React.useMemo(() => {\n      if (!text || !query) return 0\n      const pattern = buildPattern(query, caseSensitive, wholeWord)\n      if (!pattern) return 0\n\n      let total = 0\n      let m: RegExpExecArray | null\n      while ((m = pattern.exec(text)) !== null) {\n        total++\n        if (m[0] === '') pattern.lastIndex++\n      }\n      return total\n    }, [text, query, caseSensitive, wholeWord])\n\n    const renderedMatchCount = React.useMemo(\n      () => segments.filter((s) => s.match).length,\n      [segments],\n    )\n\n    React.useEffect(() => {\n      onMatchCount?.(renderedMatchCount)\n    }, [renderedMatchCount, onMatchCount])\n\n    React.useEffect(() => {\n      onTotalMatchCount?.(totalMatchCount)\n    }, [totalMatchCount, onTotalMatchCount])\n\n    const Tag = highlightTag\n\n    return (\n      <span ref={ref} data-uipkge=\"\" data-slot=\"highlight\" className={cn(className)} {...props}>\n        {segments.map((seg, i) =>\n          seg.match ? (\n            <Tag\n              key={i}\n              data-slot=\"highlight-match\"\n              className={cn(\n                'bg-yellow-200 text-yellow-950 dark:bg-yellow-500/30 dark:text-yellow-100 rounded px-0.5 font-medium',\n                highlightClass,\n              )}\n              style={highlightStyle}\n            >\n              {seg.text}\n            </Tag>\n          ) : (\n            <React.Fragment key={i}>{seg.text}</React.Fragment>\n          ),\n        )}\n      </span>\n    )\n  },\n)\nHighlight.displayName = 'Highlight'\n\nexport { Highlight }\n",
      "type": "registry:ui",
      "target": "~/components/ui/highlight/Highlight.tsx"
    },
    {
      "path": "packages/registry-react/components/highlight/index.ts",
      "content": "export { Highlight, type HighlightProps } from './Highlight'\n",
      "type": "registry:ui",
      "target": "~/components/ui/highlight/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Highlights matching substrings in text for search results. Supports string or regex queries, case-sensitive and whole-word matching, custom highlight tag, and a max-highlight cap.",
  "categories": [
    "display",
    "utility"
  ]
}