Skip to content

Inline search modal #3416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9d8f251
Initial try-out
zenoachtig Jun 30, 2025
e822a53
Cleanup
zenoachtig Jun 30, 2025
d7ab5d9
Style results
zenoachtig Jul 1, 2025
27dc51e
Tweak styling of non-ask empty state
zenoachtig Jul 1, 2025
41953a9
Get ask working
zenoachtig Jul 1, 2025
4f2bf71
Cleanup
zenoachtig Jul 1, 2025
1f28b52
Make sidebar-only mode work better
zenoachtig Jul 1, 2025
7a9b8a6
Fix some focus bugs
zenoachtig Jul 1, 2025
6d65312
Standardise header button style
zenoachtig Jul 1, 2025
60f0a44
Tweaks and tests
zenoachtig Jul 2, 2025
685a838
Fix mobile input
zenoachtig Jul 2, 2025
f06b193
Add search scope toggle
zenoachtig Jul 2, 2025
6225caa
Properly hide searchtoggle when not multivariants
zenoachtig Jul 2, 2025
72ebee4
Fix input focus, add clear button
zenoachtig Jul 2, 2025
1f01cff
Theme-bold input colors
zenoachtig Jul 2, 2025
ce25651
Tweaks to popover showing
zenoachtig Jul 2, 2025
d2446f4
Change button to div to prevent nested interactive elements
zenoachtig Jul 2, 2025
120ac7a
Typecheck
zenoachtig Jul 2, 2025
2ec9bbf
Fix search input sizing
zenoachtig Jul 2, 2025
1aa0f62
Changeset
zenoachtig Jul 2, 2025
4e5590a
Merge branch 'main' into zeno/rnd-7437-inline-search-modal
zenoachtig Jul 2, 2025
7a728a9
Merge branch 'main' into zeno/rnd-7437-inline-search-modal
zenoachtig Jul 4, 2025
3c38835
Use `ai.mode` instead of `aiSearch.enabled`
zenoachtig Jul 4, 2025
8e4d45e
Review
zenoachtig Jul 4, 2025
29c43ba
Update SearchInput.tsx
zenoachtig Jul 4, 2025
1b3fa96
Improve state management
zenoachtig Jul 4, 2025
2d25257
Improve clear button
zenoachtig Jul 4, 2025
cc4bdc5
Update SearchInput.tsx
zenoachtig Jul 4, 2025
25dba09
Fix scroll-padding issue
zenoachtig Jul 7, 2025
8a310e5
Fix empty search state not launching on page load
zenoachtig Jul 7, 2025
7cbe709
Review comments
zenoachtig Jul 7, 2025
928a541
Merge branch 'main' into zeno/rnd-7437-inline-search-modal
zenoachtig Jul 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/purple-toys-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gitbook": minor
---

