From 40192ce55b6b458ac534d5acfdfaab149124e800 Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Thu, 23 Apr 2026 12:56:39 +0200 Subject: [PATCH 1/2] Add cover image background mode and mask --- .changeset/hot-views-judge.md | 5 +++++ .../gitbook/src/components/PageAside/PageAside.tsx | 3 ++- .../gitbook/src/components/PageBody/PageBody.tsx | 3 ++- .../gitbook/src/components/PageBody/PageCover.tsx | 14 +++++++++++--- .../src/components/PageBody/PageCoverImage.tsx | 11 +++++++++++ .../gitbook/src/components/PageBody/PageHeader.tsx | 9 ++++++++- .../gitbook/src/components/SitePage/SitePage.tsx | 6 +++++- 7 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 .changeset/hot-views-judge.md diff --git a/.changeset/hot-views-judge.md b/.changeset/hot-views-judge.md new file mode 100644 index 0000000000..3840961001 --- /dev/null +++ b/.changeset/hot-views-judge.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Add cover image background mode and masks diff --git a/packages/gitbook/src/components/PageAside/PageAside.tsx b/packages/gitbook/src/components/PageAside/PageAside.tsx index 2c12d3ba3b..b688cc6eec 100644 --- a/packages/gitbook/src/components/PageAside/PageAside.tsx +++ b/packages/gitbook/src/components/PageAside/PageAside.tsx @@ -95,7 +95,8 @@ export function PageAside(props: { 'page-api-block:page-has-outline:min-[96rem]:border-l-0', 'page-api-block:page-has-outline:min-[96rem]:pl-8', - 'hydrated:site-background', // Only add a background once the element is positioned correctly to prevent overlapping the page cover + 'layout-default:max-xl:site-background', + 'layout-wide:max-3xl:site-background', 'text-tint', 'contrast-more:text-tint-strong' )} diff --git a/packages/gitbook/src/components/PageBody/PageBody.tsx b/packages/gitbook/src/components/PageBody/PageBody.tsx index dd2056435f..ef402ef6cd 100644 --- a/packages/gitbook/src/components/PageBody/PageBody.tsx +++ b/packages/gitbook/src/components/PageBody/PageBody.tsx @@ -82,6 +82,7 @@ export function PageBody(props: { 'py-8', 'layout-wide:no-sidebar:lg:max-xl:pb-20', // Add padding to prevent overlap of minimised trademark '@container', + 'flex flex-col', CONTENT_STYLE, pageHasToc ? 'page-has-toc' : 'page-no-toc', wideLayout ? 'layout-wide' : 'layout-default' @@ -106,7 +107,7 @@ export function PageBody(props: { {imgs.dark && ( @@ -69,6 +75,11 @@ export function PageCoverImage(props: PageCoverImageProps) { : `${PAGE_COVER_SIZE.width}/${PAGE_COVER_SIZE.height}`, objectPosition: `50% ${objectPositionY}%`, height, // if no height is passed, no height will be set. + maskComposite: 'intersect', + maskImage: + imgs.dark.mask === 'radial' + ? 'radial-gradient(200% 200% at 50% -100%, black 70%, rgba(0,0,0,0.85) 77.5%, rgba(0,0,0,0.6) 85%, rgba(0,0,0,0.1) 96.25%, transparent 100%), linear-gradient(to left, black 60%, rgba(0,0,0,0.8) 75%, rgba(0,0,0,0.1) 95%, transparent 100%), linear-gradient(to right, black 60%, rgba(0,0,0,0.8) 75%, rgba(0,0,0,0.1) 95%, transparent 100%)' + : undefined, }} /> )} diff --git a/packages/gitbook/src/components/PageBody/PageHeader.tsx b/packages/gitbook/src/components/PageBody/PageHeader.tsx index 8c2cdfde6c..e39db78531 100644 --- a/packages/gitbook/src/components/PageBody/PageHeader.tsx +++ b/packages/gitbook/src/components/PageBody/PageHeader.tsx @@ -124,7 +124,14 @@ export async function PageHeader(props: { ) : null} {page.description && page.layout.description ? ( -

+

{page.description}

) : null} diff --git a/packages/gitbook/src/components/SitePage/SitePage.tsx b/packages/gitbook/src/components/SitePage/SitePage.tsx index 2284b1fce8..5bb0d889b8 100644 --- a/packages/gitbook/src/components/SitePage/SitePage.tsx +++ b/packages/gitbook/src/components/SitePage/SitePage.tsx @@ -72,7 +72,11 @@ export async function SitePage(props: SitePageProps & { staticRoute: boolean }) {/* Using `contents` makes the children of this div according to its parent — which keeps them in a single flex row with the TOC by default. If there's a page cover, we use `flex flex-col` to lay out the PageCover above the PageBody + PageAside instead. */} -
+
{withFullPageCover && page.cover ? ( ) : null} From 30482358f7dee8ffe9e9f07e2f15f01600364dd7 Mon Sep 17 00:00:00 2001 From: Zeno Kapitein Date: Fri, 24 Apr 2026 20:51:25 +0200 Subject: [PATCH 2/2] Add auto text contrast based on API Still WIP, need value from API --- .../src/components/DocumentView/Heading.tsx | 1 + .../src/components/DocumentView/Paragraph.tsx | 9 +- .../src/components/PageAside/PageAside.tsx | 3 +- .../PageAside/ScrollSectionsList.tsx | 1 + .../src/components/PageBody/PageCover.tsx | 154 +++++++++++------- .../components/PageBody/PageCoverImage.tsx | 8 +- .../src/components/PageBody/PageHeader.tsx | 9 +- .../src/components/RootLayout/globals.css | 49 ++++++ .../components/SitePage/PageClientLayout.tsx | 68 ++++++++ .../src/components/primitives/Button.tsx | 2 +- packages/gitbook/tailwind.config.ts | 8 + 11 files changed, 242 insertions(+), 70 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/Heading.tsx b/packages/gitbook/src/components/DocumentView/Heading.tsx index 68013e9ea0..0d41ea192a 100644 --- a/packages/gitbook/src/components/DocumentView/Heading.tsx +++ b/packages/gitbook/src/components/DocumentView/Heading.tsx @@ -53,6 +53,7 @@ export function Heading(props: BlockProps) { 'justify-self-start', 'max-w-full', 'break-words', + 'page-cover-background:text-contrast-cover', getTextAlignment(block.data.align), textStyle.lineHeight )} diff --git a/packages/gitbook/src/components/DocumentView/Paragraph.tsx b/packages/gitbook/src/components/DocumentView/Paragraph.tsx index 9ba46fa59f..bb575893ea 100644 --- a/packages/gitbook/src/components/DocumentView/Paragraph.tsx +++ b/packages/gitbook/src/components/DocumentView/Paragraph.tsx @@ -14,7 +14,14 @@ export function Paragraph(props: BlockProps) { 'has-[.button,input]:flex has-[.button,input]:flex-wrap has-[.button,input]:gap-2 has-[.button,input]:items-center'; return ( -

+

); diff --git a/packages/gitbook/src/components/PageAside/PageAside.tsx b/packages/gitbook/src/components/PageAside/PageAside.tsx index b688cc6eec..e25258a640 100644 --- a/packages/gitbook/src/components/PageAside/PageAside.tsx +++ b/packages/gitbook/src/components/PageAside/PageAside.tsx @@ -98,7 +98,8 @@ export function PageAside(props: { 'layout-default:max-xl:site-background', 'layout-wide:max-3xl:site-background', 'text-tint', - 'contrast-more:text-tint-strong' + 'contrast-more:text-tint-strong', + 'xl:page-cover-background:text-contrast-cover' )} >
diff --git a/packages/gitbook/src/components/PageAside/ScrollSectionsList.tsx b/packages/gitbook/src/components/PageAside/ScrollSectionsList.tsx index eee74203bf..b61714cf9e 100644 --- a/packages/gitbook/src/components/PageAside/ScrollSectionsList.tsx +++ b/packages/gitbook/src/components/PageAside/ScrollSectionsList.tsx @@ -91,6 +91,7 @@ export function ScrollSectionsList({ sections }: { sections: DocumentSection[] } 'sidebar-list-line:border-l-2', 'border-transparent', 'sidebar-list-line:-left-px', + 'xl:page-cover-background:text-contrast-cover', section.depth > 1 && [ 'subitem', diff --git a/packages/gitbook/src/components/PageBody/PageCover.tsx b/packages/gitbook/src/components/PageBody/PageCover.tsx index 6ff2165b73..b658fba411 100644 --- a/packages/gitbook/src/components/PageBody/PageCover.tsx +++ b/packages/gitbook/src/components/PageBody/PageCover.tsx @@ -1,5 +1,9 @@ import type { GitBookSiteContext } from '@/lib/context'; -import type { RevisionPageDocument, RevisionPageDocumentCover } from '@gitbook/api'; +import { + CustomizationHeaderPreset, + type RevisionPageDocument, + type RevisionPageDocumentCover, +} from '@gitbook/api'; import type { StaticImageData } from 'next/image'; import { getImageAttributes } from '@/components/utils'; @@ -13,6 +17,7 @@ import { getCoverHeight } from './coverHeight'; import defaultPageCoverSVG from './default-page-cover.svg'; const defaultPageCover = defaultPageCoverSVG as StaticImageData; +const DEFAULT_RESPONSIVE_COVER_CUTOFF = '56.25%'; /** * Cover for the page. @@ -23,9 +28,29 @@ export async function PageCover(props: { cover: RevisionPageDocumentCover; context: GitBookSiteContext; }) { - const { as, cover, context } = props; + let { as, cover, context } = props; const height = getCoverHeight(cover); + const initialCoverCutoff = () => { + if (!height) { + return DEFAULT_RESPONSIVE_COVER_CUTOFF; + } + + let total = height; + if (context.customization.announcement?.enabled) { + total += 68; + } + if (context.customization.header.preset !== CustomizationHeaderPreset.None) { + total += 64; + } + if (context.visibleSections && context.visibleSections.list.length > 1) { + total += 45; + } + return `${total}px`; + }; + + as = 'background'; + const [resolved, resolvedDark] = await Promise.all([ cover.ref ? resolveContentRef(cover.ref, context) : null, cover.refDark ? resolveContentRef(cover.refDark, context) : null, @@ -79,64 +104,71 @@ export async function PageCover(props: { assert(light, 'Light image should be defined'); return ( -
- -
+ <> + +
+ +
+ ); } diff --git a/packages/gitbook/src/components/PageBody/PageCoverImage.tsx b/packages/gitbook/src/components/PageBody/PageCoverImage.tsx index 76be5c0556..ae2d18c4bf 100644 --- a/packages/gitbook/src/components/PageBody/PageCoverImage.tsx +++ b/packages/gitbook/src/components/PageBody/PageCoverImage.tsx @@ -10,7 +10,6 @@ interface ImageAttributes { width?: number; height?: number; size?: ImageSize; - mask?: 'none' | 'radial'; } interface Images { @@ -25,10 +24,11 @@ interface PageCoverImageProps { y: number; // Only if the `height` was customized by the user (and thus defined), we use it to set the cover's height and skip the default behaviour of fixed aspect-ratio. height: number | undefined; + mask?: 'none' | 'radial'; } export function PageCoverImage(props: PageCoverImageProps) { - const { imgs, y, height } = props; + const { imgs, y, height, mask } = props; const { containerRef, objectPositionY, isLoading } = useCoverPosition(imgs, y); if (isLoading) { @@ -56,7 +56,7 @@ export function PageCoverImage(props: PageCoverImageProps) { height, // if no height is passed, no height will be set. maskComposite: 'intersect', maskImage: - imgs.light.mask === 'radial' + mask === 'radial' ? 'radial-gradient(200% 200% at 50% -100%, black 70%, rgba(0,0,0,0.85) 77.5%, rgba(0,0,0,0.6) 85%, rgba(0,0,0,0.1) 96.25%, transparent 100%), linear-gradient(to left, black 60%, rgba(0,0,0,0.8) 75%, rgba(0,0,0,0.1) 95%, transparent 100%), linear-gradient(to right, black 60%, rgba(0,0,0,0.8) 75%, rgba(0,0,0,0.1) 95%, transparent 100%)' : undefined, }} @@ -77,7 +77,7 @@ export function PageCoverImage(props: PageCoverImageProps) { height, // if no height is passed, no height will be set. maskComposite: 'intersect', maskImage: - imgs.dark.mask === 'radial' + mask === 'radial' ? 'radial-gradient(200% 200% at 50% -100%, black 70%, rgba(0,0,0,0.85) 77.5%, rgba(0,0,0,0.6) 85%, rgba(0,0,0,0.1) 96.25%, transparent 100%), linear-gradient(to left, black 60%, rgba(0,0,0,0.8) 75%, rgba(0,0,0,0.1) 95%, transparent 100%), linear-gradient(to right, black 60%, rgba(0,0,0,0.8) 75%, rgba(0,0,0,0.1) 95%, transparent 100%)' : undefined, }} diff --git a/packages/gitbook/src/components/PageBody/PageHeader.tsx b/packages/gitbook/src/components/PageBody/PageHeader.tsx index e39db78531..351d3c8a2e 100644 --- a/packages/gitbook/src/components/PageBody/PageHeader.tsx +++ b/packages/gitbook/src/components/PageBody/PageHeader.tsx @@ -58,7 +58,10 @@ export async function PageHeader(props: {
{hasAncestors && ( -