Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/toc-link-active-highlight.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
Comment thread
zenoachtig marked this conversation as resolved.
"gitbook": patch
---

Highlight a table-of-contents link entry as active when it points to the page — or the section of a page — you are currently viewing.
16 changes: 15 additions & 1 deletion packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { SiteInsightsLinkPosition } from '@gitbook/api';
import { Icon } from '@gitbook/icons';

import { useCurrentPagePath, useHash } from '../hooks';
import type { ClientTOCPageLink } from './encodeClientTableOfContents';
import { TOCPageIcon } from './TOCPageIcon';
import { Link } from '@/components/primitives';
Expand All @@ -13,11 +14,24 @@ export function PageLinkItem(props: { page: ClientTOCPageLink }) {

const isExternal = page.target.kind === 'url';

const currentPagePath = useCurrentPagePath();
const hash = useHash();
const isOnTargetPage =
page.pathnames?.some((pathname) => pathname === currentPagePath) ?? false;
// A link to a section only lights up once the reader is at that section, so sibling links
// pointing at other sections of the same page don't all highlight together.
const isActive = isOnTargetPage && (!page.anchor || page.anchor === hash);

return (
<li className="page-link-item flex flex-col [.page-group-item+&]:mt-4">
<Link
href={page.href ?? '#'}
classNames={['ToCLinkItemStyles']}
data-active={isActive}
aria-current={isActive ? 'page' : undefined}
classNames={[
'ToCLinkItemStyles',
...(isActive ? ['ToCLinkItemActiveStyles' as const] : []),
]}
insights={{
type: 'link_click',
link: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export type ClientTOCPageLink = {
emoji?: string;
icon?: string;
target: ContentRef;
/** Paths of the page this link points to, when it resolves to a page in the current space. */
pathnames?: string[];
/** Anchor the link points to on that page, when the target is a section of it. */
anchor?: string;
};

export type ClientTOCPageDocument = {
Expand Down Expand Up @@ -96,6 +100,15 @@ export async function encodeClientTableOfContents(
}
case 'link': {
const resolved = await resolveContentRef(page.target, context);
// Links to a page (or a section of one) in the current space compute their active
// state the same way page entries do. Cross-space and external targets are left out
// to avoid over-highlighting.
const targetPage =
(page.target.kind === 'page' || page.target.kind === 'anchor') &&
resolved?.page &&
resolved.space?.id === context.space.id
? resolved.page
: undefined;
result.push(
removeUndefined({
id: page.id,
Expand All @@ -104,6 +117,13 @@ export async function encodeClientTableOfContents(
emoji: page.emoji,
icon: page.icon,
target: page.target,
pathnames: targetPage
? getSiteSpacePagePaths(context.siteSpace, rootPages, targetPage)
: undefined,
anchor:
targetPage && page.target.kind === 'anchor'
? page.target.anchor
: undefined,
type: 'link',
})
);
Expand Down
Loading