{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "markdown",
  "title": "Markdown",
  "description": "An answer's markdown: prose, lists, quotes, tables and code blocks with a copy button.",
  "dependencies": [
    "@stylexjs/stylex",
    "@tanstack/markdown"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json",
    "https://constella.aayush.cv/r/code.json",
    "https://constella.aayush.cv/r/code-block.json",
    "https://constella.aayush.cv/r/copy-button.json",
    "https://constella.aayush.cv/r/link.json",
    "https://constella.aayush.cv/r/table.json"
  ],
  "files": [
    {
      "path": "src/components/ai/markdown.tsx",
      "content": "\"use client\";\n\nimport * as stylex from \"@stylexjs/stylex\";\nimport { streamingMarkdownExtension } from \"@tanstack/markdown/extensions/streaming\";\nimport { Markdown as MarkdownPrimitive } from \"@tanstack/markdown/react\";\nimport type {\n  MarkdownComponentProps,\n  MarkdownComponents,\n  MarkdownProps as MarkdownPrimitiveProps,\n} from \"@tanstack/markdown/react\";\nimport { useRef } from \"react\";\n\nimport {\n  CodeBlock as CodeBlockRoot,\n  CodeBlockActions,\n  CodeBlockContent,\n} from \"@/components/ai/code-block\";\nimport { CopyButton } from \"@/components/primitives/copy-button\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/primitives/table\";\nimport { Code } from \"@/components/typography/code\";\nimport { Link } from \"@/components/typography/link\";\nimport type { Sx } from \"@/lib/constella/styles\";\nimport { typeStyles } from \"@/lib/constella/styles\";\nimport {\n  colors,\n  fontWeights,\n  space,\n  strokes,\n} from \"@/lib/constella/tokens.stylex\";\n\ntype MarkdownProps = Omit<MarkdownPrimitiveProps, \"extensions\"> & {\n  extensions?: MarkdownPrimitiveProps[\"extensions\"];\n  /** While the answer is still arriving: unclosed fences, lists and emphasis render as if closed. */\n  streaming?: boolean;\n  sx?: Sx;\n};\n\ntype Props<T extends keyof React.JSX.IntrinsicElements> =\n  MarkdownComponentProps<T>;\n\nconst styles = stylex.create({\n  root: {\n    color: colors.textPrimary,\n    minWidth: 0,\n    overflowWrap: \"break-word\",\n  },\n  // Blocks space by collapsing margins, so the root stays a block box, not flex or grid.\n  block: {\n    marginBlockEnd: { default: \"1em\", \":last-child\": 0 },\n    marginBlockStart: 0,\n  },\n  heading: {\n    fontWeight: fontWeights.semibold,\n    marginBlockEnd: { default: \"0.5em\", \":last-child\": 0 },\n    marginBlockStart: { default: \"1.5em\", \":first-child\": 0 },\n    textWrap: \"balance\",\n  },\n  strong: { fontWeight: fontWeights.semibold },\n  list: {\n    marginBlockStart: { default: 0, \":is(li > *)\": \"0.5em\" },\n    paddingInlineStart: \"1.25em\",\n  },\n  // Its padding, not the list's, sets the marker's distance from the text.\n  item: {\n    marginBlockStart: { default: \"0.5em\", \":first-child\": 0 },\n    paddingInlineStart: \"0.25em\",\n    \"::marker\": { color: colors.textSecondary },\n  },\n  quote: {\n    borderInlineStartColor: colors.border,\n    borderInlineStartStyle: \"solid\",\n    borderInlineStartWidth: `calc(2 * ${strokes.border})`,\n    color: colors.textSecondary,\n    marginInlineStart: 0,\n    marginInlineEnd: 0,\n    paddingInlineStart: \"1em\",\n  },\n  rule: {\n    borderBlockEndWidth: 0,\n    borderBlockStartColor: colors.borderSubtle,\n    borderBlockStartStyle: \"solid\",\n    borderBlockStartWidth: strokes.border,\n    borderInlineStartWidth: 0,\n    borderInlineEndWidth: 0,\n    marginBlockEnd: { default: \"1em\", \":last-child\": 0 },\n    marginBlockStart: { default: \"1em\", \":first-child\": 0 },\n    width: \"100%\",\n  },\n  // The table has no box, so its text lines up with the prose.\n  cell: {\n    paddingInlineEnd: { default: space.xs, \":last-child\": 0 },\n    paddingInlineStart: { default: space.xs, \":first-child\": 0 },\n  },\n  start: { textAlign: \"start\" },\n  center: { textAlign: \"center\" },\n  end: { textAlign: \"end\" },\n});\n\n// A GFM column's alignment arrives only as an inline style on each cell.\nconst alignStyles = {\n  center: styles.center,\n  left: styles.start,\n  right: styles.end,\n} as const;\n\nfunction alignOf(style: Props<\"td\">[\"style\"]) {\n  const align = style?.textAlign;\n  return align === \"left\" || align === \"center\" || align === \"right\"\n    ? alignStyles[align]\n    : undefined;\n}\n\n// Only a fenced block's `code` carries a `language-*` class. CodeBlockContent renders that `code`, so the highlighted markup goes in a span.\nfunction MarkdownCode({ className, ...props }: Props<\"code\">) {\n  if (className?.startsWith(\"language-\") ?? false) {\n    return <span {...props} />;\n  }\n  return <Code {...props} />;\n}\n\nfunction CodeBlock({ className: _className, ...props }: Props<\"pre\">) {\n  const pre = useRef<HTMLPreElement>(null);\n  return (\n    // The margin goes on a wrapper: `sx` takes only space tokens, and here 1em is the prose's.\n    <div {...stylex.props(styles.block)}>\n      <CodeBlockRoot>\n        {/* Dropping the class keeps `data-lang`, which a highlight theme keys on. */}\n        <CodeBlockContent ref={pre} {...props} />\n        <CodeBlockActions>\n          <CopyButton\n            aria-label=\"Copy code\"\n            value={() => pre.current?.textContent?.trimEnd() ?? \"\"}\n          />\n        </CodeBlockActions>\n      </CodeBlockRoot>\n    </div>\n  );\n}\n\nfunction LastHeading({ children, ...props }: Props<\"h6\">) {\n  return (\n    <h6 {...props} {...stylex.props(styles.heading, typeStyles.textLg)}>\n      {children}\n    </h6>\n  );\n}\n\n// An answer sits under the page's own headings, so its outline starts two levels down.\nconst components = {\n  a: ({ children, ...props }: Props<\"a\">) => (\n    <Link {...props} rel=\"noopener noreferrer\" target=\"_blank\">\n      {children}\n    </Link>\n  ),\n  blockquote: (props: Props<\"blockquote\">) => (\n    <blockquote {...props} {...stylex.props(styles.block, styles.quote)} />\n  ),\n  code: MarkdownCode,\n  h1: ({ children, ...props }: Props<\"h1\">) => (\n    <h3 {...props} {...stylex.props(styles.heading, typeStyles.heading6)}>\n      {children}\n    </h3>\n  ),\n  h2: ({ children, ...props }: Props<\"h2\">) => (\n    <h4 {...props} {...stylex.props(styles.heading, typeStyles.textLg)}>\n      {children}\n    </h4>\n  ),\n  h3: ({ children, ...props }: Props<\"h3\">) => (\n    <h5 {...props} {...stylex.props(styles.heading, typeStyles.textLg)}>\n      {children}\n    </h5>\n  ),\n  h4: LastHeading,\n  h5: LastHeading,\n  h6: LastHeading,\n  hr: (props: Props<\"hr\">) => <hr {...props} {...stylex.props(styles.rule)} />,\n  li: (props: Props<\"li\">) => <li {...props} {...stylex.props(styles.item)} />,\n  ol: (props: Props<\"ol\">) => (\n    <ol {...props} {...stylex.props(styles.block, styles.list)} />\n  ),\n  p: (props: Props<\"p\">) => <p {...props} {...stylex.props(styles.block)} />,\n  pre: CodeBlock,\n  strong: (props: Props<\"strong\">) => (\n    <strong {...props} {...stylex.props(styles.strong)} />\n  ),\n  // A margin on the table would stay inside its scroll container.\n  table: (props: Props<\"table\">) => (\n    <div {...stylex.props(styles.block)}>\n      <Table {...props} />\n    </div>\n  ),\n  tbody: (props: Props<\"tbody\">) => <TableBody {...props} />,\n  td: ({ style, ...props }: Props<\"td\">) => (\n    <TableCell {...props} sx={[styles.cell, alignOf(style)]} />\n  ),\n  th: ({ style, ...props }: Props<\"th\">) => (\n    <TableHead {...props} sx={[styles.cell, alignOf(style)]} />\n  ),\n  thead: (props: Props<\"thead\">) => <TableHeader {...props} />,\n  tr: (props: Props<\"tr\">) => <TableRow {...props} />,\n  ul: (props: Props<\"ul\">) => (\n    <ul {...props} {...stylex.props(styles.block, styles.list)} />\n  ),\n} satisfies MarkdownComponents;\n\nconst noExtensions: NonNullable<MarkdownProps[\"extensions\"]> = [];\nconst streamingExtensions = [streamingMarkdownExtension()];\n\n/** An answer's markdown: prose, lists, quotes, tables on `Table`, and code blocks with a copy button. */\nfunction Markdown({\n  components: own,\n  extensions = noExtensions,\n  streaming = false,\n  sx,\n  ...props\n}: MarkdownProps) {\n  return (\n    <div\n      data-slot=\"markdown\"\n      {...stylex.props([typeStyles.textBase, styles.root], sx)}\n    >\n      <MarkdownPrimitive\n        components={{ ...components, ...own }}\n        extensions={\n          streaming ? [...streamingExtensions, ...extensions] : extensions\n        }\n        {...props}\n      />\n    </div>\n  );\n}\n\nexport { Markdown };\nexport type { MarkdownProps };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/markdown/index.md",
  "type": "registry:ui"
}