veza/apps/web/src/features/search/utils/highlightMatch.tsx
senke aeade50247 feat(ui): tooltip adoption + search highlighting & skeleton loading
Tooltip adoption (18 conversions across 11 files):
- Player controls: shuffle, repeat, mute, expand, close, lyrics, auto-scroll
- Navbar: theme toggle
- File browser: download, add tag, AI auto-tag, watermark, process with AI
- Notifications: mark as read
- Share links: open link, revoke link
- Chat: scroll to bottom

Search polish:
- New highlightMatch utility — wraps matching text in <mark> with primary color
- Applied to track titles, artist names, playlist names in SearchPageResults
- Applied to suggestion dropdown titles and subtitles
- Replaced spinner loading state with content-aware SearchPageSkeleton
- Skeleton matches actual results layout (tab bar, track cards, artist circles)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:14:00 +01:00

33 lines
826 B
TypeScript

import React from 'react';
/**
* Highlights portions of `text` that match `query` (case-insensitive).
*
* Returns the original string when there's nothing to highlight,
* or a ReactNode array with `<mark>` wrappers around matched fragments.
*/
export function highlightMatch(
text: string,
query: string,
): React.ReactNode {
if (!query.trim()) return text;
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escaped})`, 'gi');
const parts = text.split(regex);
if (parts.length === 1) return text;
return parts.map((part, i) =>
regex.test(part) ? (
<mark
key={i}
className="bg-primary/20 text-primary rounded-sm px-0.5"
>
{part}
</mark>
) : (
<React.Fragment key={i}>{part}</React.Fragment>
),
);
}