flowstate/app/components/ui/HighlightText.tsx
Chneemann a60851ac07
All checks were successful
Deploy Flowstate to VPS / deploy (push) Successful in 1m27s
refactor(lib): consolidate services, types, utils, and actions into lib folder and update import paths
2026-08-23 16:44:18 +02:00

56 lines
1.7 KiB
TypeScript

/**
* @file app/components/ui/HighlightText.tsx
* @description Component to highlight matching search query terms within a text string.
*/
/**
* Properties for the HighlightText component.
*
* @interface HighlightTextProps
* @property {string | null} [text] - The full text string to render and search within.
* @property {string} [query] - The search query term to highlight within the text.
* @property {string} [className] - Optional CSS classes applied to the wrapping container element.
*/
interface HighlightTextProps {
text?: string | null;
query?: string;
className?: string;
}
/**
* Renders a text string with case-insensitive highlighted search query matches wrapped in a marked element.
*
* @param {HighlightTextProps} props - The component props.
* @returns {JSX.Element | null} The rendered text component with highlighted query terms, or null if no text is provided.
*/
export default function HighlightText({
text,
query,
className = "",
}: HighlightTextProps) {
if (!text) return null;
if (!query || !query.trim()) {
return <span className={className}>{text}</span>;
}
// Regular expression with escaping for special characters, case-insensitive ('gi')
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const parts = text.split(new RegExp(`(${escapedQuery})`, "gi"));
return (
<span className={className}>
{parts.map((part, index) =>
part.toLowerCase() === query.toLowerCase() ? (
<mark
key={index}
className="bg-primary/20 text-primary font-semibold px-0.5 rounded"
>
{part}
</mark>
) : (
part
),
)}
</span>
);
}