ContentToc

A sticky table of contents component with dynamic active section highlighting.
This component is only available when the @nuxt/content module is installed.

Usage

Use the links prop with the page?.body?.toc?.links you get when fetching a page.

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

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

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

Title

Use the title prop to change the title of the Table of Contents.

<script setup lang="ts">
import type { ContentTocLink } from '@bitrix24/b24ui-nuxt'

const links = ref<ContentTocLink[]>([
  {
    id: 'usage',
    depth: 2,
    text: 'Usage',
    children: [
      {
        id: 'title',
        depth: 3,
        text: 'Title'
      }
    ]
  },
  {
    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>
  <B24ContentToc title="On this page" :links="links" />
</template>

Examples

Within a page

Use the ContentToc component in a page to display the Table of Contents:

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

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

<template>
  <template v-if="page">
    <ProseH1>{{ page.title }}</ProseH1>

    <template v-if="page?.body?.toc?.links?.length">
      <B24ContentToc :links="page.body.toc.links" />
    </template>

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

      <B24Separator v-if="surround?.filter(Boolean).length" class="my-4" />

      <B24ContentSurround :surround="(surround as any)" />
    </div>
  </template>
</template>

API

Props

Prop Default Type
as'nav'any

The element or component this component should render as.

trailingIconicons.chevronDownIconComponent

The icon displayed to collapse the content.

titlet('contentToc.title') string

The title of the table of contents.

links T[]
defaultOpenboolean

The open state of the collapsible when it is initially rendered.
Use when you do not need to control its open state.

openboolean

The controlled open state of the collapsible. Can be binded with v-model.

b24ui { root?: ClassNameValue; container?: ClassNameValue; top?: ClassNameValue; bottom?: ClassNameValue; trigger?: ClassNameValue; title?: ClassNameValue; trailing?: ClassNameValue; trailingIcon?: ClassNameValue; content?: ClassNameValue; list?: ClassNameValue; listWithChildren?: ClassNameValue; item?: ClassNameValue; itemWithChildren?: ClassNameValue; link?: ClassNameValue; linkText?: ClassNameValue; }

Slots

Slot Type
leading{ open: boolean; b24ui: object; }
default{ open: boolean; }
trailing{ open: boolean; b24ui: object; }
content{ links: T[]; }
link{ link: T; }
top{ links?: T[] | undefined; }
bottom{ links?: T[] | undefined; }

Emits

Event Type
update:open[value: boolean]
move[id: string]

Theme

app.config.ts
export default defineAppConfig({
  b24ui: {
    contentToc: {
      slots: {
        root: '',
        container: 'border-dashed border-b-(length:--ui-border-width-thick) border-(--ui-color-divider-vibrant-default) lg:border-0 flex flex-col',
        top: '',
        bottom: 'hidden lg:flex lg:flex-col gap-[24px]',
        trigger: 'group pb-[12px] cursor-pointer lg:cursor-text focus-visible:outline-(--ui-color-accent-main-primary) focus-visible:outline-2 focus-visible:rounded-[4px] text-(--b24ui-typography-legend-color) text-(length:--ui-font-size-lg)/(--ui-font-line-height-3xs) font-(--ui-font-weight-semi-bold) flex-1 flex items-center gap-[6px]',
        title: 'line-clamp-1 lg:line-clamp-2',
        trailing: 'ms-auto inline-flex gap-[6px] items-center',
        trailingIcon: 'size-[20px] transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180 lg:hidden',
        content: 'relative motion-safe:data-[state=open]:animate-[collapsible-down_200ms_ease-out] motion-safe:data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden focus:outline-none',
        list: 'min-w-0',
        listWithChildren: 'ms-[12px]',
        item: 'min-w-0',
        itemWithChildren: '',
        link: 'group relative text-(length:--ui-font-size-lg)/(--ui-font-line-height-3xs) flex items-center focus-visible:outline-(--ui-color-accent-main-primary) focus-visible:outline-1 focus-visible:rounded-[4px] text-(--b24ui-typography-description-color) underline-offset-2 hover:text-(--b24ui-typography-label-color) hover:underline pb-[12px]',
        linkText: 'line-clamp-2'
      },
      variants: {
        active: {
          false: {
            link: 'text-(--b24ui-typography-description-color) hover:text-(--b24ui-typography-label-color) transition-colors'
          }
        },
        body: {
          true: {
            bottom: 'mt-6'
          }
        }
      },
      compoundVariants: [
        {
          active: true,
          class: {
            link: 'text-(--b24ui-typography-label-color)',
            linkLeadingIcon: 'text-(--b24ui-typography-label-color)'
          }
        }
      ],
      defaultVariants: {}
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import bitrix24UIPluginVite from '@bitrix24/b24ui-nuxt/vite'

export default defineConfig({
  plugins: [
    vue(),
    bitrix24UIPluginVite({
      b24ui: {
        contentToc: {
          slots: {
            root: '',
            container: 'border-dashed border-b-(length:--ui-border-width-thick) border-(--ui-color-divider-vibrant-default) lg:border-0 flex flex-col',
            top: '',
            bottom: 'hidden lg:flex lg:flex-col gap-[24px]',
            trigger: 'group pb-[12px] cursor-pointer lg:cursor-text focus-visible:outline-(--ui-color-accent-main-primary) focus-visible:outline-2 focus-visible:rounded-[4px] text-(--b24ui-typography-legend-color) text-(length:--ui-font-size-lg)/(--ui-font-line-height-3xs) font-(--ui-font-weight-semi-bold) flex-1 flex items-center gap-[6px]',
            title: 'line-clamp-1 lg:line-clamp-2',
            trailing: 'ms-auto inline-flex gap-[6px] items-center',
            trailingIcon: 'size-[20px] transform transition-transform duration-200 shrink-0 group-data-[state=open]:rotate-180 lg:hidden',
            content: 'relative motion-safe:data-[state=open]:animate-[collapsible-down_200ms_ease-out] motion-safe:data-[state=closed]:animate-[collapsible-up_200ms_ease-out] overflow-hidden focus:outline-none',
            list: 'min-w-0',
            listWithChildren: 'ms-[12px]',
            item: 'min-w-0',
            itemWithChildren: '',
            link: 'group relative text-(length:--ui-font-size-lg)/(--ui-font-line-height-3xs) flex items-center focus-visible:outline-(--ui-color-accent-main-primary) focus-visible:outline-1 focus-visible:rounded-[4px] text-(--b24ui-typography-description-color) underline-offset-2 hover:text-(--b24ui-typography-label-color) hover:underline pb-[12px]',
            linkText: 'line-clamp-2'
          },
          variants: {
            active: {
              false: {
                link: 'text-(--b24ui-typography-description-color) hover:text-(--b24ui-typography-label-color) transition-colors'
              }
            },
            body: {
              true: {
                bottom: 'mt-6'
              }
            }
          },
          compoundVariants: [
            {
              active: true,
              class: {
                link: 'text-(--b24ui-typography-label-color)',
                linkLeadingIcon: 'text-(--b24ui-typography-label-color)'
              }
            }
          ],
          defaultVariants: {}
        }
      }
    })
  ]
})
Releases
Published under MIT License.

Copyright © 2024-present Bitrix24