diff --git a/.changeset/fuzzy-bulldogs-listen.md b/.changeset/fuzzy-bulldogs-listen.md new file mode 100644 index 000000000..e58b92c18 --- /dev/null +++ b/.changeset/fuzzy-bulldogs-listen.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Add fullwidth page option diff --git a/packages/gitbook/e2e/internal.spec.ts b/packages/gitbook/e2e/internal.spec.ts index 002fb36ff..0db15e051 100644 --- a/packages/gitbook/e2e/internal.spec.ts +++ b/packages/gitbook/e2e/internal.spec.ts @@ -932,6 +932,22 @@ const testCases: TestsCase[] = [ }, ]), }, + { + name: 'Page actions', + contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/', + tests: [ + { + name: 'Without page actions', + url: getCustomizationURL({ + pageActions: { + markdown: false, + externalAI: false, + }, + }), + run: waitForCookiesDialog, + }, + ], + }, { name: 'Ads', contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/', diff --git a/packages/gitbook/src/components/AIActions/AIActionsDropdown.tsx b/packages/gitbook/src/components/AIActions/AIActionsDropdown.tsx index d10d2abfe..a08afc091 100644 --- a/packages/gitbook/src/components/AIActions/AIActionsDropdown.tsx +++ b/packages/gitbook/src/components/AIActions/AIActionsDropdown.tsx @@ -8,6 +8,7 @@ import { } from '@/components/AIActions/AIActions'; import { Button } from '@/components/primitives/Button'; import { DropdownMenu } from '@/components/primitives/DropdownMenu'; +import type { SiteCustomizationSettings } from '@gitbook/api'; import { Icon } from '@gitbook/icons'; import { useRef } from 'react'; @@ -16,10 +17,7 @@ interface AIActionsDropdownProps { markdownPageUrl: string; withAIChat?: boolean; trademark: boolean; - /** - * Whether to include the "Open in LLM" entries in the dropdown menu. - */ - withLLMActions?: boolean; + actions: SiteCustomizationSettings['pageActions']; } /** @@ -59,7 +57,7 @@ export function AIActionsDropdown(props: AIActionsDropdownProps) { * The content of the dropdown menu. */ function AIActionsDropdownMenuContent(props: AIActionsDropdownProps) { - const { markdownPageUrl, withAIChat, trademark, withLLMActions } = props; + const { markdownPageUrl, withAIChat, trademark, actions } = props; return ( <> @@ -67,14 +65,18 @@ function AIActionsDropdownMenuContent(props: AIActionsDropdownProps) { ) : null} - - + {actions.markdown ? ( + <> + + + + ) : null} - {withLLMActions ? ( + {actions.externalAI ? ( <> @@ -88,17 +90,27 @@ function AIActionsDropdownMenuContent(props: AIActionsDropdownProps) { * A default action shown as a quick-access button beside the dropdown menu */ function DefaultAction(props: AIActionsDropdownProps) { - const { markdownPageUrl, withAIChat, trademark } = props; + const { markdownPageUrl, withAIChat, trademark, actions } = props; if (withAIChat) { return ; } - return ( - - ); + if (actions.markdown) { + return ( + + ); + } + + if (actions.externalAI) { + return ( + <> + + + ); + } } diff --git a/packages/gitbook/src/components/DocumentView/Blocks.tsx b/packages/gitbook/src/components/DocumentView/Blocks.tsx index 4bbe605d8..be09f2b42 100644 --- a/packages/gitbook/src/components/DocumentView/Blocks.tsx +++ b/packages/gitbook/src/components/DocumentView/Blocks.tsx @@ -47,6 +47,23 @@ type UnwrappedBlocksProps = DocumentContextProps & isOffscreen?: boolean; }; +/* Blocks that can be full width are automatically expanded on full-width pages. + * Ideally we'd rely on the block type to determine if it can be full width, but + * the block's `fullWidth` property does not differentiate between `undefined` and `false`. + * So instead we hardcode a list of blocks that can be full width. */ +const FULL_WIDTH_BLOCKS = [ + 'table', + 'tabs', + 'integration', + 'openapi', + 'images', + 'embed', + 'columns', + 'code', + 'content-ref', + 'hint', +]; + /** * Renders a list of blocks without a wrapper element. */ @@ -68,10 +85,11 @@ export function UnwrappedBlocks(props: UnwrappedBl key={node.key || `${node.type}-${index}`} block={node} style={[ - 'mx-auto w-full decoration-primary/6', + 'mx-auto page-width-wide:mx-0 w-full decoration-primary/6', node.data && 'fullWidth' in node.data && node.data.fullWidth ? 'max-w-screen-2xl' - : 'page-full-width:ml-0 max-w-3xl', + : 'max-w-3xl', + FULL_WIDTH_BLOCKS.includes(node.type) && 'page-width-wide:max-w-screen-2xl', blockStyle, ]} isEstimatedOffscreen={isOffscreen} diff --git a/packages/gitbook/src/components/DocumentView/Divider.tsx b/packages/gitbook/src/components/DocumentView/Divider.tsx index f787d044b..15302df47 100644 --- a/packages/gitbook/src/components/DocumentView/Divider.tsx +++ b/packages/gitbook/src/components/DocumentView/Divider.tsx @@ -7,5 +7,5 @@ import type { BlockProps } from './Block'; export function Divider(props: BlockProps) { const { style } = props; - return
; + return
; } diff --git a/packages/gitbook/src/components/DocumentView/Heading.tsx b/packages/gitbook/src/components/DocumentView/Heading.tsx index 5ca93c148..769bb66ba 100644 --- a/packages/gitbook/src/components/DocumentView/Heading.tsx +++ b/packages/gitbook/src/components/DocumentView/Heading.tsx @@ -27,6 +27,7 @@ export function Heading(props: BlockProps) { 'flex', 'items-baseline', 'scroll-m-12', + getTextAlignment(block.data.align), hashLinkButtonWrapperStyles, style, textStyle.marginTop diff --git a/packages/gitbook/src/components/Footer/Footer.tsx b/packages/gitbook/src/components/Footer/Footer.tsx index eaeb6cc95..31c2c5fd5 100644 --- a/packages/gitbook/src/components/Footer/Footer.tsx +++ b/packages/gitbook/src/components/Footer/Footer.tsx @@ -36,16 +36,16 @@ export function Footer(props: { context: GitBookSiteContext }) {
{ @@ -106,7 +106,7 @@ export function Footer(props: { context: GitBookSiteContext }) { 'col-span-2 page-has-toc:lg:col-span-1 page-has-toc:lg:col-start-2 page-no-toc:xl:col-span-1 page-no-toc:xl:col-start-2' )} > -
+
{partition(customization.footer.groups, FOOTER_COLUMNS).map( (column, columnIndex) => (
- + {page.cover && page.layout.cover && page.layout.coverSize === 'hero' ? ( ) : null} @@ -93,19 +93,28 @@ export function PageBody(props: { ) : null} -
- {updatedAt ? ( -

- {t(language, 'page_last_modified', )} -

- ) : null} - {withPageFeedback ? ( - - ) : null} -
+ { + // TODO: after 25/07/2025, we can chage it to a true check as the cache will be updated + page.layout.metadata !== false ? ( +
+ {updatedAt ? ( +

+ {t( + language, + 'page_last_modified', + + )} +

+ ) : null} + {withPageFeedback ? ( + + ) : null} +
+ ) : null + } diff --git a/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx b/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx index 1bf4a8428..6eb6c039b 100644 --- a/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx +++ b/packages/gitbook/src/components/PageBody/PageBodyBlankslate.tsx @@ -71,11 +71,13 @@ export async function PageBodyBlankslate(props: { className={tcls( 'grid', 'max-w-3xl', + 'page-width-wide:max-w-screen-2xl', 'w-full', 'mx-auto', 'gap-4', 'grid-cols-1', - 'sm:grid-cols-2' + 'sm:grid-cols-2', + 'page-width-wide:md:grid-cols-3' )} > {pageElements} diff --git a/packages/gitbook/src/components/PageBody/PageCover.tsx b/packages/gitbook/src/components/PageBody/PageCover.tsx index 9c8113e8d..5e52ddf8a 100644 --- a/packages/gitbook/src/components/PageBody/PageCover.tsx +++ b/packages/gitbook/src/components/PageBody/PageCover.tsx @@ -95,7 +95,7 @@ export async function PageCover(props: { : [ 'sm:mx-auto', 'max-w-3xl ', - 'page-full-width:max-w-screen-2xl', + 'page-width-wide:max-w-screen-2xl', 'sm:rounded-md', 'mb-8', ] diff --git a/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx b/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx index 465eb81ca..1e9df3be2 100644 --- a/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx +++ b/packages/gitbook/src/components/PageBody/PageFooterNavigation.tsx @@ -34,7 +34,7 @@ export async function PageFooterNavigation(props: { 'mt-6', 'gap-2', 'max-w-3xl', - 'page-full-width:max-w-screen-2xl', + 'page-width-wide:max-w-screen-2xl', 'mx-auto', 'text-tint' )} diff --git a/packages/gitbook/src/components/PageBody/PageHeader.tsx b/packages/gitbook/src/components/PageBody/PageHeader.tsx index b5e0f1bf3..6b96faac0 100644 --- a/packages/gitbook/src/components/PageBody/PageHeader.tsx +++ b/packages/gitbook/src/components/PageBody/PageHeader.tsx @@ -3,7 +3,7 @@ import { isAIChatEnabled } from '@/components/utils/isAIChatEnabled'; import type { GitBookSiteContext } from '@/lib/context'; import type { AncestorRevisionPage } from '@/lib/pages'; import { tcls } from '@/lib/tailwind'; -import { type RevisionPageDocument, SiteVisibility } from '@gitbook/api'; +import type { RevisionPageDocument } from '@gitbook/api'; import { Icon } from '@gitbook/icons'; import { PageIcon } from '../PageIcon'; import { StyledLink } from '../primitives'; @@ -22,11 +22,17 @@ export async function PageHeader(props: { const withAIChat = isAIChatEnabled(context); + const pageActions = context.customization.pageActions ?? { + // TODO: After 25/07/2025, we can remove this default values as the cache will be updated + markdown: true, + externalAI: true, + }; + return (
- {page.layout.tableOfContents ? ( + {page.layout.tableOfContents && + // Show page actions if *any* of the actions are enabled + (withAIChat || pageActions.markdown || pageActions.externalAI) ? (
) : null} diff --git a/packages/gitbook/src/components/PageBody/PreservePageLayout.tsx b/packages/gitbook/src/components/PageBody/PreservePageLayout.tsx index 06a801034..834224982 100644 --- a/packages/gitbook/src/components/PageBody/PreservePageLayout.tsx +++ b/packages/gitbook/src/components/PageBody/PreservePageLayout.tsx @@ -6,14 +6,14 @@ import * as React from 'react'; * This approach is needed as page layout (full width block) is done using CSS (`body:has(.full-width)`), * which becomes false while transitioning between the 2 page states: * - * 1. Page 1 with full width block: `body:has(.site-full-width)` is true - * 2. Loading skeleton while transitioning to page 2: `body:has(.site-full-width)` is false - * 3. Page 2 with full width block: `body:has(.site-full-width)` is true + * 1. Page 1 with full width block: `body:has(.site-width-wide)` is true + * 2. Loading skeleton while transitioning to page 2: `body:has(.site-width-wide)` is false + * 3. Page 2 with full width block: `body:has(.site-width-wide)` is true * * This component ensures that the layout is preserved while transitioning between the 2 page states (in step 2). */ -export function PreservePageLayout(props: { asFullWidth: boolean }) { - const { asFullWidth } = props; +export function PreservePageLayout(props: { siteWidthWide: boolean }) { + const { siteWidthWide } = props; React.useLayoutEffect(() => { // We use the header as it's an element preserved between page transitions @@ -23,12 +23,12 @@ export function PreservePageLayout(props: { asFullWidth: boolean }) { return; } - if (asFullWidth) { - header.classList.add('site-full-width'); + if (siteWidthWide) { + header.classList.add('site-width-wide'); } else { - header.classList.remove('site-full-width'); + header.classList.remove('site-width-wide'); } - }, [asFullWidth]); + }, [siteWidthWide]); return null; } diff --git a/packages/gitbook/src/components/SitePage/SitePageSkeleton.tsx b/packages/gitbook/src/components/SitePage/SitePageSkeleton.tsx index 7b840afbb..2f18c0b8c 100644 --- a/packages/gitbook/src/components/SitePage/SitePageSkeleton.tsx +++ b/packages/gitbook/src/components/SitePage/SitePageSkeleton.tsx @@ -19,7 +19,7 @@ export function SitePageSkeleton() { 'lg:items-start' )} > -
+
diff --git a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx index 0c0efdb1e..f57aec64c 100644 --- a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx +++ b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx @@ -97,7 +97,7 @@ export function SpaceLayout(props: { 'flex-col', 'lg:flex-row', CONTAINER_STYLE, - 'site-full-width:max-w-full', + 'site-width-wide:max-w-full', // Ensure the footer is display below the viewport even if the content is not enough withFooter && 'min-h-[calc(100vh-64px)]', diff --git a/packages/gitbook/src/components/layout.ts b/packages/gitbook/src/components/layout.ts index 6a9b39a98..4f6579ab6 100644 --- a/packages/gitbook/src/components/layout.ts +++ b/packages/gitbook/src/components/layout.ts @@ -14,7 +14,6 @@ export const CONTAINER_STYLE: ClassValue = [ 'md:px-8', 'max-w-screen-2xl', 'mx-auto', - // 'site-full-width:max-w-full', ]; /** diff --git a/packages/gitbook/tailwind.config.ts b/packages/gitbook/tailwind.config.ts index a895b5271..fa5266945 100644 --- a/packages/gitbook/tailwind.config.ts +++ b/packages/gitbook/tailwind.config.ts @@ -564,9 +564,9 @@ const config: Config = { /** * Variant when the page contains a block that will be rendered in full-width mode. */ - addVariant('site-full-width', 'body:has(.site-full-width) &'); - addVariant('site-default-width', 'body:has(.site-default-width) &'); - addVariant('page-full-width', 'body:has(.page-full-width) &'); + addVariant('site-width-wide', 'body:has(.site-width-wide) &'); + addVariant('site-width-default', 'body:has(.site-width-default) &'); + addVariant('page-width-wide', 'body:has(.page-width-wide) &'); /** * Variant when the page is configured to hide the table of content.