/** * 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, useMemo } from "react"; import { Tooltip } from "../components/tooltip/Tooltip"; import { useMemoDebugger } from "../hooks/useEffectDebugger"; import type { ChildProp, CssProp, CustomColors } from "../types"; import { getColor, getTooltipProps } from "../utils"; interface AbbrTextProps { children: string; color?: string; tooltip?: string; content?: string; } function AbbrText({ color, children, tooltip, content }: AbbrTextProps) { const TooltipWrapper = tooltip ? Tooltip : Fragment; const tooltipProps = useMemo(() => { return tooltip ? { ...getTooltipProps(tooltip), copyContent: content } : {}; }, [tooltip, content]); return ( {children} ); } export interface AbbrProps extends ChildProp, CssProp { /** * content to copy using the tooltip copy action */ copyContent?: "text" | "tooltip"; /** * texts to match and abbreviate within * * [textPart, tooltip, color (class name or color string)] */ abbr: Array<[textPart: string, tooltip: string, color?: CustomColors]>; /** * renderer callback */ renderer?: (part: string[]) => React.ReactElement; } /** * A component that renders abbreviated text with tooltips for additional information. * It allows you to define a list of abbreviations, each with a tooltip and optional color. * The component will tokenize the children text and replace matching parts with the abbreviated version. * It supports a copy action that can either copy the text or the tooltip content. * This component is useful for displaying technical terms or acronyms in a user-friendly way, * while providing additional context through tooltips. * * @example * ```jsx * * Learn more about HTML, CSS, and JS. * * // Renders the text with abbreviations for HTML, CSS, and JS, * // each with a tooltip showing the full form and colored according to the specified color. * ``` */ export function Abbr({ children, abbr, className, renderer, copyContent = "text" }: AbbrProps) { /** ***************** abbr text renderer *******************/ const abbrRender = useCallback( (text: string, tooltip: string, color = "") => { return ( {(renderer?.([text, tooltip, color]) ?? text) as string} ); }, [renderer, copyContent], ); /** ***************** tokenize text with abbr list *******************/ const inner = useMemoDebugger( () => { if (isString(children)) { if (!isEmpty(abbr)) { const tokens = tokenize( children, abbr.map(([keyword]) => keyword), ); const titles = abbr.reduce>( (t, [a, tooltip = "", color = ""]) => ({ ...t, [a.toLowerCase()]: { tooltip, color, }, }), {}, ); return ( {tokens.map(([start, text], i) => { const { tooltip = "", color = "" } = titles[text.toLowerCase()] ?? {}; return ( // eslint-disable-next-line @eslint-react/no-array-index-key {start} {text ? abbrRender(text, tooltip, color) : null} ); })} ); } } return children; }, [children, abbr, abbrRender], "TextAbbr inner", ); /** ***************** component *******************/ return {inner}; }