78 lines
3.5 KiB
TypeScript
78 lines
3.5 KiB
TypeScript
import * as React from "react";
|
|
import { cn } from "@/components/ui/utils";
|
|
|
|
interface FormattedDescriptionProps {
|
|
content: string;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* FormattedDescription Component
|
|
*
|
|
* Renders HTML content with proper styling for lists, tables, and other formatted content.
|
|
* Use this component to display descriptions that may contain HTML formatting.
|
|
*/
|
|
export function FormattedDescription({ content, className }: FormattedDescriptionProps) {
|
|
const processedContent = React.useMemo(() => {
|
|
if (!content) return '';
|
|
|
|
// Wrap tables that aren't already wrapped in a scrollable container using regex
|
|
// Match <table> tags that aren't already inside a .table-wrapper
|
|
let processed = content;
|
|
|
|
// Pattern to match table tags that aren't already wrapped
|
|
const tablePattern = /<table[^>]*>[\s\S]*?<\/table>/gi;
|
|
|
|
processed = processed.replace(tablePattern, (match) => {
|
|
// Check if this table is already wrapped
|
|
if (match.includes('table-wrapper')) {
|
|
return match;
|
|
}
|
|
|
|
// Wrap the table in a scrollable container
|
|
return `<div class="table-wrapper">${match}</div>`;
|
|
});
|
|
|
|
return processed;
|
|
}, [content]);
|
|
|
|
if (!content) return null;
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"text-sm text-gray-700 max-w-none",
|
|
// Horizontal scrolling for smaller screens
|
|
"overflow-x-auto",
|
|
"md:overflow-x-visible",
|
|
// Lists
|
|
"[&_ul]:list-disc [&_ul]:ml-6 [&_ul]:my-2 [&_ul]:list-outside",
|
|
"[&_ol]:list-decimal [&_ol]:ml-6 [&_ol]:my-2 [&_ol]:list-outside",
|
|
"[&_li]:my-1 [&_li]:pl-2",
|
|
// Table wrapper for scrolling
|
|
"[&_.table-wrapper]:overflow-x-auto [&_.table-wrapper]:max-w-full [&_.table-wrapper]:my-2 [&_.table-wrapper]:-mx-2 [&_.table-wrapper]:px-2",
|
|
"[&_.table-wrapper_table]:border-collapse [&_.table-wrapper_table]:border [&_.table-wrapper_table]:border-gray-300 [&_.table-wrapper_table]:min-w-full",
|
|
"[&_.table-wrapper_table_td]:border [&_.table-wrapper_table_td]:border-gray-300 [&_.table-wrapper_table_td]:px-3 [&_.table-wrapper_table_td]:py-2 [&_.table-wrapper_table_td]:text-sm [&_.table-wrapper_table_td]:whitespace-nowrap",
|
|
"[&_.table-wrapper_table_th]:border [&_.table-wrapper_table_th]:border-gray-300 [&_.table-wrapper_table_th]:px-3 [&_.table-wrapper_table_th]:py-2 [&_.table-wrapper_table_th]:bg-gray-50 [&_.table-wrapper_table_th]:font-semibold [&_.table-wrapper_table_th]:text-sm [&_.table-wrapper_table_th]:text-left [&_.table-wrapper_table_th]:whitespace-nowrap",
|
|
"[&_.table-wrapper_table_tr:nth-child(even)]:bg-gray-50",
|
|
// Direct table styles (fallback for tables not wrapped)
|
|
"[&_table]:border-collapse [&_table]:my-2 [&_table]:border [&_table]:border-gray-300",
|
|
"[&_table_td]:border [&_table_td]:border-gray-300 [&_table_td]:px-3 [&_table_td]:py-2 [&_table_td]:text-sm",
|
|
"[&_table_th]:border [&_table_th]:border-gray-300 [&_table_th]:px-3 [&_table_th]:py-2 [&_table_th]:bg-gray-50 [&_table_th]:font-semibold [&_table_th]:text-sm [&_table_th]:text-left",
|
|
"[&_table_tr:nth-child(even)]:bg-gray-50",
|
|
// Text formatting
|
|
"[&_p]:my-1 [&_p]:leading-relaxed",
|
|
"[&_strong]:font-bold",
|
|
"[&_em]:italic",
|
|
"[&_u]:underline",
|
|
"[&_h1]:text-xl [&_h1]:font-bold [&_h1]:my-2",
|
|
"[&_h2]:text-lg [&_h2]:font-semibold [&_h2]:my-2",
|
|
"[&_h3]:text-base [&_h3]:font-semibold [&_h3]:my-1",
|
|
className
|
|
)}
|
|
dangerouslySetInnerHTML={{ __html: processedContent }}
|
|
/>
|
|
);
|
|
}
|
|
|