UNPKG

@medalsocial/meda

Version:

Shared Meda UI shell and runtime package.

57 lines (56 loc) 3.79 kB
'use client'; import { jsx as _jsx } from "react/jsx-runtime"; import ReactMarkdown from 'react-markdown'; import rehypeHighlight from 'rehype-highlight'; import remarkGfm from 'remark-gfm'; import { cn } from '../lib/utils.js'; // remark-gfm renders task-list `[ ]` / `[x]` as unlabelled disabled checkboxes, // which fails the axe `label` rule. Override the `input` renderer to add an // aria-label derived from the checked state — keeps the checkbox semantics // for AT users while satisfying the contrast/label gate. const DEFAULT_COMPONENTS = { input: ({ type, checked, ...rest }) => { if (type === 'checkbox') { return (_jsx("input", { type: "checkbox", checked: checked, "aria-label": checked ? 'task complete' : 'task incomplete', ...rest })); } return _jsx("input", { type: type, ...rest }); }, }; /** * MarkdownView wraps `react-markdown` with `remark-gfm` and `rehype-highlight`, * and applies a consistent prose stylesheet using meda design tokens. * * The markdown libraries are listed as **optional peer dependencies**. Install * them in your app if you import this component: * * ```bash * pnpm add react-markdown remark-gfm rehype-highlight * ``` */ export function MarkdownView({ children, className, components, ...props }) { return (_jsx("div", { "data-slot": "markdown-view", className: cn( // Base prose 'text-sm leading-relaxed text-foreground', // Trim outer block margins: only the first/last *child* of the // wrapper should lose its top/bottom margin. The earlier shape // `first:[&_h1]:mt-0` selects all descendant h1s when the wrapper // itself is :first-child of its parent — which then collapses the // margin on every nested h1, not just the leading one. '[&>*:first-child]:mt-0 [&>*:last-child]:mb-0', // Headings '[&_h1]:mt-6 [&_h1]:mb-3 [&_h1]:text-xl [&_h1]:font-semibold [&_h1]:text-foreground', '[&_h2]:mt-5 [&_h2]:mb-2 [&_h2]:text-lg [&_h2]:font-semibold [&_h2]:text-foreground', '[&_h3]:mt-4 [&_h3]:mb-2 [&_h3]:text-base [&_h3]:font-semibold [&_h3]:text-foreground', '[&_h4]:mt-3 [&_h4]:mb-1.5 [&_h4]:text-sm [&_h4]:font-semibold [&_h4]:text-foreground', // Paragraphs and inline '[&_p]:my-3', '[&_strong]:font-semibold [&_strong]:text-foreground', '[&_em]:italic', '[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2 hover:[&_a]:no-underline', // Lists '[&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-5 [&_ul]:space-y-1', '[&_ol]:my-3 [&_ol]:list-decimal [&_ol]:pl-5 [&_ol]:space-y-1', '[&_li]:text-foreground', // Blockquote '[&_blockquote]:my-3 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground', // Code '[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.875em] [&_code]:text-foreground', '[&_pre]:my-3 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-border [&_pre]:bg-muted [&_pre]:p-3', '[&_pre_code]:bg-transparent [&_pre_code]:p-0', // Tables (GFM) '[&_table]:my-3 [&_table]:w-full [&_table]:border-collapse [&_table]:text-sm', '[&_th]:border [&_th]:border-border [&_th]:bg-muted [&_th]:px-2 [&_th]:py-1 [&_th]:text-left [&_th]:font-semibold', '[&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1', // Horizontal rule '[&_hr]:my-6 [&_hr]:border-border', // Images '[&_img]:my-3 [&_img]:rounded-md [&_img]:border [&_img]:border-border', className), ...props, children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], rehypePlugins: [rehypeHighlight], components: { ...DEFAULT_COMPONENTS, ...components }, children: children }) })); }