Skip to content

Commit 2d7954f

Browse files
Enhance layout handling for OpenAPI and full-width modes
- Removed deprecated configuration from bun.lock. - Added new layout variants in tailwind.config.ts for better control over page structure. - Updated DocumentView and related components to support full-width and OpenAPI layouts, ensuring proper styling and responsiveness. - Introduced utility functions to check for OpenAPI blocks in documents. - Adjusted various components to conditionally apply styles based on the layout mode, improving overall layout consistency.
1 parent e00d1c5 commit 2d7954f

19 files changed

Lines changed: 221 additions & 74 deletions

File tree

bun.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/gitbook/src/components/DocumentView/Blocks.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
8484
ancestorBlocks: props.ancestorBlocks,
8585
});
8686

87+
// Determine if this block should expand to full width or use readable width
88+
// Text blocks (paragraphs, headings, lists) use a readable max-width
89+
// Visual blocks (code, tables, images, etc.) expand to full width
90+
const isFullWidthBlock = FULL_WIDTH_BLOCKS.includes(node.type);
91+
8792
return (
8893
<Block
8994
key={node.key || `${node.type}-${index}`}
@@ -94,7 +99,12 @@ export function UnwrappedBlocks<TBlock extends DocumentBlock>(props: UnwrappedBl
9499
? 'max-w-screen-xl'
95100
: 'max-w-3xl',
96101
!LIST_BLOCKS.includes(node.type) && 'print:break-inside-avoid',
97-
FULL_WIDTH_BLOCKS.includes(node.type) && 'page-width-wide:max-w-full',
102+
isFullWidthBlock && 'page-width-wide:max-w-full',
103+
// In OpenAPI mode, all blocks expand to full width
104+
// In full-width mode, content is capped at 64rem (1024px) and centered
105+
'layout-openapi:max-w-full',
106+
'layout-full-width:max-w-5xl',
107+
'layout-full-width:mx-auto',
98108
blockStyle,
99109
]}
100110
isEstimatedOffscreen={isOffscreen}

