docu v 1.11.0
This commit is contained in:
2
hooks/index.ts
Normal file
2
hooks/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './useScrollPosition';
|
||||
export * from './useActiveSection';
|
||||
68
hooks/useActiveSection.ts
Normal file
68
hooks/useActiveSection.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { TocItem } from '@/lib/toc';
|
||||
|
||||
export function useActiveSection(tocs: TocItem[]) {
|
||||
const [activeId, setActiveId] = useState<string | null>(null);
|
||||
const observerRef = useRef<IntersectionObserver | null>(null);
|
||||
const clickedIdRef = useRef<string | null>(null);
|
||||
|
||||
// Handle intersection observer for active section
|
||||
useEffect(() => {
|
||||
if (typeof document === 'undefined' || !tocs.length) return;
|
||||
|
||||
const handleIntersect = (entries: IntersectionObserverEntry[]) => {
|
||||
if (clickedIdRef.current) return;
|
||||
|
||||
const visibleEntries = entries.filter(entry => entry.isIntersecting);
|
||||
if (!visibleEntries.length) return;
|
||||
|
||||
// Find the most visible entry
|
||||
const mostVisibleEntry = visibleEntries.reduce((prev, current) => {
|
||||
return current.intersectionRatio > prev.intersectionRatio ? current : prev;
|
||||
}, visibleEntries[0]);
|
||||
|
||||
const newActiveId = mostVisibleEntry.target.id;
|
||||
if (newActiveId !== activeId) {
|
||||
setActiveId(newActiveId);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize intersection observer
|
||||
observerRef.current = new IntersectionObserver(handleIntersect, {
|
||||
root: null,
|
||||
rootMargin: '0px 0px -80% 0px',
|
||||
threshold: 0.1,
|
||||
});
|
||||
|
||||
// Observe all headings
|
||||
tocs.forEach(toc => {
|
||||
const element = document.getElementById(toc.href.slice(1));
|
||||
if (element) {
|
||||
observerRef.current?.observe(element);
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
observerRef.current?.disconnect();
|
||||
};
|
||||
}, [tocs, activeId]);
|
||||
|
||||
const handleLinkClick = useCallback((id: string) => {
|
||||
clickedIdRef.current = id;
|
||||
setActiveId(id);
|
||||
|
||||
// Reset clicked state after scroll completes
|
||||
const timer = setTimeout(() => {
|
||||
clickedIdRef.current = null;
|
||||
}, 1000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
activeId,
|
||||
setActiveId,
|
||||
handleLinkClick,
|
||||
};
|
||||
}
|
||||
28
hooks/useScrollPosition.ts
Normal file
28
hooks/useScrollPosition.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
|
||||
export function useScrollPosition(threshold = 0.5) {
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const scrollPosition = window.scrollY;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const shouldBeSticky = scrollPosition > viewportHeight * threshold;
|
||||
|
||||
setIsScrolled(prev => shouldBeSticky !== prev ? shouldBeSticky : prev);
|
||||
}, [threshold]);
|
||||
|
||||
// Add scroll event listener
|
||||
useEffect(() => {
|
||||
// Initial check
|
||||
handleScroll();
|
||||
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, [handleScroll]);
|
||||
|
||||
return isScrolled;
|
||||
}
|
||||
Reference in New Issue
Block a user