rayine-ui/docs/components/Toc.vue

92 lines
2.1 KiB
Vue

<script setup lang="ts">
import { ref, computed } from "vue";
interface TocItem {
id: string;
depth: number;
text: string;
children?: TocItem[];
}
const props = defineProps<{
toc: TocItem[];
maxDepth?: number;
}>();
const maxDepth = props.maxDepth ?? 3;
const filteredToc = computed(() => {
const filterByDepth = (items: TocItem[]): TocItem[] => {
return items
.filter(item => item.depth <= maxDepth)
.map(item => ({
...item,
children: item.children ? filterByDepth(item.children) : undefined,
}));
};
return filterByDepth(props.toc);
});
const activeLinks = ref<string[]>([]);
onMounted(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
activeLinks.value.push(entry.target.id);
} else {
activeLinks.value = activeLinks.value.filter(link => link !== entry.target.id);
}
});
},
// { rootMargin: '0px 0px -80% 0px' }
);
filteredToc.value.forEach(item => {
const element = document.getElementById(item.id);
if (element) {
observer.observe(element);
}
item.children?.forEach(child => {
const childElement = document.getElementById(child.id);
if (childElement) {
observer.observe(childElement);
}
});
});
});
</script>
<template>
<ul>
<template v-for="item in filteredToc" :key="item.id">
<li>
<NuxtLink :href="'#' + item.id" class="link" :class="{ 'active': activeLinks.includes(item.id) }">
{{ item.text }}
</NuxtLink>
<ul v-if="item.children && item.children.length" class="ml-4">
<template v-for="child in item.children" :key="child.id">
<li>
<NuxtLink :href="'#' + child.id" class="link" :class="{ 'active': activeLinks.includes(child.id) }">
{{ child.text }}
</NuxtLink>
</li>
</template>
</ul>
</li>
</template>
</ul>
</template>
<style scoped>
.link {
@apply text-xs text-neutral-400 dark:text-neutral-500 font-medium;
}
.link.active {
@apply text-primary;
}
</style>