New search layout
4 changes: 2 additions & 2 deletions packages/gitbook/src/components/AIChat/AIChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import { useNow } from '../hooks';
import { Button } from '../primitives';
import { DropdownMenu, DropdownMenuItem } from '../primitives/DropdownMenu';
import AIChatIcon from './AIChatIcon';
import { AIChatIcon } from './AIChatIcon';
import { AIChatInput } from './AIChatInput';
import { AIChatMessages } from './AIChatMessages';
import AIChatSuggestedQuestions from './AIChatSuggestedQuestions';
Expand Down Expand Up @@ -86,7 +86,7 @@ export function AIChatWindow(props: { chat: AIChatState }) {

return (
<div
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enterFromRight lg:pr-4 lg:pl-0 xl:w-96"
className="ai-chat inset-y-0 right-0 z-40 mx-auto flex max-w-3xl animate-present scroll-mt-36 px-4 py-4 transition-all duration-300 sm:px-6 lg:fixed lg:w-80 lg:animate-enterFromRight lg:pr-4 lg:pl-0 xl:w-96"
ref={containerRef}
>
<div className="relative flex h-full grow flex-col overflow-hidden circular-corners:rounded-3xl rounded-corners:rounded-md bg-tint-base text-sm text-tint depth-subtle:shadow-lg shadow-tint ring-1 ring-tint-subtle">
Expand Down
9 changes: 5 additions & 4 deletions packages/gitbook/src/components/AIChat/AIChatButton.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
'use client';
import { tString, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind';
import { useAIChatController, useAIChatState } from '../AI/useAIChat';
import { Button } from '../primitives';
import AIChatIcon from './AIChatIcon';
import { AIChatIcon } from './AIChatIcon';

/**
* Button to open/close the AI chat.
*/
export function AIChatButton() {
export function AIChatButton(props: { className?: string }) {
const chatController = useAIChatController();
const chat = useAIChatState();

Expand All @@ -18,8 +19,8 @@ export function AIChatButton() {
icon={<AIChatIcon />}
iconOnly
size="default"
variant="secondary"
className="!px-3 bg-tint-base py-2.5"
variant="header"
className={tcls('h-9 px-2.5', props.className)}
label={tString(language, 'ai_chat_assistant_name')}
onClick={() => {
if (chat.opened) {
Expand Down
4 changes: 1 addition & 3 deletions packages/gitbook/src/components/AIChat/AIChatIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface AIChatIconProps extends React.SVGProps<SVGSVGElement> {
state?: 'thinking' | 'default';
}

const AIChatIcon = ({
export const AIChatIcon = ({
className = 'size-4',
size,
state = 'default',
Expand Down Expand Up @@ -60,5 +60,3 @@ const AIChatIcon = ({
</svg>
);
};

export default AIChatIcon;
1 change: 1 addition & 0 deletions packages/gitbook/src/components/AIChat/AIChatMessages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function AIChatMessages(props: {
message.content ? 'animate-[fadeIn_500ms_both]' : '',
'shrink-0',
'last:min-h-[calc(100%-5rem)]',
'scroll-mt-36',
message.role === AIMessageRole.User
? 'max-w-[80%] self-end rounded-md bg-tint px-4 py-2'
: ''
Expand Down
2 changes: 2 additions & 0 deletions packages/gitbook/src/components/AIChat/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './AIChat';
export * from './AIChatButton';
export * from './AIChatIcon';
54 changes: 3 additions & 51 deletions packages/gitbook/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import type { GitBookSiteContext } from '@/lib/context';
import { Suspense } from 'react';

import { CONTAINER_STYLE, HEADER_HEIGHT_DESKTOP } from '@/components/layout';
import { getSpaceLanguage, t } from '@/intl/server';
import { tcls } from '@/lib/tailwind';

import { CustomizationAIMode } from '@gitbook/api';
import { AIChatButton } from '../AIChat/AIChatButton';
import { SearchButton } from '../Search';
import { SiteSectionTabs, encodeClientSiteSections } from '../SiteSections';
import { HeaderLink } from './HeaderLink';
import { HeaderLinkMore } from './HeaderLinkMore';
Expand All @@ -22,9 +17,9 @@ import { SpacesDropdown } from './SpacesDropdown';
export function Header(props: {
context: GitBookSiteContext;
withTopHeader?: boolean;
withAIChat?: boolean;
search?: React.ReactNode;
}) {
const { context, withTopHeader, withAIChat } = props;
const { context, withTopHeader, search } = props;
const { siteSpace, siteSpaces, sections, customization } = context;

return (
Expand Down Expand Up @@ -123,50 +118,7 @@ export function Header(props: {
: ['order-last']
)}
>
<Suspense fallback={null}>
<SearchButton
style={[
'theme-bold:bg-header-link/2',
'theme-bold:hover:bg-header-link/3',

'theme-bold:text-header-link/8',
'theme-bold:hover:text-header-link',

'theme-bold:ring-header-link/4',
'theme-bold:hover:ring-header-link/5',

'theme-bold:[&_svg]:text-header-link/10',
'theme-bold:[&_.shortcut]:text-header-link/8',

'theme-bold:contrast-more:bg-header-background',
'theme-bold:contrast-more:text-header-link',
'theme-bold:contrast-more:ring-header-link',
'theme-bold:contrast-more:hover:bg-header-background',
'theme-bold:contrast-more:hover:ring-header-link',
'theme-bold:contrast-more:focus:text-header-link',
'theme-bold:contrast-more:focus:bg-header-background',
'theme-bold:contrast-more:focus:ring-header-link',

'theme-bold:shadow-none',
'theme-bold:hover:shadow-none',
'whitespace-nowrap',
]}
>
<span className={tcls('flex-1')}>
{t(
getSpaceLanguage(customization),
// TODO: remove aiSearch and optional chain once the cache has been fully updated (after 11/07/2025)
customization.aiSearch?.enabled ||
customization.ai?.mode !==
CustomizationAIMode.None
? 'search_or_ask'
: 'search'
)}
...
</span>
</SearchButton>
</Suspense>
{withAIChat && <AIChatButton />}
{search}
</div>

{customization.header.links.length > 0 && (
Expand Down
6 changes: 2 additions & 4 deletions packages/gitbook/src/components/Header/HeaderLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function HeaderItemButton(
const variant = (() => {
switch (linkStyle) {
case 'button-secondary':
return 'secondary';
return 'header';
case 'button-primary':
return 'primary';
default:
Expand All @@ -120,9 +120,7 @@ function HeaderItemButton(
{
'button-primary':
'theme-bold:bg-header-link theme-bold:text-header-background theme-bold:shadow-none theme-bold:hover:bg-header-link theme-bold:hover:text-header-background theme-bold:hover:shadow-none',
'button-secondary': tcls(
'theme-bold:bg-header-link/2 theme-gradient:bg-tint-base theme-muted:bg-tint-base theme-bold:text-header-link theme-bold:shadow-none theme-bold:ring-header-link/4 theme-bold:hover:bg-header-link/3 theme-bold:hover:text-header-link theme-bold:hover:shadow-none theme-bold:hover:ring-header-link/5 theme-bold:contrast-more:bg-header-background theme-bold:contrast-more:text-header-link theme-bold:contrast-more:ring-header-link theme-bold:contrast-more:hover:ring-header-link'
),
'button-secondary': '',
}[linkStyle]
)}
insights={{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
CustomizationHeaderPreset,
CustomizationIconsStyle,
CustomizationSidebarBackgroundStyle,
CustomizationSidebarListStyle,
Expand Down Expand Up @@ -86,11 +85,6 @@ export async function CustomizationRootLayout(props: {
suppressHydrationWarning
lang={customization.internationalization.locale}
className={tcls(
/* This offset is used when scrolling to elements on the page, like #anchors and (on mobile) the latest AI chat message.
* It is set to the maximum height of the header (64px) + sections (44px) + offset (40px) */
customization.header.preset === CustomizationHeaderPreset.None
? null
: 'scroll-pt-[148px]',
customization.styling.corners && `${customization.styling.corners}-corners`,
'theme' in customization.styling && `theme-${customization.styling.theme}`,
tintColor ? ' tint' : 'no-tint',
Expand Down
3 changes: 3 additions & 0 deletions packages/gitbook/src/components/Search/HighlightQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export function HighlightQuery(props: {
'py-0.5',
'rounded',
'straight-corners:rounded-sm',
'transition-colors',
'group-hover:bg-primary-active',
'group-hover:text-contrast-primary-active',
'group-[.is-active]:bg-primary-active',
'group-[.is-active]:text-contrast-primary-active',
],
Expand Down
18 changes: 8 additions & 10 deletions packages/gitbook/src/components/Search/SearchAskAnswer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,22 @@ export function SearchAskAnswer(props: { query: string }) {
}, [setAskState]);

const loading = (
<div className={tcls('w-full', 'flex', 'items-center', 'justify-center')}>
<Loading className={tcls('w-6', 'py-8', 'text-primary-subtle')} />
<div className="flex grow items-center justify-center">
<Loading className={tcls('size-6', 'text-tint/6')} />
</div>
);

return (
<div className={tcls('max-h-[60vh]', 'overflow-y-auto')}>
<div className="flex min-h-full p-4">
{askState?.type === 'answer' ? (
<React.Suspense fallback={loading}>
<TransitionAnswerBody answer={askState.answer} placeholder={loading} />
</React.Suspense>
) : null}
{askState?.type === 'error' ? (
<div className={tcls('p-4')}>{t(language, 'search_ask_error')}</div>
<div className={tcls('grow p-4 text-center')}>
{t(language, 'search_ask_error')}
</div>
) : null}
{askState?.type === 'loading' ? loading : null}
</div>
Expand Down Expand Up @@ -138,10 +140,7 @@ function AnswerBody(props: { answer: AskAnswerResult }) {

return (
<>
<div
data-testid="search-ask-answer"
className={tcls('my-4', 'sm:mt-6', 'px-4', 'sm:px-12', 'text-tint-strong')}
>
<div data-testid="search-ask-answer" className="text-tint-strong">
{answer.body ?? t(language, 'search_ask_no_answer')}
{answer.followupQuestions.length > 0 ? (
<AnswerFollowupQuestions followupQuestions={answer.followupQuestions} />
Expand Down Expand Up @@ -212,8 +211,7 @@ function AnswerSources(props: {
'gap-2',
'mt-4',
'sm:mt-6',
'py-4',
'px-4',
'pt-4',
'border-t',
'border-subtle'
)}
Expand Down
144 changes: 0 additions & 144 deletions packages/gitbook/src/components/Search/SearchButton.tsx

This file was deleted.

Loading