import Image from "next/image"; import type { ComponentPropsWithoutRef, ReactNode } from "react"; import { Callout } from "./callout"; import { CodeBlock } from "./code-block"; /** * MDX 组件映射。 * * 通过替换原生元素,把 DESIGN.md 的排版规则与可访问性要求统一注入正文: * - img → next/image(图片优化 + 保留 alt) * - pre → 带语言标签与复制按钮的代码块 * - a → 外链自动补 rel/target * * 类型说明:这里不使用 `mdx/types` 的 MDXComponents,因为它依赖可选安装的 * @types/mdx。改为按组件签名手写映射类型,避免引入额外类型包。 */ type MdxImageProps = ComponentPropsWithoutRef<"img">; type MdxAnchorProps = ComponentPropsWithoutRef<"a"> & { children?: ReactNode }; /** * MDX 组件映射类型。 * * `next-mdx-remote` 内部使用 `MDXComponents`(带字符串索引签名), * 因此这里显式声明索引签名,保证类型兼容的同时仍然约束了我们实际用到的键。 */ export interface MdxComponentMap { // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; img: (props: MdxImageProps) => ReactNode; a: (props: MdxAnchorProps) => ReactNode; pre: typeof CodeBlock; Callout: typeof Callout; } export const mdxComponents: MdxComponentMap = { // 文章内图片改用 next/image,alt 缺失时降级为空字符串(装饰性图片) img: ({ src, alt, width, height }) => { if (typeof src !== "string") { return null; } return ( {alt ); }, // 外链统一加上安全属性;站内锚点(# 开头)与相对路径保持原样 a: ({ href, children, ...props }) => { const isExternal = typeof href === "string" && /^https?:\/\//.test(href); return ( {children} ); }, pre: CodeBlock, Callout, };