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>
33 lines
826 B
TypeScript
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>
|
|
),
|
|
);
|
|
}
|