Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9e669f1
Add SideSheet component, refactor TOC and AIChat to use it
zenoachtig Dec 2, 2025
0d44191
Fix trademark & other visual bugs
zenoachtig Dec 2, 2025
f53e042
Naming and docs
zenoachtig Dec 2, 2025
e05f262
Get ready for supporting side sections list
zenoachtig Dec 2, 2025
a7b80c3
Small fixes
zenoachtig Dec 2, 2025
b9f9623
Fix scroll gutter
zenoachtig Dec 2, 2025
555a1da
Changeset
zenoachtig Dec 2, 2025
fb49a27
Layout tweaks
zenoachtig Dec 2, 2025
0a7979e
More layout fixes
zenoachtig Dec 2, 2025
72a0671
Layout
zenoachtig Dec 2, 2025
f78e9ca
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Dec 4, 2025
8436352
Make sidesheet work well in docs embed
zenoachtig Dec 4, 2025
c895672
Update Header.tsx
zenoachtig Dec 4, 2025
e329b41
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Dec 5, 2025
e372dae
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Dec 11, 2025
ec0b607
Update AnnouncementBanner.tsx
zenoachtig Dec 11, 2025
9281b5e
Update SideSheet.tsx
zenoachtig Dec 11, 2025
dcd0355
Update TOC to use normal scrollcontainer
zenoachtig Dec 11, 2025
2f6ea5b
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Dec 11, 2025
8a887fd
Fix tests
zenoachtig Dec 11, 2025
4cdab37
Rework table of contents to accommodate faded edges
zenoachtig Dec 11, 2025
43e8dec
Add margin to first document item
zenoachtig Dec 11, 2025
5347917
Spacing
zenoachtig Dec 11, 2025
aa86711
More spacing
zenoachtig Dec 11, 2025
8ee84f1
Update SpaceLayout.tsx
zenoachtig Dec 12, 2025
c716df3
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Dec 16, 2025
8c3249a
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Jan 5, 2026
01e2725
Update SideSheet.tsx
zenoachtig Jan 5, 2026
91f2afb
Update Trademark
zenoachtig Jan 5, 2026
c0c87e3
Update IconsProvider.tsx
zenoachtig Jan 5, 2026
4402f45
Review
zenoachtig Jan 6, 2026
633224c
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Jan 6, 2026
18f575e
Fix embed
zenoachtig Jan 6, 2026
60fc983
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Jan 6, 2026
0bb278b
Fix mobile <> desktop transition
zenoachtig Jan 7, 2026
1586444
Update AIChat.tsx
zenoachtig Jan 7, 2026
890e77c
Merge branch 'main' into zeno/rnd-7450-mobile-toc-chat-layout
zenoachtig Jan 13, 2026
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/curly-eels-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gitbook": patch
---

