InhoudToc

GitHub
Een kleverige inhoudsopgave met automatische actieve ankerlink-markering.
Dit onderdeel is alleen beschikbaar wanneer de @nuxt/content module is geïnstalleerd.

Gebruik

Gebruik de links prop met de page?.body?.toc?.links die je krijgt bij het ophalen van een pagina.

<script setup lang="ts">
import type { Collections } from '@nuxt/content'

const route = useRoute()
const { locale } = useI18n()

const contentPath = computed(() => {
  const path = route.path.replace(/\/$/, '')
  if (locale.value === 'en') return path
  return path.replace(`/${locale.value}`, '') || '/'
})

const { data: page } = await useAsyncData(
  `toc-${locale.value}-${contentPath.value}`,
  async () => {
    const collection = `docs_${locale.value}` as keyof Collections
    let content = await queryCollection(collection).path(contentPath.value).first()
    if (!content && locale.value !== 'en') {
      content = await queryCollection('docs_en').path(contentPath.value).first()
    }
    return content
  },
  { watch: [locale] }
)
if (!page.value) {
  throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
</script>

<template>
  <UContentToc :links="page?.body?.toc?.links" />
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { Collections } from '@nuxt/content'

const route = useRoute()
const { locale } = useI18n()

const contentPath = computed(() => {
  const path = route.path.replace(/\/$/, '')
  if (locale.value === 'en') return path
  return path.replace(`/${locale.value}`, '') || '/'
})

const { data: page } = await useAsyncData(
  `toc-${locale.value}-${contentPath.value}`,
  async () => {
    const collection = `docs_${locale.value}` as keyof Collections
    let content = await queryCollection(collection).path(contentPath.value).first()
    if (!content && locale.value !== 'en') {
      content = await queryCollection('docs_en').path(contentPath.value).first()
    }
    return content
  },
  { watch: [locale] }
)
if (!page.value) {
  throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
</script>

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

Titel

Gebruik de title prop om de titel van de inhoudsopgave te wijzigen.

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

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

<template>
  <UContentToc title="On this page" :links="links" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentTocLink } from '@nuxt/ui'

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

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

Kleur

Gebruik de color prop om de kleur van de links te veranderen.

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

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

<template>
  <UContentToc color="neutral" :links="links" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentTocLink } from '@nuxt/ui'

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

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

Hoogtepunt

Gebruik de highlight prop om een gemarkeerde rand voor het actieve item weer te geven.

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

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

<template>
  <UContentToc highlight :links="links" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentTocLink } from '@nuxt/ui'

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

<template>
  <UContentToc highlight :links="links" />
</template>

Highlight Kleur

Gebruik de highlight-color prop om de kleur van de highlight te veranderen. Standaard is dit de color prop.

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

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

<template>
  <UContentToc highlight highlight-color="neutral" :links="links" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentTocLink } from '@nuxt/ui'

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  }
])
</script>

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

Hoogtepunt Variant: badge{label="4.6+" class="align-text-top"}

Gebruik de highlight-variant-prop om de stijl van het hoogtepunt te wijzigen. Standaard op straight.

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

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  },
  {
    id: 'examples',
    depth: 2,
    text: 'Examples',
    children: [
      {
        id: 'within-a-page',
        depth: 3,
        text: 'Within a Page'
      }
    ]
  },
  {
    id: 'api',
    depth: 2,
    text: 'API',
    children: [
      {
        id: 'props',
        depth: 3,
        text: 'Props'
      },
      {
        id: 'slots',
        depth: 3,
        text: 'Slots'
      },
      {
        id: 'emits',
        depth: 3,
        text: 'Emits'
      }
    ]
  },
  {
    id: 'theme',
    depth: 2,
    text: 'Theme'
  }
])
</script>

<template>
  <UContentToc highlight highlight-color="primary" highlight-variant="circuit" :links="links" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ContentTocLink } from '@nuxt/ui'

const links = ref<ContentTocLink[]>([
  {
    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: 'highlight-color',
        depth: 3,
        text: 'Highlight Color'
      },
      {
        id: 'highlight-variant',
        depth: 3,
        text: 'Highlight Variant'
      }
    ]
  },
  {
    id: 'examples',
    depth: 2,
    text: 'Examples',
    children: [
      {
        id: 'within-a-page',
        depth: 3,
        text: 'Within a Page'
      }
    ]
  },
  {
    id: 'api',
    depth: 2,
    text: 'API',
    children: [
      {
        id: 'props',
        depth: 3,
        text: 'Props'
      },
      {
        id: 'slots',
        depth: 3,
        text: 'Slots'
      },
      {
        id: 'emits',
        depth: 3,
        text: 'Emits'
      }
    ]
  },
  {
    id: 'theme',
    depth: 2,
    text: 'Theme'
  }
])
</script>

