From 27d9ad6fe4b934085cbc104e44babe7cffb330c4 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 16 Jun 2026 13:31:47 +0200 Subject: [PATCH] Refactor Header and StructurePreview components to utilize HeaderLayout for improved structure and consistency in header rendering --- .../gitbook/src/components/Header/Header.tsx | 235 ++++++------------ .../src/components/Header/HeaderLayout.tsx | 127 ++++++++++ .../StructurePreview/StructurePreview.tsx | 234 ++++++----------- 3 files changed, 275 insertions(+), 321 deletions(-) create mode 100644 packages/gitbook/src/components/Header/HeaderLayout.tsx diff --git a/packages/gitbook/src/components/Header/Header.tsx b/packages/gitbook/src/components/Header/Header.tsx index bbe808653..4d7d61ac1 100644 --- a/packages/gitbook/src/components/Header/Header.tsx +++ b/packages/gitbook/src/components/Header/Header.tsx @@ -1,12 +1,12 @@ import type { GitBookSiteContext } from '@/lib/context'; -import { CONTAINER_STYLE, HEADER_HEIGHT_DESKTOP } from '@/components/layout'; import { getSpaceLanguage, t } from '@/intl/server'; import { tcls } from '@/lib/tailwind'; import type { SiteSpace } from '@gitbook/api'; import { SocialAccountButton } from '../Footer/SocialAccounts'; import { SearchContainer, getSearchBaseProps } from '../Search'; import { SiteSectionTabs, encodeClientSiteSections } from '../SiteSections'; +import { HeaderLayout } from './HeaderLayout'; import { HeaderLink } from './HeaderLink'; import { HeaderLinkMore } from './HeaderLinkMore'; import { HeaderLinks } from './HeaderLinks'; @@ -41,172 +41,81 @@ export async function Header(props: { ); return ( -
-
-
-
+ 1 + ? 'lg:hidden' + : 'no-sidebar:hidden lg:hidden' )} - > -
- 1 - ? 'lg:hidden' - : 'no-sidebar:hidden lg:hidden' - )} - /> - -
- -
- -
- - {customization.header.links.length > 0 || - headerSocialAccounts.length > 0 || - (!withSections && variants.translations.length > 1) ? ( - - {customization.header.links.map((link) => { + /> + + + } + search={ + + } + links={ + customization.header.links.length > 0 || + headerSocialAccounts.length > 0 || + (!withSections && variants.translations.length > 1) ? ( + + {customization.header.links.map((link) => { + return ; + })} + {headerSocialAccounts.length > 0 ? ( +
+ {headerSocialAccounts.map((account) => { return ( - ); })} - {headerSocialAccounts.length > 0 ? ( -
- {headerSocialAccounts.map((account) => { - return ( - - ); - })} -
- ) : null} - {customization.header.links.length > 0 || - headerSocialAccounts.length > 0 ? ( - - ) : null} - {!withSections && variants.translations.length > 1 ? ( - space.id === siteSpace.id - ) ?? siteSpace - } - siteSpaces={variants.translations} - className="flex! site-header:theme-bold:text-header-link hover:site-header:theme-bold:bg-header-link/3 focus-visible:site-header:theme-bold:bg-header-link/3 aria-expanded:site-header:theme-bold:bg-header-link/5" - /> - ) : null} - +
) : null} -
-
-
- - {visibleSections && withSections ? ( -
+ {customization.header.links.length > 0 || + headerSocialAccounts.length > 0 ? ( + + ) : null} + {!withSections && variants.translations.length > 1 ? ( + space.id === siteSpace.id + ) ?? siteSpace + } + siteSpaces={variants.translations} + className="flex! site-header:theme-bold:text-header-link hover:site-header:theme-bold:bg-header-link/3 focus-visible:site-header:theme-bold:bg-header-link/3 aria-expanded:site-header:theme-bold:bg-header-link/5" + /> + ) : null} + + ) : null + } + sections={ + visibleSections && withSections ? ( {variants.translations.length > 1 ? ( ) : null} -
- ) : null} -
+ ) : null + } + /> ); } diff --git a/packages/gitbook/src/components/Header/HeaderLayout.tsx b/packages/gitbook/src/components/Header/HeaderLayout.tsx new file mode 100644 index 000000000..1c76947f3 --- /dev/null +++ b/packages/gitbook/src/components/Header/HeaderLayout.tsx @@ -0,0 +1,127 @@ +import type { CustomizationSearchStyle } from '@gitbook/api'; +import type React from 'react'; + +import { CONTAINER_STYLE, HEADER_HEIGHT_DESKTOP } from '@/components/layout'; +import { tcls } from '@/lib/tailwind'; + +const PROMINENT_SEARCH_STYLE: CustomizationSearchStyle = 'prominent' as CustomizationSearchStyle; + +/** + * Shared visual layout for the site header. + * + * The live site and structure preview provide different interactive pieces, but the shell, + * spacing, responsive behavior, and theme classes should stay identical. + */ +export function HeaderLayout(props: { + leading: React.ReactNode; + search: React.ReactNode; + searchStyle: CustomizationSearchStyle; + withTopHeader?: boolean; + links?: React.ReactNode; + sections?: React.ReactNode; +}) { + const { leading, search, searchStyle, withTopHeader, links, sections } = props; + const hasProminentSearch = searchStyle === PROMINENT_SEARCH_STYLE; + + return ( +
+
+
+
+
+ {leading} +
+ +
+ {search} +
+ + {links} +
+
+
+ + {sections ? ( +
+ {sections} +
+ ) : null} +
+ ); +} diff --git a/packages/gitbook/src/components/StructurePreview/StructurePreview.tsx b/packages/gitbook/src/components/StructurePreview/StructurePreview.tsx index f369b1d53..0c501e803 100644 --- a/packages/gitbook/src/components/StructurePreview/StructurePreview.tsx +++ b/packages/gitbook/src/components/StructurePreview/StructurePreview.tsx @@ -13,6 +13,7 @@ import { tcls } from '@/lib/tailwind'; import { tString, useLanguage } from '@/intl/client'; import { AIChatButtonView, AIChatIcon, getAIChatName } from '../AIChat'; +import { HeaderLayout } from '../Header/HeaderLayout'; import { HeaderLinkItem, HeaderLinkMenuItem, @@ -39,7 +40,6 @@ import { } from '../Header/SpacesDropdownData'; import headerLinksStyles from '../Header/headerLinks.module.css'; import { SearchHeaderInput } from '../Search'; -import { CONTAINER_STYLE, HEADER_HEIGHT_DESKTOP } from '../layout'; import { Button } from '../primitives'; import { SOCIAL_PLATFORM_ICONS, @@ -112,161 +112,79 @@ function StructurePreviewHeader(props: { snapshot: StructurePreviewSnapshot }) { ); return ( -
-
-
-
-
- -
- -
- - {previewAssistants.map((assistant, index) => ( - - ))} -
- - {customization.header.links.length > 0 || - headerSocialAccounts.length > 0 || - (!withSections && variants.translations.length > 1) ? ( - - {customization.header.links.map((link, index) => ( - - ))} - {headerSocialAccounts.length > 0 ? ( -
- {headerSocialAccounts.map((account) => { - const icon = SOCIAL_PLATFORM_ICONS[account.platform]; - return icon ? ( -
- ) : null} - {customization.header.links.length > 0 || - headerSocialAccounts.length > 0 ? ( - - ) : null} - {!withSections && variants.translations.length > 1 ? ( - - ) : null} -
+ } + search={ + <> + + {previewAssistants.map((assistant, index) => ( + + ))} + + } + links={ + customization.header.links.length > 0 || + headerSocialAccounts.length > 0 || + (!withSections && variants.translations.length > 1) ? ( + + {customization.header.links.map((link, index) => ( + + ))} + {headerSocialAccounts.length > 0 ? ( +
+ {headerSocialAccounts.map((account) => { + const icon = SOCIAL_PLATFORM_ICONS[account.platform]; + return icon ? ( +
) : null} -
-
-
- - {sections && withSections ? ( -
+ {customization.header.links.length > 0 || + headerSocialAccounts.length > 0 ? ( + + ) : null} + {!withSections && variants.translations.length > 1 ? ( + + ) : null} + + ) : null + } + sections={ + sections && withSections ? ( {variants.translations.length > 1 ? ( ) : null} -
- ) : null} -
+ ) : null + } + /> ); }