Skip to content

Commit 3b3d6e2

Browse files
authored
Add an icon to site sections (#2593)
1 parent f2a467f commit 3b3d6e2

File tree

5 files changed

+79
-43
lines changed

5 files changed

+79
-43
lines changed

.changeset/tender-carrots-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook': patch
3+
---
4+
5+
Add icons to sections

bun.lockb

392 Bytes
Binary file not shown.

packages/gitbook/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static"
1717
},
1818
"dependencies": {
19-
"@gitbook/api": "^0.77.0",
19+
"@gitbook/api": "^0.78.0",
2020
"@gitbook/cache-do": "workspace:*",
2121
"@gitbook/emoji-codepoints": "workspace:*",
2222
"@gitbook/icons": "workspace:*",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { SiteSection } from '@gitbook/api';
2+
import { Icon, type IconName } from '@gitbook/icons';
3+
4+
import { type ClassValue, tcls } from '@/lib/tailwind';
5+
6+
/**
7+
* Icon shown beside a section in the site section tabs.
8+
*/
9+
export function SectionIcon(props: { icon: IconName; isActive: boolean }) {
10+
const { icon, isActive } = props;
11+
12+
return (
13+
<Icon
14+
icon={icon}
15+
className={tcls(
16+
'size-[1em] text-inherit opacity-8',
17+
isActive && 'text-inherit opacity-10',
18+
)}
19+
/>
20+
);
21+
}

packages/gitbook/src/components/SiteSectionTabs/SiteSectionTabs.tsx

Lines changed: 52 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
'use client';
2-
import { SiteSection } from '@gitbook/api';
2+
3+
import type { SiteSection } from '@gitbook/api';
4+
import type { IconName } from '@gitbook/icons';
35
import React from 'react';
46

57
import { tcls } from '@/lib/tailwind';
68

79
import { Link } from '../primitives';
10+
import { SectionIcon } from './SectionIcon';
811

912
/**
1013
* A set of navigational tabs representing site sections for multi-section sites
@@ -14,13 +17,7 @@ export function SiteSectionTabs(props: {
1417
section: SiteSection;
1518
index: number;
1619
}) {
17-
const { list: sections, section: currentSection, index: currentIndex } = props;
18-
19-
const tabs = sections.map((section) => ({
20-
id: section.id,
21-
label: section.title,
22-
path: section.urls.published ?? '',
23-
}));
20+
const { list: sections, index: currentIndex } = props;
2421

2522
const currentTabRef = React.useRef<HTMLAnchorElement>(null);
2623
const navRef = React.useRef<HTMLDivElement>(null);
@@ -43,7 +40,9 @@ export function SiteSectionTabs(props: {
4340
}, []);
4441

4542
React.useEffect(() => {
46-
updateTabDimensions();
43+
if (currentIndex >= 0) {
44+
updateTabDimensions();
45+
}
4746
}, [currentIndex, updateTabDimensions]);
4847

4948
React.useLayoutEffect(() => {
@@ -55,11 +54,11 @@ export function SiteSectionTabs(props: {
5554
};
5655
}, [updateTabDimensions]);
5756

58-
const opacity = Boolean(tabDimensions) ? 1 : 0.0;
57+
const opacity = tabDimensions ? 1 : 0.0;
5958
const scale = (tabDimensions?.width ?? 0) * 0.01;
6059
const startPos = `${tabDimensions?.left ?? 0}px`;
6160

62-
return tabs.length > 0 ? (
61+
return sections.length > 0 ? (
6362
<nav
6463
aria-label="Sections"
6564
ref={navRef}
@@ -84,15 +83,24 @@ export function SiteSectionTabs(props: {
8483
'md:px-5',
8584
)}
8685
>
87-
{tabs.map((tab, index) => (
88-
<Tab
89-
active={currentIndex === index}
90-
key={index + tab.path}
91-
label={tab.label}
92-
href={tab.path}
93-
ref={currentIndex === index ? currentTabRef : null}
94-
/>
95-
))}
86+
{sections.map((section, index) => {
87+
const { id, urls, title, icon } = section;
88+
const isActive = index === currentIndex;
89+
return (
90+
<Tab
91+
active={isActive}
92+
key={id}
93+
label={title}
94+
href={urls.published ?? ''}
95+
ref={isActive ? currentTabRef : null}
96+
icon={
97+
icon ? (
98+
<SectionIcon isActive={isActive} icon={icon as IconName} />
99+
) : null
100+
}
101+
/>
102+
);
103+
})}
96104
</div>
97105
{/* A container for a pseudo element for active tab indicator. A container is needed so we can set
98106
a relative position without breaking the z-index of other parts of the header. */}
@@ -117,7 +125,7 @@ export function SiteSectionTabs(props: {
117125
'after:bg-primary',
118126
'dark:after:bg-primary-400',
119127
)}
120-
></div>
128+
/>
121129
</div>
122130
</nav>
123131
) : null;
@@ -126,24 +134,26 @@ export function SiteSectionTabs(props: {
126134
/**
127135
* The tab item - a link to a site section
128136
*/
129-
const Tab = React.forwardRef<HTMLSpanElement, { active: boolean; href: string; label: string }>(
130-
function Tab(props, ref) {
131-
const { active, href, label } = props;
132-
return (
133-
<Link
134-
className={tcls(
135-
'px-3 py-1 my-2 rounded straight-corners:rounded-none transition-colors',
136-
active && 'text-primary dark:text-primary-400',
137-
!active &&
138-
'text-dark/8 hover:bg-dark/1 hover:text-dark/9 dark:text-light/8 dark:hover:bg-light/2 dark:hover:text-light/9',
139-
)}
140-
role="tab"
141-
href={href}
142-
>
143-
<span ref={ref} className={tcls('inline-flex w-full truncate')}>
144-
{label}
145-
</span>
146-
</Link>
147-
);
148-
},
149-
);
137+
const Tab = React.forwardRef<
138+
HTMLSpanElement,
139+
{ active: boolean; href: string; icon?: React.ReactNode; label: string }
140+
>(function Tab(props, ref) {
141+
const { active, href, icon, label } = props;
142+
return (
143+
<Link
144+
className={tcls(
145+
'group/tab px-3 py-1 my-2 rounded straight-corners:rounded-none transition-colors',
146+
active && 'text-primary dark:text-primary-400',
147+
!active &&
148+
'text-dark/8 hover:bg-dark/1 hover:text-dark/9 dark:text-light/8 dark:hover:bg-light/2 dark:hover:text-light/9',
149+
)}
150+
role="tab"
151+
href={href}
152+
>
153+
<span ref={ref} className={tcls('inline-flex gap-2 items-center w-full truncate')}>
154+
{icon}
155+
{label}
156+
</span>
157+
</Link>
158+
);
159+
});

0 commit comments

Comments
 (0)