<template>
  <UContentToc highlight highlight-color="primary" highlight-variant="circuit" :links="links" />
</template>

Voorbeelden

Binnen een pagina

Gebruik de ContentToc-component op een pagina om de inhoudsopgave weer te geven:

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>
  <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 Default Type
as
'nav'
any

The element or component this component should render as.

trailingIcon
appConfig.ui.icons.chevronDown
any

The icon displayed to collapse the content.

title
t('contentToc.title')
string

The title of the table of contents.

color
'primary'
"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
highlight
false
boolean

Display a line next to the active link.

highlightColor
'primary'
"primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral"
highlightVariant
'straight'
"straight" | "circuit"

The variant of the highlight indicator.

links
T[]
defaultOpen
boolean

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

open
boolean

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

ui
{ root?: SlotClass; container?: SlotClass; top?: SlotClass; bottom?: SlotClass; trigger?: SlotClass; title?: SlotClass; trailing?: SlotClass; trailingIcon?: SlotClass; content?: SlotClass; list?: SlotClass; listWithChildren?: SlotClass; item?: SlotClass; itemWithChildren?: SlotClass; link?: SlotClass; linkText?: SlotClass; indicator?: SlotClass; indicatorLine?: SlotClass; indicatorActive?: SlotClass; }

Slots

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

Uitzendt

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

Thema

