/** * React Fabric * @version 1.0.0 * @license MIT * @copyright 2024 Adarsh Pastakia * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import { cn, isEmpty, isString, tokenize } from "@react-fabric/utilities"; import { Fragment, useCallback } from "react"; import { useMemoDebugger } from "../hooks/useEffectDebugger"; import type { ChildProp, CustomColors } from "../types"; import { getColor } from "../utils"; interface MarkTextProps { children: string; color?: string; } function MarkText({ color, children }: MarkTextProps) { return ( {children} ); } export interface MarkProps extends ChildProp { /** * texts to match and highlight within * * [textPart, color (class name or color string)] */ mark: Array<[textPart: string, color?: CustomColors]>; /** * renderer callback */ renderer?: (part: string[]) => React.ReactElement; } /** * A component that highlights specific text parts with a mark element, allowing for custom rendering and color. * It tokenizes the children string based on the provided `mark` array, * which contains pairs of text parts and their corresponding colors. * The component can use a custom renderer to display the marked text, * allowing for greater flexibility in how the text is presented. * * @example * * This is an important note that should be highlighted. * */ export function Mark({ children, mark, renderer }: MarkProps) { /** ***************** mark text renderer *******************/ const markRender = useCallback( (text: string, color = "") => { return {(renderer?.([text, color]) ?? text) as string}; }, [renderer], ); /** ***************** tokenize text with mark list *******************/ const inner = useMemoDebugger( () => { if (isString(children)) { if (!isEmpty(mark)) { const tokens = tokenize( children, mark.map(([keyword]) => keyword), ); const titles: KeyValue<{ color: string }> = mark.reduce>( (t, [a, color = ""]) => ({ ...t, [a.toLowerCase()]: { color }, }), {}, ); return ( {tokens.map(([start, text], i) => { const { color = "" } = titles[text.toLowerCase()] ?? {}; return ( // eslint-disable-next-line @eslint-react/no-array-index-key {start} {text ? markRender(text, color) : null} ); })} ); } } return children; }, [children, mark, markRender], "TextMark inner", ); /** ***************** component *******************/ return {inner}; }