Add sidesheet component, use it for TOC and AIChat
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export default async function SiteDynamicLayout({

return (
<CustomizationRootLayout
className="site-background"
htmlClassName="sheet-open:gutter-stable"
bodyClassName="site-background"
forcedTheme={forcedTheme}
context={context}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export default async function SiteStaticLayout({
const withTracking = shouldTrackEvents();

return (
<CustomizationRootLayout className="site-background" context={context}>
<CustomizationRootLayout
htmlClassName="sheet-open:gutter-stable"
bodyClassName="site-background"
context={context}
>
<SiteLayout
context={context}
withTracking={withTracking}
Expand Down
26 changes: 17 additions & 9 deletions packages/gitbook/src/components/AIChat/AIChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useTrackEvent } from '../Insights';
import { useNow } from '../hooks';
import { Button } from '../primitives';
import { ScrollContainer } from '../primitives/ScrollContainer';
import { SideSheet } from '../primitives/SideSheet';
import { AIChatControlButton } from './AIChatControlButton';
import { AIChatIcon } from './AIChatIcon';
import { AIChatInput } from './AIChatInput';
Expand Down Expand Up @@ -69,15 +70,22 @@ export function AIChat() {
}, [chat.opened, trackEvent]);

return (
<div
<SideSheet
side="right"
open={chat.opened}
onOpenChange={(open) => {
if (open) {
chatController.open();
} else {
chatController.close();
}
}}
withScrim={true}
className={tcls(
'ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl scroll-mt-36 px-4 py-4 transition-[width,opacity,margin,display] transition-discrete duration-300 sm:px-6 lg:fixed lg:w-80 lg:p-0 xl:w-96',
chat.opened
? 'lg:starting:ml-0 lg:starting:w-0 lg:starting:opacity-0'
: 'hidden lg:ml-0 lg:w-0! lg:opacity-0'
'ai-chat mx-auto ml-8 not-hydrated:hidden w-96 transition-[width] duration-300 ease-quint lg:max-xl:w-80'
)}
>
<EmbeddableFrame className="relative shrink-0 border-tint-subtle border-l to-tint-base transition-all duration-300 max-lg:circular-corners:rounded-3xl max-lg:rounded-corners:rounded-md max-lg:border lg:w-80 xl:w-96">
<EmbeddableFrame className="relative shrink-0 border-tint-subtle border-l to-tint-base">
<EmbeddableFrameMain data-testid="ai-chat">
<EmbeddableFrameHeader>
<AIChatDynamicIcon trademark={config.trademark} />
Expand Down Expand Up @@ -107,7 +115,7 @@ export function AIChat() {
</EmbeddableFrameBody>
</EmbeddableFrameMain>
</EmbeddableFrame>
</div>
</SideSheet>
);
}

Expand Down Expand Up @@ -218,8 +226,8 @@ export function AIChatBody(props: {
className="shrink grow basis-80 animate-fade-in-slow [container-type:size]"
contentClassName="p-4 gutter-stable flex flex-col gap-4"
orientation="vertical"
fadeEdges={['leading']}
active={`message-group-${chat.messages.filter((message) => message.role === 'user').length - 1}`}
trailing={{ fade: false, button: true }}
active={`#message-group-${chat.messages.filter((message) => message.role === 'user').length - 1}`}
>
{isEmpty ? (
<div className="flex grow flex-col">
Expand Down
8 changes: 4 additions & 4 deletions packages/gitbook/src/components/Cookies/CookiesToast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
aria-describedby={describedById}
className={tcls(
'fixed',
'z-10',
'z-50',
'bg-tint-base',
'rounded-sm',
'straight-corners:rounded-none',
Expand All @@ -52,9 +52,9 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
'depth-flat:shadow-none',
'p-4',
'pr-8',
'bottom-4',
'right-4',
'left-16',
'bottom-[max(env(safe-area-inset-bottom),1rem)]',
'right-[max(env(safe-area-inset-right),1rem)]',
'left-[max(env(safe-area-inset-left),4rem)]',
'max-w-md',
'text-balance',
'sm:left-auto',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ export async function EmbeddableDocsPage(
orientation="vertical"
className="not-hydrated:animate-blur-in-slow"
contentClassName="p-4"
fadeEdges={context.sections ? [] : ['leading']}
leading={{ fade: !context.sections, button: true }}
trailing={{ fade: false, button: true }}
>
<TableOfContents className="pt-0" context={context} />
<TableOfContents context={context} withTrademark={false} />
<PageBody
context={context}
page={page}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { VisitorAuthClaims } from '@/lib/adaptive';
import type { GitBookSiteContext } from '@/lib/context';
import { SiteInsightsTrademarkPlacement } from '@gitbook/api';
import { SpaceLayoutServerContext } from '../SpaceLayout';
import { TrademarkLink } from '../TableOfContents/Trademark';
import { Trademark } from '../TableOfContents/Trademark';
import { NavigationLoader } from '../primitives/NavigationLoader';
import { EmbeddableIframeAPI } from './EmbeddableIframeAPI';

Expand Down Expand Up @@ -57,8 +57,8 @@ export async function EmbeddableRootLayout({
<div className="fixed inset-0 flex flex-col">
{children}
{context.customization.trademark.enabled ? (
<TrademarkLink
className="border-tint-solid/3 border-t bg-tint-solid/1 px-4 py-2.5 text-tint/8 ring-0"
<Trademark
className="rounded-none! border-x-0 border-t border-b-0 bg-tint-solid/1 depth-flat:bg-tint-solid/1 px-4 py-2.5 text-tint/8"
context={context}
placement={SiteInsightsTrademarkPlacement.Embed}
/>
Expand Down
1 change: 1 addition & 0 deletions packages/gitbook/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function Header(props: {
`h-[${HEADER_HEIGHT_DESKTOP}px]`,
'sticky',
'top-0',
'pt-[env(safe-area-inset-top)]',
'z-30',
'w-full',
'flex-none',
Expand Down
30 changes: 5 additions & 25 deletions packages/gitbook/src/components/Header/HeaderMobileMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,20 @@
'use client';
import { usePathname } from 'next/navigation';
import { useEffect, useRef, useState } from 'react';
import { useEffect } from 'react';

import { tString, useLanguage } from '@/intl/client';

import { useScrollListener } from '../hooks/useScrollListener';
import { Button, type ButtonProps } from '../primitives';

const globalClassName = 'navigation-open';

const SCROLL_DISTANCE = 320;

/**
* Button to show/hide the table of content on mobile.
*/
export function HeaderMobileMenu(props: ButtonProps) {
const language = useLanguage();

const pathname = usePathname();
const hasScrollRef = useRef(false);

const [isOpen, setIsOpen] = useState(false);

const toggleNavigation = () => {
if (!hasScrollRef.current && document.body.classList.contains(globalClassName)) {
document.body.classList.remove(globalClassName);
setIsOpen(false);
} else {
document.body.classList.add(globalClassName);
window.scrollTo(0, 0);
setIsOpen(true);
}
};

const windowRef = useRef(typeof window === 'undefined' ? null : window);
useScrollListener(() => {
hasScrollRef.current = window.scrollY >= SCROLL_DISTANCE;
}, windowRef);

// Close the navigation when navigating to a page
useEffect(() => {
Expand All @@ -50,8 +28,10 @@ export function HeaderMobileMenu(props: ButtonProps) {
iconOnly
variant="blank"
label={tString(language, 'table_of_contents_button_label')}
onClick={toggleNavigation}
active={isOpen}
onClick={() => {
document.body.classList.toggle(globalClassName);
}}
// Since the button is hidden behind the TOC after toggling, we don't need to keep track of its active state.
{...props}
/>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/gitbook/src/components/PDF/PDFPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { notFound } from 'next/navigation';
import * as React from 'react';

import { DocumentView } from '@/components/DocumentView';
import { TrademarkLink } from '@/components/TableOfContents/Trademark';
import { Trademark } from '@/components/TableOfContents/Trademark';
import type { PolymorphicComponentProp } from '@/components/utils/types';
import { getSpaceLanguage } from '@/intl/server';
import { tString } from '@/intl/translate';
Expand Down Expand Up @@ -148,7 +148,7 @@ export async function PDFPage(props: {
total={total}
trademark={
customization.trademark.enabled ? (
<TrademarkLink
<Trademark
context={context}
placement={SiteInsightsTrademarkPlacement.Pdf}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@ function preloadFont(fontData: FontData) {
* It takes care of setting the theme and the language.
*/
export async function CustomizationRootLayout(props: {
/** The class name to apply to the html element. */
htmlClassName?: string;
/** The class name to apply to the body element. */
className?: string;
bodyClassName?: string;
forcedTheme?: CustomizationThemeMode | null;
context: GitBookAnyContext;
children: React.ReactNode;
}) {
const { className, context, forcedTheme, children } = props;
const { htmlClassName, bodyClassName, context, forcedTheme, children } = props;
const customization =
'customization' in context ? context.customization : defaultCustomization();

Expand Down Expand Up @@ -106,7 +108,8 @@ export async function CustomizationRootLayout(props: {
// Set the dark/light class statically to avoid flashing and make it work when JS is disabled
(forcedTheme ?? customization.themes.default) === CustomizationThemeMode.Dark
? 'dark'
: ''
: '',
htmlClassName
)}
>
<head>
Expand Down Expand Up @@ -178,7 +181,7 @@ export async function CustomizationRootLayout(props: {
}
`}</style>
</head>
<body className={className}>
<body className={tcls(bodyClassName, 'sheet-open:overflow-hidden')}>
<IconsProvider
assetsURL={GITBOOK_ICONS_URL}
assetsURLToken={GITBOOK_ICONS_TOKEN}
Expand Down
1 change: 1 addition & 0 deletions packages/gitbook/src/components/SiteLayout/SiteLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export async function generateSiteLayoutViewport(context: GitBookSiteContext): P
width: 'device-width',
initialScale: 1,
maximumScale: 1,
viewportFit: 'cover',
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function SiteSectionList(props: { sections: ClientSiteSections; className
orientation="vertical"
style={{ maxHeight: `${MAX_ITEMS * 3 + 2}rem` }}
className="pb-4"
active={currentSection.id}
active={`#${currentSection.id}`}
>
<div className="flex w-full flex-col px-2">
{sectionsAndGroups.map((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ export function SiteSectionTabs(props: {
? 'md:-mr-8 -mr-4 sm:-mr-6'
: 'after:contents[] after:absolute after:inset-y-2 after:right-0 after:border-transparent after:border-r after:transition-colors'
)}
active={currentSection.id}
trailingEdgeScrollClassName={children ? 'after:border-tint' : ''}
active={`#${currentSection.id}`}
trailing={{
fade: true,
button: true,
className: children ? 'after:border-tint' : '',
}}
>
<NavigationMenu.List
className={tcls(
Expand Down
Loading
Loading