app.config.ts
export default defineAppConfig({
  ui: {
    contentToc: {
      slots: {
        root: 'sticky top-(--ui-header-height) z-10 bg-default/75 lg:bg-[initial] backdrop-blur-sm -mx-4 px-4 sm:px-6 sm:-mx-6 lg:ms-0 flex flex-col 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 lg:min-h-0',
        top: 'lg:shrink-0',
        bottom: 'hidden lg:flex lg:flex-col lg:shrink-0 gap-6',
        trigger: 'group text-sm font-semibold flex-1 flex items-center gap-1.5 py-1.5 -mt-1.5 rounded-sm outline-primary/25 focus-visible:outline-3 lg:shrink-0',
        title: 'truncate',
        trailing: 'ms-auto inline-flex gap-1.5 items-center',
        trailingIcon: 'size-5 transform transition-transform duration-200 ease-out motion-reduce:transition-none shrink-0 group-data-[state=open]:rotate-180 lg:hidden',
        content: 'relative data-[state=open]:animate-[collapsible-down_200ms_var(--ease-out)] data-[state=closed]:animate-[collapsible-up_200ms_var(--ease-out)] data-[state=closed]:overflow-hidden focus:outline-none lg:min-h-[min(var(--list-height,8rem),8rem)] lg:overflow-y-auto lg:scrollbar-none',
        list: 'min-w-0',
        listWithChildren: 'ms-3',
        item: 'min-w-0',
        itemWithChildren: '',
        link: 'group relative text-sm flex items-center rounded-sm outline-primary/25 focus-visible:outline-3 py-1',
        linkText: 'truncate',
        indicator: '',
        indicatorLine: '',
        indicatorActive: ''
      },
      variants: {
        color: {
          primary: '',
          secondary: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        highlightColor: {
          primary: {
            indicatorActive: 'bg-primary'
          },
          secondary: {
            indicatorActive: 'bg-secondary'
          },
          success: {
            indicatorActive: 'bg-success'
          },
          info: {
            indicatorActive: 'bg-info'
          },
          warning: {
            indicatorActive: 'bg-warning'
          },
          error: {
            indicatorActive: 'bg-error'
          },
          neutral: {
            indicatorActive: 'bg-inverted'
          }
        },
        active: {
          false: {
            link: [
              'text-muted hover:text-default',
              'transition-colors'
            ]
          }
        },
        highlight: {
          true: ''
        },
        highlightVariant: {
          straight: '',
          circuit: ''
        },
        body: {
          true: {
            bottom: 'mt-6'
          }
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          active: true,
          class: {
            link: 'text-primary'
          }
        },
        {
          color: 'neutral',
          active: true,
          class: {
            link: 'text-highlighted'
          }
        },
        {
          highlight: true,
          highlightVariant: 'straight',
          class: {
            list: 'ms-2.5 ps-4 border-s border-default',
            item: '-ms-px',
            indicator: 'absolute ms-2.5 transition-[translate,height] duration-200 ease-out motion-reduce:transition-none h-(--indicator-size) translate-y-(--indicator-position) w-px rounded-full',
            indicatorLine: 'hidden',
            indicatorActive: 'w-full h-full'
          }
        },
        {
          highlight: true,
          highlightVariant: 'circuit',
          class: {
            list: 'ps-6.5',
            item: '-ms-px',
            itemWithChildren: 'ps-px',
            indicator: 'absolute ms-2.5 start-0 top-0 rtl:-scale-x-100',
            indicatorLine: 'absolute inset-0 bg-(--ui-border)',
            indicatorActive: 'absolute w-full h-(--indicator-size) translate-y-(--indicator-position) transition-[translate,height] duration-200 ease-out motion-reduce:transition-none'
          }
        }
      ],
      defaultVariants: {
        color: 'primary',
        highlightColor: 'primary',
        highlightVariant: 'straight'
      }
    }
  }
})
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({
      ui: {
        contentToc: {
          slots: {
            root: 'sticky top-(--ui-header-height) z-10 bg-default/75 lg:bg-[initial] backdrop-blur-sm -mx-4 px-4 sm:px-6 sm:-mx-6 lg:ms-0 flex flex-col 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 lg:min-h-0',
            top: 'lg:shrink-0',
            bottom: 'hidden lg:flex lg:flex-col lg:shrink-0 gap-6',
            trigger: 'group text-sm font-semibold flex-1 flex items-center gap-1.5 py-1.5 -mt-1.5 rounded-sm outline-primary/25 focus-visible:outline-3 lg:shrink-0',
            title: 'truncate',
            trailing: 'ms-auto inline-flex gap-1.5 items-center',
            trailingIcon: 'size-5 transform transition-transform duration-200 ease-out motion-reduce:transition-none shrink-0 group-data-[state=open]:rotate-180 lg:hidden',
            content: 'relative data-[state=open]:animate-[collapsible-down_200ms_var(--ease-out)] data-[state=closed]:animate-[collapsible-up_200ms_var(--ease-out)] data-[state=closed]:overflow-hidden focus:outline-none lg:min-h-[min(var(--list-height,8rem),8rem)] lg:overflow-y-auto lg:scrollbar-none',
            list: 'min-w-0',
            listWithChildren: 'ms-3',
            item: 'min-w-0',
            itemWithChildren: '',
            link: 'group relative text-sm flex items-center rounded-sm outline-primary/25 focus-visible:outline-3 py-1',
            linkText: 'truncate',
            indicator: '',
            indicatorLine: '',
            indicatorActive: ''
          },
          variants: {
            color: {
              primary: '',
              secondary: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            highlightColor: {
              primary: {
                indicatorActive: 'bg-primary'
              },
              secondary: {
                indicatorActive: 'bg-secondary'
              },
              success: {
                indicatorActive: 'bg-success'
              },
              info: {
                indicatorActive: 'bg-info'
              },
              warning: {
                indicatorActive: 'bg-warning'
              },
              error: {
                indicatorActive: 'bg-error'
              },
              neutral: {
                indicatorActive: 'bg-inverted'
              }
            },
            active: {
              false: {
                link: [
                  'text-muted hover:text-default',
                  'transition-colors'
                ]
              }
            },
            highlight: {
              true: ''
            },
            highlightVariant: {
              straight: '',
              circuit: ''
            },
            body: {
              true: {
                bottom: 'mt-6'
              }
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              active: true,
              class: {
                link: 'text-primary'
              }
            },
            {
              color: 'neutral',
              active: true,
              class: {
                link: 'text-highlighted'
              }
            },
            {
              highlight: true,
              highlightVariant: 'straight',
              class: {
                list: 'ms-2.5 ps-4 border-s border-default',
                item: '-ms-px',
                indicator: 'absolute ms-2.5 transition-[translate,height] duration-200 ease-out motion-reduce:transition-none h-(--indicator-size) translate-y-(--indicator-position) w-px rounded-full',
                indicatorLine: 'hidden',
                indicatorActive: 'w-full h-full'
              }
            },
            {
              highlight: true,
              highlightVariant: 'circuit',
              class: {
                list: 'ps-6.5',
                item: '-ms-px',
                itemWithChildren: 'ps-px',
                indicator: 'absolute ms-2.5 start-0 top-0 rtl:-scale-x-100',
                indicatorLine: 'absolute inset-0 bg-(--ui-border)',
                indicatorActive: 'absolute w-full h-(--indicator-size) translate-y-(--indicator-position) transition-[translate,height] duration-200 ease-out motion-reduce:transition-none'
              }
            }
          ],
          defaultVariants: {
            color: 'primary',
            highlightColor: 'primary',
            highlightVariant: 'straight'
          }
        }
      }
    })
  ]
})
Some colors in compoundVariants are omitted for readability. Check out the source code on GitHub.

Wijzigingsgelog

No recent changes