104 lines
3.2 KiB
TypeScript
104 lines
3.2 KiB
TypeScript
import React from "react";
|
||
import { parseTextMarkup, hasTextMarkup, type TextMarkupSegment } from "@/lib/text-markup";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
interface MarkupTextProps {
|
||
children: string;
|
||
className?: string;
|
||
as?: keyof React.JSX.IntrinsicElements;
|
||
boldClassName?: string;
|
||
strikeClassName?: string;
|
||
}
|
||
|
||
/**
|
||
* Компонент для рендеринга текста с разметкой **bold** и ~~strike~~
|
||
*
|
||
* Примеры использования:
|
||
* <MarkupText>Добро пожаловать в **WitLab**!</MarkupText>
|
||
* <MarkupText as="h1">**50%** скидка только сегодня</MarkupText>
|
||
* <MarkupText boldClassName="text-primary">Ваш **идеальный партнер** найден!</MarkupText>
|
||
*/
|
||
export function MarkupText({
|
||
children,
|
||
className,
|
||
as: Component = "span",
|
||
boldClassName = "font-bold",
|
||
strikeClassName = "line-through"
|
||
}: MarkupTextProps) {
|
||
// Если текста нет, возвращаем пустой элемент
|
||
if (!children || typeof children !== 'string') {
|
||
return React.createElement(Component as string, { className }, children);
|
||
}
|
||
|
||
// Если нет разметки, возвращаем обычный текст
|
||
if (!hasTextMarkup(children)) {
|
||
return React.createElement(Component as string, { className }, children);
|
||
}
|
||
|
||
// Парсим разметку и рендерим сегменты
|
||
const segments = parseTextMarkup(children);
|
||
|
||
return React.createElement(
|
||
Component as string,
|
||
{ className },
|
||
segments.map((segment: TextMarkupSegment, index: number) => {
|
||
if (segment.type === 'bold') {
|
||
return React.createElement(
|
||
'strong',
|
||
{
|
||
key: index,
|
||
className: cn(boldClassName)
|
||
},
|
||
segment.content
|
||
);
|
||
}
|
||
if (segment.type === 'strike') {
|
||
return React.createElement(
|
||
'span',
|
||
{
|
||
key: index,
|
||
className: cn(strikeClassName)
|
||
},
|
||
segment.content
|
||
);
|
||
}
|
||
|
||
return React.createElement(
|
||
React.Fragment,
|
||
{ key: index },
|
||
segment.content
|
||
);
|
||
})
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Хук для проверки наличия разметки в тексте
|
||
*/
|
||
export function useHasMarkup(text: string): boolean {
|
||
return React.useMemo(() => hasTextMarkup(text), [text]);
|
||
}
|
||
|
||
/**
|
||
* Компонент для превью разметки в админке
|
||
*/
|
||
export function MarkupPreview({ text }: { text: string }) {
|
||
return (
|
||
<div className="space-y-2">
|
||
<div className="text-xs font-medium text-muted-foreground">
|
||
Превью:
|
||
</div>
|
||
<div className="p-3 bg-muted/30 rounded-lg border">
|
||
<MarkupText className="text-sm">
|
||
{text}
|
||
</MarkupText>
|
||
</div>
|
||
{hasTextMarkup(text) && (
|
||
<div className="text-xs text-blue-600 bg-blue-50 border border-blue-200 rounded p-2">
|
||
💡 <strong>Разметка обнаружена:</strong> Текст в **двойных звездочках** — жирный, в ~~двойных тильдах~~ — зачёркнутый.
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|