Skip to content

Commit de4708b

Browse files
committed
feat: Improve the blog sidebar by adding a highlighter to active section
1 parent cfa687f commit de4708b

File tree

1 file changed

+117
-65
lines changed

1 file changed

+117
-65
lines changed

components/StyledMarkdown.tsx

Lines changed: 117 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -564,17 +564,53 @@ export function TableOfContentMarkdown({
564564
markdown: string;
565565
depth?: number;
566566
}) {
567+
// Add state to track the active section
568+
const [activeSection, setActiveSection] = useState<string | null>(null);
569+
570+
// Set up intersection observer to track which section is in view
571+
useEffect(() => {
572+
const headings = document.querySelectorAll('h1, h2, h3, h4');
573+
574+
const observer = new IntersectionObserver(
575+
(entries) => {
576+
entries.forEach((entry) => {
577+
if (entry.isIntersecting) {
578+
setActiveSection(entry.target.id);
579+
}
580+
});
581+
},
582+
{
583+
rootMargin: '-100px 0px -80% 0px',
584+
threshold: 0
585+
}
586+
);
587+
588+
headings.forEach((heading) => {
589+
observer.observe(heading);
590+
});
591+
592+
return () => {
593+
headings.forEach((heading) => {
594+
observer.unobserve(heading);
595+
});
596+
};
597+
}, []);
598+
567599
return (
568600
<Markdown
569601
options={{
570602
overrides: {
571603
h1: {
572604
component: ({ children }) => {
573605
const slug = slugifyMarkdownHeadline(children);
606+
const isActive = activeSection === slug;
574607
return (
575608
<a
576609
href={`#${slug}`}
577-
className='flex cursor-pointer mb-3 max-sm:text-sm text-slate-600 dark:text-slate-300 leading-6 font-medium'
610+
className={`flex cursor-pointer mb-3 max-sm:text-sm leading-6 font-medium ${isActive
611+
? 'text-blue-600 dark:text-blue-400 font-semibold bg-blue-50 dark:bg-slate-700 px-2 py-1 -ml-2 rounded'
612+
: 'text-slate-600 dark:text-slate-300'
613+
}`}
578614
>
579615
{children}
580616
</a>
@@ -586,91 +622,107 @@ export function TableOfContentMarkdown({
586622
h2:
587623
depth === 0
588624
? {
625+
component: ({ children }) => {
626+
const slug = slugifyMarkdownHeadline(children);
627+
const isActive = activeSection === slug;
628+
return (
629+
<a
630+
href={`#${slug}`}
631+
className={`block cursor-pointer mb-3 leading-5 font-medium ml-4 ${isActive
632+
? 'text-blue-600 dark:text-blue-400 font-semibold bg-blue-50 dark:bg-slate-700 px-2 py-1 -ml-2 rounded'
633+
: 'text-slate-600 dark:text-slate-300'
634+
}`}
635+
>
636+
{children}
637+
</a>
638+
);
639+
},
640+
}
641+
: depth >= 2
642+
? {
589643
component: ({ children }) => {
590644
const slug = slugifyMarkdownHeadline(children);
645+
const [isChrome, setIsChrome] = useState(false);
646+
const isActive = activeSection === slug;
647+
648+
useEffect(() => {
649+
const chromeCheck =
650+
/Chrome/.test(navigator.userAgent) &&
651+
/Google Inc/.test(navigator.vendor);
652+
setIsChrome(chromeCheck);
653+
}, []);
654+
591655
return (
656+
// chromeClass
592657
<a
593658
href={`#${slug}`}
594-
className='block cursor-pointer mb-3 text-slate-600 dark:text-slate-300 leading-5 font-medium ml-4'
659+
className={`block cursor-pointer mb-3 max-sm:text-sm leading-4 max-sm:-ml-[6px] font-medium ${isActive
660+
? 'text-blue-600 dark:text-blue-400 font-semibold bg-blue-50 dark:bg-slate-700 px-2 py-1 rounded'
661+
: 'text-slate-600 dark:text-slate-300'
662+
} ${isChrome ? '-ml-[4.8px]' : '-ml-[6.5px]'}`}
595663
>
664+
<span className={`mr-1 text-[0.7em] ${isActive ? 'text-blue-600 dark:text-blue-400' : 'text-blue-400'}`}>
665+
&#9679;
666+
</span>
596667
{children}
597668
</a>
598669
);
599670
},
600671
}
601-
: depth >= 2
602-
? {
603-
component: ({ children }) => {
604-
const slug = slugifyMarkdownHeadline(children);
605-
const [isChrome, setIsChrome] = useState(false);
606-
607-
useEffect(() => {
608-
const chromeCheck =
609-
/Chrome/.test(navigator.userAgent) &&
610-
/Google Inc/.test(navigator.vendor);
611-
setIsChrome(chromeCheck);
612-
}, []);
613-
614-
return (
615-
// chromeClass
616-
<a
617-
href={`#${slug}`}
618-
className={`block cursor-pointer mb-3 max-sm:text-sm text-slate-600 dark:text-slate-300 leading-4 ] max-sm:-ml-[6px] font-medium ${isChrome ? '-ml-[4.8px]' : '-ml-[6.5px]'}`}
619-
>
620-
<span className='mr-1 text-blue-400 text-[0.7em]'>
621-
&#9679;
622-
</span>
623-
{children}
624-
</a>
625-
);
626-
},
627-
}
628672
: { component: () => null },
629673
h3:
630674
depth >= 3
631675
? {
632-
component: ({ children }) => {
633-
const slug = slugifyMarkdownHeadline(children);
634-
return (
635-
<a
636-
href={`#${slug}`}
637-
className='flex flex-row items-center cursor-pointer mb-3 max-sm:text-sm text-slate-600 dark:text-slate-300 leading-4 ml-[-0.25rem]'
638-
>
639-
<span className='text-blue-400/40 font-extrabold text-[0.8em] max-sm:text-[1.2em] ml-1'>
640-
&mdash;&mdash;
641-
</span>
642-
<span className='mr-1 text-blue-400/90 text-[0.7em] flex justify-center items-center'>
643-
&#9679;
644-
</span>
676+
component: ({ children }) => {
677+
const slug = slugifyMarkdownHeadline(children);
678+
const isActive = activeSection === slug;
679+
return (
680+
<a
681+
href={`#${slug}`}
682+
className={`flex flex-row items-center cursor-pointer mb-3 max-sm:text-sm leading-4 ml-[-0.25rem] ${isActive
683+
? 'text-blue-600 dark:text-blue-400 font-semibold bg-blue-50 dark:bg-slate-700 px-2 py-1 rounded'
684+
: 'text-slate-600 dark:text-slate-300'
685+
}`}
686+
>
687+
<span className='text-blue-400/40 font-extrabold text-[0.8em] max-sm:text-[1.2em] ml-1'>
688+
&mdash;&mdash;
689+
</span>
690+
<span className={`mr-1 text-[0.7em] flex justify-center items-center ${isActive ? 'text-blue-600 dark:text-blue-400' : 'text-blue-400/90'}`}>
691+
&#9679;
692+
</span>
645693

646-
{children}
647-
</a>
648-
);
649-
},
650-
}
694+
{children}
695+
</a>
696+
);
697+
},
698+
}
651699
: { component: () => null },
652700
h4:
653701
depth >= 4
654702
? {
655-
component: ({ children }) => {
656-
const slug = slugifyMarkdownHeadline(children);
657-
return (
658-
<a
659-
href={`#${slug}`}
660-
className='flex flex-row items-center cursor-pointer mb-3 max-sm:text-sm text-slate-600 dark:text-slate-300 leading-4 ml-[-0.25rem] '
661-
>
662-
<span className='text-blue-400/40 font-extrabold text-[0.8em] ml-1 max-sm:text-[1.2em]'>
663-
&mdash;&mdash;&mdash;&mdash;
664-
</span>
665-
<span className='mr-1 text-blue-400/90 text-[0.7em] flex justify-center items-center'>
666-
&#9679;
667-
</span>
703+
component: ({ children }) => {
704+
const slug = slugifyMarkdownHeadline(children);
705+
const isActive = activeSection === slug;
706+
return (
707+
<a
708+
href={`#${slug}`}
709+
className={`flex flex-row items-center cursor-pointer mb-3 max-sm:text-sm leading-4 ml-[-0.25rem] ${isActive
710+
? 'text-blue-600 dark:text-blue-400 font-semibold bg-blue-50 dark:bg-slate-700 px-2 py-1 rounded'
711+
: 'text-slate-600 dark:text-slate-300'
712+
}`}
713+
>
714+
<span className='text-blue-400/40 font-extrabold text-[0.8em] ml-1 max-sm:text-[1.2em]'>
715+
&mdash;&mdash;&mdash;&mdash;
716+
</span>
717+
<span className={`mr-1 text-[0.7em] flex justify-center items-center ${isActive ? 'text-blue-600 dark:text-blue-400' : 'text-blue-400/90'}`}>
718+
&#9679;
719+
</span>
668720

669-
{children}
670-
</a>
671-
);
672-
},
673-
} /* eslint-enable */
721+
{children}
722+
</a>
723+
);
724+
},
725+
} /* eslint-enable */
674726
: { component: () => null },
675727
...hiddenElements(
676728
'strong',

0 commit comments

Comments
 (0)