代码内联图标 定制主题

这是该模板独有的附加功能,通过自定义主题样式 + 定制组件,实现代码块内图标。

使用方法

Tip

该功能 默认开启,可根据需求自由隐藏图标

代码块标题

一个基础的代码块:

mdx
```js title="foo.js"
console.log('Hello World');
```

它将被渲染为:

foo.js
js
console.log('Hello World');
最关键的一点

注册图标配置:

theme/icon.less
less
// 针对代码块 title 添加图标
.rp-codeblock__title{
  // #set-icon.lang 是我包装的 less 选择器
  #set-icon.lang(js; var(--icon-js));
}

添加 Tabs 图标

foo.js
bar.ts
src/foo.js
js
console.log('foo');
注册关键配置
theme/icon.less
less
// 针对 Tabs 的 label 添加图标
.rp-tabs__label__item{
  // #set-icon.inline 是我包装的 less 选择器
  #set-icon.inline(js; var(--icon-js));
  #set-icon.inline(ts; var(--icon-ts));
}

// 针对代码块 title 添加图标
.rp-codeblock__title{
  // tab-lang 是针对 Tabs 容器下的代码块 title 进行 [ 修改 | 添加 ] 图标
  #set-icon.tab-lang(js; var(--icon-js-path)); // 可以不设置
}

隐藏图标 3种方法

  • 通过在父元素中添加类名 remove-title-iconremove-label-icon 来分别隐藏代码块标题和 Tabs 图标。

  • 或通过在 title 中添加字符串 --remove-icon 来单独隐藏标题图标。

    md
    ```js title="foo.js --remove-icon"
    console.log('Hello World');
    ```

    它将被渲染为:

    foo.js
    js
    console.log('Hello World');
  • 或通过在 Tab 的 label prop 中添加字符串 --remove-icon 来单独隐藏 label 图标。

    mdx
    <Tabs>
    
      // label 添加 --remove-icon
      <Tab label="foo.js --remove-icon">
    
      ```js title="src/foo.js"
      console.log('foo');
      ```
    
      </Tab>
      <Tab label="bar.ts">
    
      ```ts
      console.log('bar');
      ```
    
      </Tab>
    </Tabs>
    foo.js
    bar.ts
    src/foo.js
    js
    console.log('foo');

隐藏语言小标签

位于代码块右上角有一个语言小标签
  • 同理,父元素添加 remove-code-lang 类名来隐藏语言小标签。
  • 或通过在 title 中添加字符串 --remove-lang 来单独隐藏标题语言小标签。
    foo.js
    js
    console.log('Hello World');

定制主题源码

theme/index.tsx
tsx
import { 
  NavHamburger as BasicNavHamburger, 
  Layout as BasicLayout,
  Tabs as BasicTabs, type TabsProps,
  CodeBlock as BasicCodeBlock, type CodeBlockProps,
} from '@rspress/core/theme-original';
import './index.less';
import './icon.less';

import { type ReactNode } from 'react';
import { CssModificationProvider } from '@docs/CssModificationContext';
import { CssStyleSync } from '@docs/CssStyleSync';


// 代码块添加自定义的小语言标签
function CodeBlock(_props: CodeBlockProps) {
  const { children, ...props } = _props;
  // 通过给 title 添加额外字符串参数移除图标和语言标签
  const removeIconKeyword = '--remove-icon'
  const removeLangKeyword = '--remove-lang'
  // 基础样式
  let className = 'nbc-lang rp-copy-ignore'

  // 判断 title 额外字符串参数
  if(props.title) {
    if(props.title.includes(removeIconKeyword)) {
      props.title = props.title.replace(removeIconKeyword, '')
      className += ' remove-nbc-icon'
    }
    if(props.title.includes(removeLangKeyword)) {
      props.title = props.title.replace(removeLangKeyword, '')
      className += ' remove-nbc-lang'
    }
  }

  // 默认 txt 语言不显示语言标签
  return (
    <BasicCodeBlock {...props} >
      <span className={ className }>{ props.lang !== 'txt' && props.lang }</span>
      {children}
    </BasicCodeBlock>
  )
}

// Tab 类型定义
type TabItem = {
  label: ReactNode;
  value: string;
  disabled?: boolean;
};

// 支持 tab 图标自定义组件
function Tabs(_props: TabsProps){
  const { children, values, ...props } = _props;

  // 只针对数组, 毕竟越复杂越容易出错
  if(!values && Array.isArray(children)){
    const values: TabItem[] = []
    children.forEach( item => {
      // 判断是否移除 label 图标的后缀
      const suffix = '--remove-icon';
      const defaultValue: undefined | string = item?.props?.value
      const label:string = item?.props?.label
      // 逆天, 但没办法
      const lang:string = item?.props?.children?.props?.children?.props?.lang

      // label 明示移除图标参数
      if(label && label.includes(suffix)){
        const mainLabel = label.replace(suffix, '').trim();
        values.push({
          label: mainLabel,
          value: defaultValue ?? mainLabel,
        })
        return
      }

      const mainLabel = lang ? 
        <span className={`icon-${lang}`}>{label}</span>
        : label
      
      // 正常的 Tab
      values.push({
        label: mainLabel,
        value: defaultValue ?? label,
      })
    })
    // 返回自定义图标组件
    return (
      <BasicTabs {...props} values={values}>{children}</BasicTabs>
    )
  }
  // 兜底默认
  return (
    <BasicTabs {...props} values={values}>{children}</BasicTabs>
  )
}

// TODO 如果用不到 CSSPickerEditor 组件的话可以删
// 基础布局添加上 css 上下文
function Layout() {
  return (
    <CssModificationProvider>
      <CssStyleSync />
      <BasicLayout />
    </CssModificationProvider>
  )
}


// 自己的 hamburger 按钮
function NavHamburger(){
  return (
    <div className="nbc-nav-container">
      <BasicNavHamburger/>
      <div className="nbc-button-container">
        <span className="nbc-top" />
        <span className="nbc-middle" />
        <span className="nbc-bottom" />
      </div>
    </div>
  );
}


export { NavHamburger, Layout, Tabs, CodeBlock }
export * from '@rspress/core/theme-original';