import { cn } from "@/registry/lib/utils"
/**
* Prose styles the HTML that a markdown renderer produces. Drop the rendered
* output inside and every element (headings, paragraphs, links, code, lists,
* blockquotes, tables) picks up the design system's tokens. It does not parse
* markdown; pair it with whatever renderer the app already uses.
*
* Spacing comes from `prose-flow` (vertical rhythm owned by the following
* element) and the block caps at the reading measure. Wide code blocks and
* tables scroll inside that measure rather than overflow the page. To opt out
* of the cap, pass `className="max-w-none"`.
*/
// Element styling. Grouped by element, each rule token-driven so a re-themed
// consumer inherits the change. Descendant selectors are the only way to reach
// HTML this component did not author, so the styling lives here, not on tags.
const proseStyles = [
// Headings: strong, balanced wrapping, tight leading. Real weights only.
"[&_h1]:text-2xl [&_h1]:font-semibold [&_h1]:leading-snug [&_h1]:text-foreground [&_h1]:text-pretty",
"[&_h2]:text-xl [&_h2]:font-semibold [&_h2]:leading-snug [&_h2]:text-foreground [&_h2]:text-pretty",
"[&_h3]:text-lg [&_h3]:font-semibold [&_h3]:leading-snug [&_h3]:text-foreground [&_h3]:text-pretty",
"[&_h4]:text-base [&_h4]:font-semibold [&_h4]:leading-snug [&_h4]:text-foreground [&_h4]:text-pretty",
"[&_h5]:text-sm [&_h5]:font-semibold [&_h5]:leading-snug [&_h5]:text-foreground [&_h5]:text-pretty",
"[&_h6]:text-sm [&_h6]:font-semibold [&_h6]:leading-snug [&_h6]:text-muted-foreground [&_h6]:text-pretty",
// No size here on purpose: paragraphs take the block's reading size from the
// root below, so the two cannot drift apart. Only the wrapping is set, to
// keep orphan words out. Color comes from the muted-foreground root;
// headings, bold, and links step back up to foreground/accent explicitly.
"[&_p]:text-pretty",
// Inline emphasis. Bold and italic use loaded weights, never synthesized.
"[&_strong]:font-semibold [&_strong]:text-foreground [&_b]:font-semibold [&_b]:text-foreground",
"[&_em]:italic [&_i]:italic",
"[&_del]:text-muted-foreground [&_del]:line-through [&_s]:text-muted-foreground [&_s]:line-through",
// Links: the kit's inline-link rule. Accent, underlined at rest, plain on
// hover; the underline pulls its weight from the font metrics.
"[&_a]:text-accent [&_a]:underline [&_a]:decoration-from-font [&_a]:underline-offset-2 [&_a]:rounded-sm [&_a:hover]:no-underline",
"[&_a:focus-visible]:ring-2 [&_a:focus-visible]:ring-ring-accent [&_a:focus-visible]:outline-none",
// Inline code: the same chip the docs site's `InlineCode` draws (an accent
// tint on accent-100 / accent-900, accent label, medium weight, 0.85em),
// so a code word inside rendered markdown and a prop name in an API table
// are one chip. It used to be a neutral chip that inherited the sentence's
// size, and the two read as two different systems on the same page. The
// recipe and its dark-mode numbers live on `InlineCode`; change them there
// first, then here, and keep the two identical. The pre reset below keeps
// this off code blocks.
"[&_:not(pre)>code]:rounded-sm [&_:not(pre)>code]:bg-accent-100 dark:[&_:not(pre)>code]:bg-accent-900 [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-[0.85em] [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:font-medium [&_:not(pre)>code]:text-accent dark:[&_:not(pre)>code]:text-accent-400",
// Code blocks: the Card tier (ringed surface, rounded-lg), matching the
// CodeBlock component. Wide lines scroll inside the block. The inner code
// resets to the code-block type (text-xs, relaxed leading).
"[&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:bg-card [&_pre]:p-4 [&_pre]:text-sm [&_pre]:shadow-ring-md",
"[&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:font-mono [&_pre_code]:font-normal [&_pre_code]:text-xs [&_pre_code]:leading-relaxed [&_pre_code]:text-inherit",
// Lists: logical inline padding (RTL-safe, tight so the marker sits close to
// the first word), muted markers matching the muted body, tight item gaps.
"[&_ul]:ps-4 [&_ul]:list-disc [&_ol]:ps-4 [&_ol]:list-decimal [&_li]:marker:text-muted-foreground",
"[&_li+li]:mt-2 [&_li>p]:m-0 [&_li>ul]:mt-2 [&_li>ol]:mt-2",
// A code block or table inside an item: `prose-flow` spaces figures only
// between the block's DIRECT children, so a nested one got no gap and its
// ringed edge sat on the line above it. 12px, a step past the 8px item gap,
// so the box reads as part of its item rather than a peer of the next one.
// On the element itself rather than as `* + pre`, because a tight list emits
// `<li>Lead-in:<pre>` and a text node is not a sibling a selector can see.
// The bottom margin collapses through the `li` into the next item's gap.
"[&_li>pre]:my-3 [&_li>table]:my-3",
// GFM task lists carry their own checkbox, so drop the bullet and the indent
// that a bullet would need.
"[&_ul:has(input[type=checkbox])]:list-none [&_ul:has(input[type=checkbox])]:ps-0 [&_li:has(input[type=checkbox])]:list-none",
// Blockquote: the kit's one set-apart-prose treatment (accent rule, indent,
// muted voice), the same chrome the docs site's Callout uses. Both exist
// because they reach different content: this one styles markdown a renderer
// produced, Callout is authored directly. The values have to stay identical.
"[&_blockquote]:border-s-2 [&_blockquote]:border-accent [&_blockquote]:ps-4 [&_blockquote]:text-muted-foreground",
"[&_blockquote>*]:m-0 [&_blockquote>*+*]:mt-3",
// Tables: mirror the Table component. Ringed card container, muted micro-cap
// header, muted-foreground cells on the 24/12 padding rhythm, last row
// unbordered. Sizes to content but never wider than the measure; scrolls
// within it on narrow screens.
"[&_table]:block [&_table]:w-max [&_table]:max-w-full [&_table]:overflow-x-auto [&_table]:rounded-md [&_table]:border [&_table]:border-border [&_table]:bg-card [&_table]:shadow-xs [&_table]:text-sm",
"[&_thead]:bg-muted [&_th]:h-11 [&_th]:px-6 [&_th]:text-start [&_th]:align-middle [&_th]:font-semibold [&_th]:text-2xs [&_th]:text-muted-foreground [&_th]:uppercase [&_th]:tracking-wide [&_th]:whitespace-nowrap [&_th]:border-b [&_th]:border-border",
"[&_td]:px-6 [&_td]:py-3 [&_td]:align-middle [&_td]:text-muted-foreground [&_td]:border-b [&_td]:border-border [&_tbody_tr:last-child_td]:border-0",
// Divider: a hairline on the border token, matching Separator.
"[&_hr]:h-px [&_hr]:border-0 [&_hr]:bg-border",
// Images: responsive, with the kit's neutral hairline outline.
"[&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-lg [&_img]:ring-1 [&_img]:ring-black/10 dark:[&_img]:ring-white/10",
].join(" ")
// @use-when rendered markdown, or any long-form HTML you did not author
// element by element.
function Prose({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="prose"
className={cn(
// text-base is the READING size for the whole block, so list items and
// blockquotes inherit it alongside paragraphs. Setting it on `p` alone
// would leave lists a step smaller than the prose around them.
// Descendants that are NOT reading copy (h5/h6, pre, table) keep their
// own smaller size explicitly below.
"prose-flow max-w-measure text-base text-muted-foreground break-words [font-synthesis:none]",
proseStyles,
className,
)}
{...props}
/>
)
}
export { Prose }