Skip to content

Commit 23584c9

Browse files
Update header styling (#2613)
Co-authored-by: Valentino Hudhra <v.hudhra@gmail.com> Co-authored-by: Valentino Hudhra <2587839+valentin0h@users.noreply.github.com>
1 parent 2f76712 commit 23584c9

18 files changed

+411
-305
lines changed

.changeset/curly-phones-judge.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook': patch
3+
---
4+
5+
Update the site header with new styling, a new search button, and refactored layout

packages/gitbook/e2e/pages.spec.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ const testCases: TestsCase[] = [
112112
name: 'Variants dropdown',
113113
url: '',
114114
run: async (page) => {
115-
const spaceDrowpdown = page.locator('[data-testid="space-dropdown-button"]');
115+
const spaceDrowpdown = page
116+
.locator('[data-testid="space-dropdown-button"]')
117+
.locator('visible=true');
116118
await spaceDrowpdown.waitFor();
117119
},
118120
},
@@ -128,7 +130,9 @@ const testCases: TestsCase[] = [
128130
name: 'Customized variant titles are displayed',
129131
url: '',
130132
run: async (page) => {
131-
const spaceDrowpdown = page.locator('[data-testid="space-dropdown-button"]');
133+
const spaceDrowpdown = page
134+
.locator('[data-testid="space-dropdown-button"]')
135+
.locator('visible=true');
132136
await spaceDrowpdown.click();
133137

134138
const variantSelectionDropdown = page.locator(
@@ -160,9 +164,9 @@ const testCases: TestsCase[] = [
160164
url: 'api-multi-versions/reference/api-reference/pets',
161165
screenshot: false,
162166
run: async (page) => {
163-
const spaceDrowpdown = await page.waitForSelector(
164-
'[data-testid="space-dropdown-button"]',
165-
);
167+
const spaceDrowpdown = await page
168+
.locator('[data-testid="space-dropdown-button"]')
169+
.locator('visible=true');
166170
await spaceDrowpdown.click();
167171

168172
// Click the second variant in the dropdown
@@ -183,9 +187,9 @@ const testCases: TestsCase[] = [
183187
url: 'api-multi-versions-share-links/8tNo6MeXg7CkFMzSSz81/reference/api-reference/pets',
184188
screenshot: false,
185189
run: async (page) => {
186-
const spaceDrowpdown = await page.waitForSelector(
187-
'[data-testid="space-dropdown-button"]',
188-
);
190+
const spaceDrowpdown = await page
191+
.locator('[data-testid="space-dropdown-button"]')
192+
.locator('visible=true');
189193
await spaceDrowpdown.click();
190194

191195
// Click the second variant in the dropdown
@@ -218,9 +222,9 @@ const testCases: TestsCase[] = [
218222
return `api-multi-versions-va/reference/api-reference/pets?jwt_token=${token}`;
219223
})(),
220224
run: async (page) => {
221-
const spaceDrowpdown = await page.waitForSelector(
222-
'[data-testid="space-dropdown-button"]',
223-
);
225+
const spaceDrowpdown = await page
226+
.locator('[data-testid="space-dropdown-button"]')
227+
.locator('visible=true');
224228
await spaceDrowpdown.click();
225229

226230
// Click the second variant in the dropdown

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function Dropdown<E extends HTMLElement>(props: {
2525
const dropdownId = useId();
2626

2727
return (
28-
<div className={tcls('group/dropdown', 'relative flex')}>
28+
<div className={tcls('group/dropdown', 'relative flex shrink min-w-0')}>
2929
{button({
3030
id: dropdownId,
3131
tabIndex: 0,

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

Lines changed: 137 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { HeaderLogo } from './HeaderLogo';
1515
import { SpacesDropdown } from './SpacesDropdown';
1616
import { SearchButton } from '../Search';
1717
import { SiteSectionTabs } from '../SiteSectionTabs';
18+
import { HeaderMobileMenu } from './HeaderMobileMenu';
1819
/**
1920
* Render the header for the space.
2021
*/
@@ -45,104 +46,156 @@ export function Header(props: {
4546
'w-full',
4647
'flex-none',
4748
'shadow-thinbottom',
49+
'dark:shadow-light/2',
50+
'bg-light',
51+
'dark:bg-dark',
4852
withTopHeader ? null : 'lg:hidden',
49-
'lg:z-10',
50-
'dark:shadow-light/1',
51-
`${isCustomizationDefault || !withTopHeader ? 'bg-light' : 'bg-header-background'}`,
52-
`${
53-
isCustomizationDefault || !withTopHeader
54-
? 'dark:bg-dark'
55-
: 'bg-header-background'
56-
}`,
53+
'text-sm',
54+
'bg-opacity-9',
55+
'dark:bg-opacity-9',
56+
'backdrop-blur-lg',
57+
'contrast-more:bg-opacity-11',
58+
'contrast-more:dark:bg-opacity-11',
5759
)}
5860
>
59-
<div className={tcls('scroll-nojump')}>
60-
<div
61-
className={tcls(
62-
'gap-4',
63-
'grid',
64-
'grid-flow-col',
65-
'auto-cols-[auto_auto_1fr_auto]',
66-
'h-16',
67-
'items-center',
68-
'align-center',
69-
'justify-between',
70-
'w-full',
71-
CONTAINER_STYLE,
72-
)}
73-
>
74-
<HeaderLogo site={site} space={space} customization={customization} />
75-
<div className="z-20">
76-
{!hasSiteSections && isMultiVariants ? (
77-
<SpacesDropdown space={space} spaces={spaces} />
78-
) : null}
79-
</div>
80-
<HeaderLinks>
81-
{customization.header.links.map((link, index) => {
82-
return (
83-
<HeaderLink
84-
key={index}
85-
link={link}
86-
context={context}
87-
customization={customization}
88-
/>
89-
);
90-
})}
91-
<HeaderLinkMore
92-
label={t(getSpaceLanguage(customization), 'more')}
93-
links={customization.header.links}
94-
context={context}
95-
customization={customization}
96-
/>
97-
</HeaderLinks>
61+
<div
62+
className={tcls(
63+
!isCustomizationDefault &&
64+
withTopHeader && [
65+
'bg-header-background',
66+
'shadow-thinbottom',
67+
'dark:shadow-light/2',
68+
],
69+
)}
70+
>
71+
<div className={tcls('scroll-nojump')}>
9872
<div
9973
className={tcls(
74+
'gap-4',
75+
'lg:gap-8',
10076
'flex',
101-
'md:w-56',
102-
'grow-0',
103-
'shrink-0',
104-
'justify-self-end',
77+
'h-16',
78+
'items-center',
79+
'justify-between',
80+
'w-full',
81+
CONTAINER_STYLE,
10582
)}
10683
>
107-
<Suspense fallback={null}>
108-
<SearchButton
109-
style={
110-
!isCustomizationDefault && withTopHeader
111-
? [
112-
'bg-header-link/3',
113-
'shadow-sm',
114-
'ring-header-link/3',
115-
'[&>span]:!text-header-link/7',
116-
'[&_svg]:text-header-link',
117-
'contrast-more:bg-transparent',
118-
'contrast-more:ring-header-link',
119-
'contrast-more:[&>span]:!text-header-link',
120-
'dark:bg-header-link/3',
121-
'dark:ring-header-link/3',
122-
'[&>span]:!text-header-link/7',
123-
'dark:[&_svg]:text-header-link',
124-
'dark:contrast-more:bg-transparent',
125-
'dark:contrast-more:ring-header-link',
126-
'dark:contrast-more:[&>span]:!text-header-link',
127-
]
128-
: null
129-
}
130-
>
131-
<span className={tcls('flex-1')}>
132-
{t(
133-
getSpaceLanguage(customization),
134-
customization.aiSearch.enabled ? 'search_or_ask' : 'search',
135-
)}
136-
</span>
137-
</SearchButton>
138-
</Suspense>
84+
<div className="flex max-w-full shrink min-w-0 gap-2 lg:gap-4 justify-start items-center">
85+
<HeaderMobileMenu
86+
className={tcls(
87+
'lg:hidden',
88+
'-ml-2',
89+
customization.header.preset ===
90+
CustomizationHeaderPreset.Default
91+
? ['text-dark', 'dark:text-light']
92+
: 'text-header-link',
93+
)}
94+
/>
95+
<HeaderLogo site={site} space={space} customization={customization} />
96+
{!hasSiteSections && isMultiVariants ? (
97+
<div className="z-20 shrink hidden sm:block">
98+
<SpacesDropdown space={space} spaces={spaces} />
99+
</div>
100+
) : null}
101+
</div>
102+
103+
{customization.header.links.length > 0 && (
104+
<HeaderLinks>
105+
{customization.header.links.map((link, index) => {
106+
return (
107+
<HeaderLink
108+
key={index}
109+
link={link}
110+
context={context}
111+
customization={customization}
112+
/>
113+
);
114+
})}
115+
<HeaderLinkMore
116+
label={t(getSpaceLanguage(customization), 'more')}
117+
links={customization.header.links}
118+
context={context}
119+
customization={customization}
120+
/>
121+
</HeaderLinks>
122+
)}
123+
<div
124+
className={tcls(
125+
'flex',
126+
'md:w-56',
127+
'grow-0',
128+
'shrink-0',
129+
'justify-self-end',
130+
)}
131+
>
132+
<Suspense fallback={null}>
133+
<SearchButton
134+
style={
135+
!isCustomizationDefault && withTopHeader
136+
? [
137+
'bg-header-link/2',
138+
'dark:bg-header-link/2',
139+
'hover:bg-header-link/3',
140+
'dark:hover:bg-header-link/3',
141+
142+
'text-header-link/8',
143+
'dark:text-header-link/8',
144+
'hover:text-header-link',
145+
'dark:hover:text-header-link',
146+
147+
'ring-header-link/4',
148+
'dark:ring-header-link/4',
149+
'hover:ring-header-link/5',
150+
'dark:hover:ring-header-link/5',
151+
152+
'[&_svg]:text-header-link/10',
153+
'dark:[&_svg]:text-header-link/10',
154+
'[&_.shortcut]:text-header-link/8',
155+
'dark:[&_.shortcut]:text-header-link/8',
156+
157+
'contrast-more:bg-header-background',
158+
'contrast-more:text-header-link',
159+
'contrast-more:ring-header-link',
160+
'contrast-more:hover:bg-header-background',
161+
'contrast-more:hover:ring-header-link',
162+
'contrast-more:focus:text-header-link',
163+
'contrast-more:focus:bg-header-background',
164+
'contrast-more:focus:ring-header-link',
165+
'dark:contrast-more:bg-header-background',
166+
'dark:contrast-more:text-header-link',
167+
'dark:contrast-more:ring-header-link',
168+
'dark:contrast-more:hover:bg-header-background',
169+
'dark:contrast-more:hover:ring-header-link',
170+
'dark:contrast-more:focus:text-header-link',
171+
'dark:contrast-more:focus:bg-header-background',
172+
'dark:contrast-more:focus:ring-header-link',
173+
174+
'shadow-none',
175+
]
176+
: null
177+
}
178+
>
179+
<span className={tcls('flex-1')}>
180+
{t(
181+
getSpaceLanguage(customization),
182+
customization.aiSearch.enabled
183+
? 'search_or_ask'
184+
: 'search',
185+
)}
186+
...
187+
</span>
188+
</SearchButton>
189+
</Suspense>
190+
</div>
139191
</div>
140192
</div>
141193
</div>
142194
{sections ? (
143195
<div
144196
className={tcls(
145-
'w-full shadow-thintop dark:shadow-light/1 bg-light dark:bg-dark mt-0.5',
197+
'scroll-nojump',
198+
'w-full',
146199
// Handle long section tabs, particularly on smaller screens.
147200
'overflow-x-auto hide-scroll',
148201
)}

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export async function HeaderLink(props: {
3333
if (link.links && link.links.length > 0) {
3434
return (
3535
<Dropdown
36+
className="shrink"
3637
button={(buttonProps) => {
3738
if (!target) {
3839
return (
@@ -147,12 +148,9 @@ function HeaderItemButton(
147148

148149
function getHeaderLinkClassName(props: { headerPreset: CustomizationHeaderPreset }) {
149150
return tcls(
150-
'overflow-hidden',
151-
'text-sm lg:text-base',
152-
'flex flex-row items-center',
153-
'whitespace-nowrap',
151+
'flex items-center shrink',
154152
'hover:text-header-link-400 dark:hover:text-light',
155-
'truncate',
153+
'min-w-0',
156154

157155
props.headerPreset === CustomizationHeaderPreset.Default
158156
? ['text-dark/8', 'dark:text-light/8']
@@ -164,7 +162,7 @@ function HeaderItemLink(props: HeaderLinkNavItemProps) {
164162
const { headerPreset, title, isDropdown, href, ...rest } = props;
165163
return (
166164
<Link href={href} className={getHeaderLinkClassName({ headerPreset })} {...rest}>
167-
{title}
165+
<span className="truncate min-w-0">{title}</span>
168166
{isDropdown ? <DropdownChevron /> : null}
169167
</Link>
170168
);

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import React from 'react';
1111
import { ContentRefContext, resolveContentRef } from '@/lib/references';
1212
import { tcls } from '@/lib/tailwind';
1313

14-
import { Dropdown, DropdownMenu, DropdownMenuItem } from './Dropdown';
14+
import { Dropdown, DropdownChevron, DropdownMenu, DropdownMenuItem } from './Dropdown';
1515
import styles from './headerLinks.module.css';
1616

1717
/**
@@ -31,15 +31,22 @@ export function HeaderLinkMore(props: {
3131
const renderButton = () => (
3232
<button
3333
className={tcls(
34-
'px-1',
35-
!isCustomizationDefault
36-
? ['text-header-link-500']
37-
: ['text-dark/8', 'dark:text-light/8', 'dark:hover:text-light'],
38-
'hover:text-header-link-400',
34+
isCustomizationDefault
35+
? [
36+
'text-dark/8',
37+
'dark:text-light/8',
38+
'hover:text-primary',
39+
'dark:hover:text-primary',
40+
]
41+
: ['text-header-link', 'hover:text-header-link/8'],
42+
'flex',
43+
'gap-1',
44+
'items-center',
3945
)}
4046
>
4147
<span className="sr-only">{label}</span>
42-
<Icon icon="ellipsis" className={tcls('opacity-6', 'size-3', 'ms-1')} />
48+
<Icon icon="ellipsis" className={tcls('size-4')} />
49+
<DropdownChevron />
4350
</button>
4451
);
4552

0 commit comments

Comments
 (0)