zufrieden

GitHub
Ein klebriges Inhaltsverzeichnis mit automatischer Hervorhebung aktiver Ankerlinks.
Diese Komponente ist nur verfügbar, wenn das Modul @nuxt/content installiert ist.

Usage (Verwendung)

Verwenden Sie die links-Prop mit der page?.body?.toc?.links, die Sie beim Abrufen einer Seite erhalten.

<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

Verwenden Sie die title-prop, um den Titel des Inhaltsverzeichnisses zu ändern.

<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>

Farbe

Verwenden Sie die color-Prop, um die Farbe der Links zu ändern.

<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>

Highlight (Englisch)

Verwenden Sie die highlight-Stütze, um einen hervorgehobenen Rahmen für das aktive Element anzuzeigen.

<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 Color (Deutsche Ausgabe)

Verwenden Sie die highlight-color-prop, um die Farbe der Markierung zu ändern. Es wird standardmäßig die color-prop verwendet.

<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>

Highlight-Variante: badge{label="4.6+" class="align-text-top"}

Verwenden Sie die highlight-variant-prop, um den Stil des highlights zu ändern. Standardmäßig auf 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>

Examples (Beispiele)

innerhalb einer Seite

Verwenden Sie die ContentToc-Komponente in einer Seite, um das Inhaltsverzeichnis anzuzeigen:

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 Bearbeiten

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 Bearbeiten

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; }

Emits (englisch)

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

Theme Bearbeiten

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.

Changelog (englisch)

No recent changes