ContentTocPRO

一个带有自动激活锚链接高亮功能的固定式目录。
此组件仅在安装了 @nuxt/content 模块后可用。

用法

使用 links prop,传入获取页面时得到的 page?.body?.toc?.links

<script setup lang="ts">
const route = useRoute()

const { data: page } = await useAsyncData(route.path, () => queryCollection('content').path(route.path).first())
if (!page.value) {
  throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
</script>

<template>
  <UContentToc :links="page?.body?.toc?.links" />
</template>

标题

使用 title prop 来更改目录的标题。

<script setup lang="ts">
const links = ref([
  {
    id: 'usage',
    depth: 2,
    text: 'Usage',
    children: [
      {
        id: 'title',
        depth: 3,
        text: 'Title'
      },
      {
        id: 'color',
        depth: 3,
        text: 'Color'
      },
      {
        id: 'highlight',
        depth: 3,
        text: 'Highlight'
      }
    ]
  },
  {
    id: 'api',
    depth: 2,
    text: 'API',
    children: [
      {
        id: 'props',
        depth: 3,
        text: 'Props'
      },
      {
        id: 'slots',
        depth: 3,
        text: 'Slots'
      }
    ]
  },
  {
    id: 'theme',
    depth: 2,
    text: 'Theme'
  }
])
</script>

<template>
  <UContentToc title="On this page" :links="links" />
</template>

颜色

使用 color prop 来更改链接的颜色。

<script setup lang="ts">
const links = ref([
  {
    id: 'usage',
    depth: 2,
    text: 'Usage',
    children: [
      {
        id: 'title',
        depth: 3,
        text: 'Title'
      },
      {
        id: 'color',
        depth: 3,
        text: 'Color'
      },
      {
        id: 'highlight',
        depth: 3,
        text: 'Highlight'
      }
    ]
  }
])
</script>

<template>
  <UContentToc color="neutral" :links="links" />
</template>

高亮

使用 highlight prop 为活动项显示高亮边框。

使用 highlight-color prop 来更改边框的颜色。默认为 color prop 的值。

<script setup lang="ts">
const links = ref([
  {
    id: 'usage',
    depth: 2,
    text: 'Usage',
    children: [
      {
        id: 'title',
        depth: 3,
        text: 'Title'
      },
      {
        id: 'color',
        depth: 3,
        text: 'Color'
      },
      {
        id: 'highlight',
        depth: 3,
        text: 'Highlight'
      }
    ]
  }
])
</script>

<template>
  <UContentToc highlight highlight-color="neutral" color="neutral" :links="links" />
</template>

示例

在页面内

在页面中使用 ContentToc 组件来显示目录

pages/[...slug].vue
<script setup lang="ts">
const route = useRoute()

const { data: page } = await useAsyncData(route.path, () => queryCollection('content').path(route.path).first())
if (!page.value) {
  throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
</script>

<template>
  <UPage v-if="page">
    <UPageHeader :title="page.title" />

    <UPageBody>
      <ContentRenderer v-if="page.body" :value="page" />

      <USeparator v-if="surround?.filter(Boolean).length" />

      <UContentSurround :surround="(surround as any)" />
    </UPageBody>

    <template v-if="page?.body?.toc?.links?.length" #right>
      <UContentToc :links="page.body.toc.links" />
    </template>
  </UPage>
</template>

API

属性 (Props)

属性 (Prop)默认值类型
as

'nav'

any

此组件应渲染成的元素或组件。

trailingIcon

appConfig.ui.icons.chevronDown

string

折叠内容时显示的图标。

title

t('contentToc.title')

string

目录的标题。

color

'primary'

"error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"

highlight

false

boolean

在活动链接旁边显示一条线。

highlightColor

'primary'

"error" | "primary" | "secondary" | "success" | "info" | "warning" | "neutral"

links

ContentTocLink[]

open

boolean

可折叠组件的受控打开状态。可以使用 v-model 进行绑定。

defaultOpen

boolean

可折叠组件在初次渲染时的打开状态。
在你不需要控制其打开状态时使用。

ui

{ root?: ClassNameValue; container?: ClassNameValue; top?: ClassNameValue; bottom?: ClassNameValue; trigger?: ClassNameValue; ... 10 more ...; indicator?: ClassNameValue; }

插槽 (Slots)

插槽 (Slot)类型
leading

{ open: boolean; }

default

{ open: boolean; }

trailing

{ open: boolean; }

content

{ links: ContentTocLink[]; }

link

{ link: ContentTocLink; }

top

{ links?: ContentTocLink[] | undefined; }

bottom

{ links?: ContentTocLink[] | undefined; }

事件 (Emits)

事件 (Event)类型
update:open

boolean

move

string

主题

app.config.ts
export default defineAppConfig({
  uiPro: {
    contentToc: {
      slots: {
        root: 'sticky top-(--ui-header-height) bg-default/75 lg:bg-[initial] backdrop-blur -mx-4 px-4 sm:px-6 sm:-mx-6 overflow-y-auto max-h-[calc(100vh-var(--ui-header-height))]',
        container: 'pt-4 sm:pt-6 pb-2.5 sm:pb-4.5 lg:py-8 border-b border-dashed border-default lg:border-0 flex flex-col',
        top: '',
        bottom: 'mt-6 hidden lg:flex lg:flex-col gap-6',
        trigger: 'group text-sm font-semibold flex-1 flex items-center gap-1.5 py-1.5 -mt-1.5 focus-visible:outline-primary',
        title: 'truncate',
        trailing: 'ms-auto inline-flex gap-1.5 items-center',
        trailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180 lg:hidden',
        content: 'data-[state=open]:animate-[collapsible-down_200ms_ease-out] data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden focus:outline-none',
        list: 'min-w-0',
        listWithChildren: 'ms-3',
        item: 'min-w-0',
        itemWithChildren: '',
        link: 'group relative text-sm flex items-center focus-visible:outline-primary py-1',
        linkText: 'truncate',
        indicator: 'absolute ms-2.5 transition-[translate,height] duration-200 h-(--indicator-size) translate-y-(--indicator-position) w-px rounded-full'
      },
      variants: {
        color: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        highlightColor: {
          primary: {
            indicator: 'bg-primary'
          },
          secondary: {
            indicator: 'bg-secondary'
          },
          success: {
            indicator: 'bg-success'
          },
          info: {
            indicator: 'bg-info'
          },
          warning: {
            indicator: 'bg-warning'
          },
          error: {
            indicator: 'bg-error'
          },
          neutral: {
            indicator: 'bg-inverted'
          }
        },
        active: {
          false: {
            link: [
              'text-muted hover:text-default',
              'transition-colors'
            ]
          }
        },
        highlight: {
          true: {
            list: 'ms-2.5 ps-4 border-s border-default',
            item: '-ms-px'
          }
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          active: true,
          class: {
            link: 'text-primary',
            linkLeadingIcon: 'text-primary'
          }
        },
        {
          color: 'neutral',
          active: true,
          class: {
            link: 'text-highlighted',
            linkLeadingIcon: 'text-highlighted'
          }
        }
      ],
      defaultVariants: {
        color: 'primary',
        highlightColor: 'primary'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      uiPro: {
        contentToc: {
          slots: {
            root: 'sticky top-(--ui-header-height) bg-default/75 lg:bg-[initial] backdrop-blur -mx-4 px-4 sm:px-6 sm:-mx-6 overflow-y-auto max-h-[calc(100vh-var(--ui-header-height))]',
            container: 'pt-4 sm:pt-6 pb-2.5 sm:pb-4.5 lg:py-8 border-b border-dashed border-default lg:border-0 flex flex-col',
            top: '',
            bottom: 'mt-6 hidden lg:flex lg:flex-col gap-6',
            trigger: 'group text-sm font-semibold flex-1 flex items-center gap-1.5 py-1.5 -mt-1.5 focus-visible:outline-primary',
            title: 'truncate',
            trailing: 'ms-auto inline-flex gap-1.5 items-center',
            trailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180 lg:hidden',
            content: 'data-[state=open]:animate-[collapsible-down_200ms_ease-out] data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden focus:outline-none',
            list: 'min-w-0',
            listWithChildren: 'ms-3',
            item: 'min-w-0',
            itemWithChildren: '',
            link: 'group relative text-sm flex items-center focus-visible:outline-primary py-1',
            linkText: 'truncate',
            indicator: 'absolute ms-2.5 transition-[translate,height] duration-200 h-(--indicator-size) translate-y-(--indicator-position) w-px rounded-full'
          },
          variants: {
            color: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            highlightColor: {
              primary: {
                indicator: 'bg-primary'
              },
              secondary: {
                indicator: 'bg-secondary'
              },
              success: {
                indicator: 'bg-success'
              },
              info: {
                indicator: 'bg-info'
              },
              warning: {
                indicator: 'bg-warning'
              },
              error: {
                indicator: 'bg-error'
              },
              neutral: {
                indicator: 'bg-inverted'
              }
            },
            active: {
              false: {
                link: [
                  'text-muted hover:text-default',
                  'transition-colors'
                ]
              }
            },
            highlight: {
              true: {
                list: 'ms-2.5 ps-4 border-s border-default',
                item: '-ms-px'
              }
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              active: true,
              class: {
                link: 'text-primary',
                linkLeadingIcon: 'text-primary'
              }
            },
            {
              color: 'neutral',
              active: true,
              class: {
                link: 'text-highlighted',
                linkLeadingIcon: 'text-highlighted'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            highlightColor: 'primary'
          }
        }
      }
    })
  ]
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import uiPro from '@nuxt/ui-pro/vite'

export default defineConfig({
  plugins: [
    vue(),
    uiPro({
      uiPro: {
        contentToc: {
          slots: {
            root: 'sticky top-(--ui-header-height) bg-default/75 lg:bg-[initial] backdrop-blur -mx-4 px-4 sm:px-6 sm:-mx-6 overflow-y-auto max-h-[calc(100vh-var(--ui-header-height))]',
            container: 'pt-4 sm:pt-6 pb-2.5 sm:pb-4.5 lg:py-8 border-b border-dashed border-default lg:border-0 flex flex-col',
            top: '',
            bottom: 'mt-6 hidden lg:flex lg:flex-col gap-6',
            trigger: 'group text-sm font-semibold flex-1 flex items-center gap-1.5 py-1.5 -mt-1.5 focus-visible:outline-primary',
            title: 'truncate',
            trailing: 'ms-auto inline-flex gap-1.5 items-center',
            trailingIcon: 'size-5 transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180 lg:hidden',
            content: 'data-[state=open]:animate-[collapsible-down_200ms_ease-out] data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden focus:outline-none',
            list: 'min-w-0',
            listWithChildren: 'ms-3',
            item: 'min-w-0',
            itemWithChildren: '',
            link: 'group relative text-sm flex items-center focus-visible:outline-primary py-1',
            linkText: 'truncate',
            indicator: 'absolute ms-2.5 transition-[translate,height] duration-200 h-(--indicator-size) translate-y-(--indicator-position) w-px rounded-full'
          },
          variants: {
            color: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            highlightColor: {
              primary: {
                indicator: 'bg-primary'
              },
              secondary: {
                indicator: 'bg-secondary'
              },
              success: {
                indicator: 'bg-success'
              },
              info: {
                indicator: 'bg-info'
              },
              warning: {
                indicator: 'bg-warning'
              },
              error: {
                indicator: 'bg-error'
              },
              neutral: {
                indicator: 'bg-inverted'
              }
            },
            active: {
              false: {
                link: [
                  'text-muted hover:text-default',
                  'transition-colors'
                ]
              }
            },
            highlight: {
              true: {
                list: 'ms-2.5 ps-4 border-s border-default',
                item: '-ms-px'
              }
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              active: true,
              class: {
                link: 'text-primary',
                linkLeadingIcon: 'text-primary'
              }
            },
            {
              color: 'neutral',
              active: true,
              class: {
                link: 'text-highlighted',
                linkLeadingIcon: 'text-highlighted'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            highlightColor: 'primary'
          }
        }
      }
    })
  ]
})
为了提高可读性,compoundVariants 中的一些颜色被省略。请在 GitHub 上查看源代码。