packages/gitbook/src/components/DocumentView/DocumentView.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ export function DocumentView(
7171
style,
7272
// Preserve adjacent whitespace and new lines.
7373
'whitespace-pre-wrap',
74+
// In OpenAPI mode, add left padding for sidebar offset
75+
'layout-openapi:pl-12',
7476
]}
7577
context={context}
7678
isOffscreen={isOffscreen}
@@ -85,7 +87,7 @@ export function DocumentViewSkeleton(props: { document: JSONDocument; blockStyle
8587
const { document, blockStyle } = props;
8688

8789
return (
88-
<div className="flex flex-col gap-4">
90+
<div className="flex flex-col gap-4 layout-openapi:pl-12">
8991
{document.nodes.map((block) => (
9092
<BlockSkeleton
9193
key={block.key!}
@@ -95,6 +97,10 @@ export function DocumentViewSkeleton(props: { document: JSONDocument; blockStyle
9597
block.data && 'fullWidth' in block.data && block.data.fullWidth
9698
? 'max-w-screen-xl'
9799
: 'max-w-3xl',
100+
// Expand in OpenAPI mode, cap at 64rem in full-width mode
101+
'layout-openapi:max-w-full',
102+
'layout-full-width:max-w-5xl',
103+
'layout-full-width:mx-auto',
98104
blockStyle,
99105
]}
100106
/>

packages/gitbook/src/components/DocumentView/StepperStep.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,16 @@ export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
3232
})();
3333

3434
return (
35-
<div className={tcls('mx-auto flex w-full max-w-3xl flex-row gap-4 md:gap-8', style)}>
35+
<div
36+
className={tcls(
37+
'mx-auto flex w-full max-w-3xl flex-row gap-4 md:gap-8',
38+
// Expand in OpenAPI mode, cap at 64rem in full-width mode
39+
'layout-openapi:max-w-full',
40+
'layout-full-width:max-w-5xl',
41+
'layout-full-width:mx-auto',
42+
style
43+
)}
44+
>
3645
<div className="relative select-none">
3746
<div
3847
className={tcls(

packages/gitbook/src/components/DocumentView/Tabs/DynamicTabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ const TabPanel = memo(function TabPanel(props: {
224224
aria-labelledby={getTabButtonId(tab.id)}
225225
className="scroll-mt-[calc(var(--content-scroll-margin)+var(--spacing)*20)]"
226226
>
227-
<div className="p-4" hidden={!isActive}>
227+
<div className={tcls('p-4')} hidden={!isActive}>
228228
{tab.body}
229229
</div>
230230
</div>

packages/gitbook/src/components/Footer/Footer.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,12 @@ export function Footer(props: { context: GitBookSiteContext }) {
4848
>
4949
<div
5050
className={tcls(
51-
'mx-auto flex @xs:grid @4xl:max-w-none! max-w-3xl site-width-wide:max-w-screen-2xl flex-col justify-between gap-12',
51+
'mx-auto flex max-w-3xl flex-col justify-between gap-12 layout-full-width:max-w-screen-2xl @xs:grid @4xl:max-w-none!',
5252
'grid-cols-[auto_auto]',
5353
'@4xl:grid-cols-[18rem_minmax(auto,48rem)_auto]',
5454
'@7xl:grid-cols-[18rem_minmax(auto,48rem)_14rem]',
55-
'@4xl:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_auto]',
56-
'@7xl:site-width-wide:grid-cols-[18rem_minmax(auto,80rem)_14rem]',
57-
'@4xl:page-no-toc:grid-cols-[minmax(auto,48rem)_auto]',
58-
'@7xl:page-no-toc:grid-cols-[14rem_minmax(auto,48rem)_14rem]',
59-
'@4xl:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[minmax(auto,90rem)_auto]',
60-
'@7xl:[body:has(.site-width-wide,.page-no-toc)_&]:grid-cols-[14rem_minmax(auto,90rem)_14rem]'
55+
'@4xl:layout-full-width:grid-cols-[minmax(auto,90rem)_auto]',
56+
'@7xl:layout-full-width:grid-cols-[14rem_minmax(auto,90rem)_14rem]'
6157
)}
6258
>
6359
{
@@ -115,10 +111,22 @@ export function Footer(props: { context: GitBookSiteContext }) {
115111
customization.footer.groups?.length > 0 ? (
116112
<div
117113
className={tcls(
118-
'@4xl:page-has-toc:col-span-1 @7xl:page-no-toc:col-span-1 col-span-2 @4xl:page-has-toc:col-start-2 @7xl:page-no-toc:col-start-2'
114+
'col-span-2',
115+
'@4xl:layout-default:col-span-1',
116+
'@4xl:layout-default:col-start-2',
117+
'@4xl:layout-openapi:col-span-1',
118+
'@4xl:layout-openapi:col-start-2',
119+
'@7xl:layout-full-width:col-span-1',
120+
'@7xl:layout-full-width:col-start-2'
119121
)}
120122
>
121-
<div className="mx-auto flex max-w-3xl site-width-wide:max-w-screen-2xl @xl:flex-row flex-col @xl:gap-6 gap-10">
123+
<div
124+
className={tcls(
125+
'mx-auto flex max-w-3xl flex-col gap-10',
126+
'layout-full-width:max-w-screen-2xl',
127+
'@xl:flex-row @xl:gap-6'
128+
)}
129+
>
122130
{partition(customization.footer.groups, FOOTER_COLUMNS).map(
123131
(column, columnIndex) => (
124132
<div

packages/gitbook/src/components/Header/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export function Header(props: {
105105
'hover:theme-bold:bg-header-link/3',
106106
variants.generic.length > 1
107107
? 'lg:hidden'
108-
: 'page-no-toc:hidden lg:hidden'
108+
: 'layout-full-width:hidden lg:hidden'
109109
)}
110110
/>
111111
<HeaderLogo context={context} />

packages/gitbook/src/components/PageAside/PageAside.tsx

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,39 @@ export function PageAside(props: {
3636
className={tcls(
3737
'group/aside',
3838
'order-last',
39-
'hidden',
40-
'max-w-0',
4139
'pt-8',
4240
'pb-4',
43-
'opacity-0',
4441

45-
'xl:flex',
42+
// Hide by default
43+
'hidden',
44+
45+
// Show on xl screens for default layout (normal sidebar)
46+
'xl:layout-default:flex',
47+
'xl:layout-default:max-w-56',
48+
'xl:layout-default:opacity-11',
49+
'xl:layout-default:ml-12',
50+
51+
// Show on xl screens for OpenAPI layout (floating overlay)
52+
'xl:layout-openapi:flex',
53+
'xl:layout-openapi:opacity-100',
54+
'xl:layout-openapi:z-10',
55+
'xl:layout-openapi:fixed',
56+
'xl:layout-openapi:right-8',
57+
'xl:layout-openapi:w-60',
58+
'xl:layout-openapi:max-w-60',
59+
'xl:layout-openapi:pb-8',
60+
'xl:layout-openapi:pt-10',
61+
'xl:layout-openapi:ml-0',
62+
63+
// Always hide for full-width layout
64+
'layout-full-width:hidden!',
4665

4766
'overflow-hidden',
4867

49-
'xl:max-w-56',
50-
'xl:opacity-11',
51-
'xl:ml-12',
68+
'max-w-0',
69+
'opacity-0',
5270

71+
// Hide when chat is open
5372
'xl:max-3xl:chat-open:hidden',
5473
'xl:max-3xl:chat-open:max-w-0',
5574
'xl:max-3xl:chat-open:opacity-0',
@@ -85,17 +104,7 @@ export function PageAside(props: {
85104

86105
// Client-side dynamic positioning (CSS vars applied by script)
87106
'lg:[html[style*="--outline-top-offset"]_&]:top-(--outline-top-offset)!',
88-
'lg:[html[style*="--outline-height"]_&]:max-h-(--outline-height)!',
89-
90-
// When in api page mode, we display it as an overlay on non-large resolutions
91-
'xl:max-2xl:page-api-block:z-10',
92-
'xl:max-2xl:page-api-block:fixed',
93-
'xl:max-2xl:page-api-block:right-8',
94-
'xl:max-2xl:page-api-block:w-60',
95-
'xl:max-2xl:page-api-block:max-w-60',
96-
'xl:max-2xl:page-api-block:pb-8',
97-
'xl:max-2xl:page-api-block:pt-10',
98-
'xl:max-2xl:[body:has(.openapi-block):has(.page-has-ancestors)_&]:pt-6.5'
107+
'lg:[html[style*="--outline-height"]_&]:max-h-(--outline-height)!'
99108
)}
100109
>
101110
<div
@@ -104,17 +113,19 @@ export function PageAside(props: {
104113
'min-w-56 shrink-0',
105114
'overflow-hidden',
106115
'w-full',
107-
'xl:max-2xl:rounded-corners:page-api-block:rounded-md',
108-
'xl:max-2xl:circular-corners:page-api-block:rounded-xl',
109-
'xl:max-2xl:page-api-block:border',
110-
'xl:max-2xl:page-api-block:border-tint',
111-
'xl:max-2xl:page-api-block:bg-tint/9',
112-
'xl:max-2xl:page-api-block:backdrop-blur-lg',
113-
'xl:max-2xl:contrast-more:page-api-block:bg-tint',
114-
'xl:max-2xl:page-api-block:hover:shadow-lg',
115-
'xl:max-2xl:page-api-block:hover:shadow-tint-12/1',
116-
'xl:max-2xl:dark:page-api-block:hover:shadow-tint-1/1',
117-
'xl:max-2xl:page-api-block:not-hover:*:hidden'
116+
117+
// OpenAPI layout: floating card styles
118+
'xl:layout-openapi:rounded-md',
119+
'xl:layout-openapi:circular-corners:rounded-xl',
120+
'xl:layout-openapi:border',
121+
'xl:layout-openapi:border-tint',
122+
'xl:layout-openapi:bg-tint/9',
123+
'xl:layout-openapi:backdrop-blur-lg',
124+
'xl:layout-openapi:contrast-more:bg-tint',
125+
'xl:layout-openapi:hover:shadow-lg',
126+
'xl:layout-openapi:hover:shadow-tint-12/1',
127+
'xl:dark:layout-openapi:hover:shadow-tint-1/1',
128+
'xl:layout-openapi:not-hover:*:hidden'
118129
)}
119130
>
120131
<PageAsideHeader context={context} />
@@ -136,6 +147,10 @@ export function PageAside(props: {
136147
);
137148
}
138149

150+
/**
151+
* Header for the aside that shows "ON THIS PAGE" label.
152+
* Only visible in OpenAPI layout (floating overlay mode).
153+
*/
139154
function PageAsideHeader(props: { context: GitBookSiteContext }) {
140155
const { context } = props;
141156
const language = getSpaceLanguage(context);
@@ -144,7 +159,7 @@ function PageAsideHeader(props: { context: GitBookSiteContext }) {
144159
<div
145160
className={tcls(
146161
'hidden',
147-
'xl:max-2xl:page-api-block:flex!',
162+
'xl:layout-openapi:flex!',
148163
'text-xs',
149164
'tracking-wide',
150165
'font-semibold',
@@ -187,7 +202,8 @@ function PageAsideActions(props: {
187202
className={tcls(
188203
'flex flex-col gap-3',
189204
'border-tint-subtle border-t first:border-none',
190-
'sidebar-list-default:px-3 pt-5 first:pt-0 xl:max-2xl:page-api-block:p-5',
205+
'sidebar-list-default:px-3 pt-5 first:pt-0',
206+
'xl:layout-openapi:p-5',
191207
'empty:hidden'
192208
)}
193209
>
@@ -209,7 +225,7 @@ async function PageAsideFooter(props: { context: GitBookSiteContext }) {
209225
className={tcls(
210226
'sticky bottom-0 z-10 mt-auto flex flex-col',
211227
'bg-tint-base theme-gradient-tint:bg-gradient-tint theme-gradient:bg-gradient-primary theme-muted:bg-tint-subtle [html.sidebar-filled.theme-bold.tint_&]:bg-tint-subtle',
212-
'border-tint-subtle xl:max-2xl:page-api-block:border-t xl:max-2xl:page-api-block:p-2',
228+
'border-tint-subtle xl:layout-openapi:border-t xl:layout-openapi:p-2',
213229
'pt-4'
214230
)}
215231
>

packages/gitbook/src/components/PageBody/PageBody.tsx

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import type { JSONDocument, RevisionPageDocument, SiteInsightsDisplayContext } f
33

44
import { getSpaceLanguage } from '@/intl/server';
55
import { t } from '@/intl/translate';
6-
import { hasFullWidthBlock, hasMoreThan, hasTopLevelBlock, isNodeEmpty } from '@/lib/document';
6+
import {
7+
hasFullWidthBlock,
8+
hasMoreThan,
9+
hasOpenAPIBlock,
10+
hasTopLevelBlock,
11+
isNodeEmpty,
12+
} from '@/lib/document';
713
import type { AncestorRevisionPage } from '@/lib/pages';
814
import { tcls } from '@/lib/tailwind';
915
import { DocumentView, DocumentViewSkeleton } from '../DocumentView';
@@ -41,6 +47,7 @@ export function PageBody(props: {
4147
const { customization } = context;
4248

4349
const contentFullWidth = document ? hasFullWidthBlock(document) : false;
50+
const contentHasOpenAPI = document ? hasOpenAPIBlock(document) : false;
4451

4552
// Update blocks can only be at the top level of the document, so we optimize the check.
4653
const contentHasUpdates = document
@@ -55,8 +62,7 @@ export function PageBody(props: {
5562
LINK_PREVIEW_MAX_COUNT
5663
)
5764
: false;
58-
const pageWidthWide = page.layout.width === 'wide';
59-
const siteWidthWide = pageWidthWide || contentFullWidth;
65+
6066
const language = getSpaceLanguage(context);
6167
const updatedAt = page.updatedAt ?? page.createdAt;
6268

@@ -65,23 +71,45 @@ export function PageBody(props: {
6571
(page) => page.type !== 'document' || (page.type === 'document' && !page.hidden)
6672
).length > 0;
6773

68-
const pageHasToc = page.layout.tableOfContents && hasVisibleTOCItems;
74+
const hasTOC = page.layout.tableOfContents && hasVisibleTOCItems;
75+
76+
// Determine layout mode:
77+
// 1. Full-width: No TOC
78+
// 2. OpenAPI: Has TOC + (OpenAPI block OR wide property)
79+
// 3. Default: Has TOC, no OpenAPI blocks, not wide
80+
const layoutMode = !hasTOC
81+
? 'layout-full-width'
82+
: contentHasOpenAPI || page.layout.width === 'wide'
83+
? 'layout-openapi'
84+
: 'layout-default';
85+
86+
// Site-wide width only applies to full-width mode
87+
const siteWidthWide = !hasTOC && (page.layout.width === 'wide' || contentFullWidth);
6988

7089
return (
7190
<CurrentPageProvider page={{ spaceId: context.space.id, pageId: page.id }}>
7291
<main
7392
className={tcls(
7493
'relative min-w-0 flex-1',
7594
'max-w-screen-2xl py-8',
95+
// In full-width layout, expand main to allow cover to go full width
96+
'layout-full-width:max-w-full',
97+
'layout-full-width:px-0',
7698
// Allow words to break if they are too long.
7799
'break-anywhere',
78100
'@container',
79-
pageWidthWide ? 'page-width-wide 3xl:px-8' : 'page-width-default',
101+
// Layout mode class for CSS variants
102+
layoutMode,
103+
// Keep existing classes for backward compatibility
104+
hasTOC ? 'page-has-toc' : 'page-no-toc',
80105
siteWidthWide ? 'site-width-wide' : 'site-width-default',
81-
pageHasToc ? 'page-has-toc' : 'page-no-toc'
106+
// Only apply page-width-wide in full-width mode
107+
!hasTOC && page.layout.width === 'wide'
108+
? 'page-width-wide 3xl:px-8'
109+
: 'page-width-default'
82110
)}
83111
>
84-
<PreservePageLayout siteWidthWide={siteWidthWide} pageHasToc={pageHasToc} />
112+
<PreservePageLayout siteWidthWide={siteWidthWide} layoutMode={layoutMode} hasTOC={hasTOC} />
85113
{page.cover && page.layout.cover && page.layout.coverSize === 'hero' ? (
86114
<PageCover as="hero" page={page} cover={page.cover} context={context} />
87115
) : null}
@@ -128,7 +156,24 @@ export function PageBody(props: {
128156
{
129157
// TODO: after 25/07/2025, we can chage it to a true check as the cache will be updated
130158
page.layout.metadata !== false ? (
131-
<div className="mx-auto mt-6 page-api-block:ml-0 flex max-w-3xl page-full-width:max-w-screen-2xl flex-row flex-wrap items-center gap-4 text-tint contrast-more:text-tint-strong">
159+
<div
160+
className={tcls(
161+
'mx-auto',
162+
'mt-6',
163+
'flex',
164+
'max-w-3xl',
165+
'flex-row',
166+
'flex-wrap',
167+
'items-center',
168+
'gap-4',
169+
'text-tint',
170+
'contrast-more:text-tint-strong',
171+
'layout-openapi:max-w-full',
172+
'layout-openapi:pl-12',
173+
'layout-full-width:max-w-5xl',
174+
'layout-full-width:mx-auto'
175+
)}
176+
>
132177
{updatedAt ? (
133178
<p className="mr-auto text-sm ">
134179
{t(

0 commit comments

Comments
 (0)