mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-24 11:26:31 +00:00
Initial styling
This commit is contained in:
@@ -18,8 +18,7 @@ export function HighlightQuery(props: {
|
||||
'text-bold',
|
||||
'bg-primary',
|
||||
'text-contrast-primary',
|
||||
'px-0.5',
|
||||
'-mx-0.5',
|
||||
'px-1',
|
||||
'py-0.5',
|
||||
'rounded',
|
||||
'straight-corners:rounded-sm',
|
||||
|
||||
@@ -94,7 +94,7 @@ export function SearchAskAnswer(props: { query: string }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={tcls('max-h-[60vh]', 'overflow-y-auto')}>
|
||||
<>
|
||||
{askState?.type === 'answer' ? (
|
||||
<React.Suspense fallback={loading}>
|
||||
<TransitionAnswerBody answer={askState.answer} placeholder={loading} />
|
||||
@@ -104,7 +104,7 @@ export function SearchAskAnswer(props: { query: string }) {
|
||||
<div className={tcls('p-4')}>{t(language, 'search_ask_error')}</div>
|
||||
) : null}
|
||||
{askState?.type === 'loading' ? loading : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
'use client';
|
||||
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React from 'react';
|
||||
@@ -219,8 +217,9 @@ function SearchModalBody(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'bg-tint-base',
|
||||
'max-w-prose',
|
||||
'max-w-screen-lg',
|
||||
'mx-auto',
|
||||
'min-h-[30dvh]',
|
||||
'max-h-[70dvh]',
|
||||
'w-full',
|
||||
'rounded-lg',
|
||||
@@ -236,69 +235,72 @@ function SearchModalBody(
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-start',
|
||||
state.query !== null ? 'border-b' : null,
|
||||
'border-tint-subtle'
|
||||
)}
|
||||
>
|
||||
<div className={tcls('p-2', 'pl-4', 'pt-4')}>
|
||||
<Icon icon="magnifying-glass" className={tcls('size-4', 'text-tint-subtle')} />
|
||||
</div>
|
||||
<div className="grid grow grid-rows-[auto_1fr_1fr] overflow-hidden md:grid-cols-[1fr_1fr] md:grid-rows-[auto_1fr]">
|
||||
<div
|
||||
className={tcls(
|
||||
'w-full',
|
||||
'flex',
|
||||
'flex-row',
|
||||
'flex-wrap',
|
||||
'gap-y-0',
|
||||
'gap-x-4',
|
||||
'items-end'
|
||||
'items-start',
|
||||
state.query !== null ? 'border-b' : null,
|
||||
'border-tint-subtle',
|
||||
'col-span-full'
|
||||
)}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={state.query}
|
||||
onKeyDown={onKeyDown}
|
||||
onChange={onChange}
|
||||
<div
|
||||
className={tcls(
|
||||
'text-tint-strong',
|
||||
'placeholder:text-tint',
|
||||
'w-full',
|
||||
'flex',
|
||||
'resize-none',
|
||||
'flex-1',
|
||||
'h-12',
|
||||
'p-2',
|
||||
'focus:outline-none',
|
||||
'bg-transparent',
|
||||
'whitespace-pre-line'
|
||||
'flex-row',
|
||||
'flex-wrap',
|
||||
'gap-y-0',
|
||||
'gap-x-4',
|
||||
'items-end'
|
||||
)}
|
||||
placeholder={tString(
|
||||
language,
|
||||
withAsk ? 'search_ask_input_placeholder' : 'search_input_placeholder'
|
||||
)}
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={state.query}
|
||||
onKeyDown={onKeyDown}
|
||||
onChange={onChange}
|
||||
className={tcls(
|
||||
'text-tint-strong',
|
||||
'placeholder:text-tint',
|
||||
'flex',
|
||||
'resize-none',
|
||||
'flex-1',
|
||||
'py-4',
|
||||
'px-8',
|
||||
'focus:outline-none',
|
||||
'bg-transparent',
|
||||
'whitespace-pre-line'
|
||||
)}
|
||||
placeholder={tString(
|
||||
language,
|
||||
withAsk
|
||||
? 'search_ask_input_placeholder'
|
||||
: 'search_input_placeholder'
|
||||
)}
|
||||
spellCheck="false"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
/>
|
||||
{isMultiVariants ? <SearchScopeToggle spaceTitle={spaceTitle} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto md:col-start-1 md:row-start-2">
|
||||
<SearchResults
|
||||
ref={resultsRef}
|
||||
global={isMultiVariants && state.global}
|
||||
query={normalizedQuery}
|
||||
withAsk={withAsk}
|
||||
onSwitchToAsk={onSwitchToAsk}
|
||||
/>
|
||||
{isMultiVariants ? <SearchScopeToggle spaceTitle={spaceTitle} /> : null}
|
||||
</div>
|
||||
<div className="overflow-y-auto border-tint-subtle bg-tint-subtle max-md:border-t md:col-start-2 md:row-start-2 md:border-l">
|
||||
<SearchAskAnswer query={normalizedQuery} />
|
||||
</div>
|
||||
</div>
|
||||
{!state.ask || !withAsk ? (
|
||||
<SearchResults
|
||||
ref={resultsRef}
|
||||
global={isMultiVariants && state.global}
|
||||
query={normalizedQuery}
|
||||
withAsk={withAsk}
|
||||
onSwitchToAsk={onSwitchToAsk}
|
||||
/>
|
||||
) : null}
|
||||
{normalizedQuery && state.ask && withAsk ? (
|
||||
<SearchAskAnswer query={normalizedQuery} />
|
||||
) : null}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import { tcls } from '@/lib/tailwind';
|
||||
import { Icon, type IconName } from '@gitbook/icons';
|
||||
import React from 'react';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { tString } from '@/intl/translate';
|
||||
import { Button, Link } from '../primitives';
|
||||
import { HighlightQuery } from './HighlightQuery';
|
||||
import type { ComputedPageResult } from './server-actions';
|
||||
|
||||
@@ -14,6 +16,7 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
},
|
||||
ref: React.Ref<HTMLAnchorElement>
|
||||
) {
|
||||
const language = useLanguage();
|
||||
const { query, item, active } = props;
|
||||
|
||||
const breadcrumbs =
|
||||
@@ -34,16 +37,19 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'p-4',
|
||||
'border-t',
|
||||
'border-tint-subtle',
|
||||
'first:border-none',
|
||||
'rounded-lg',
|
||||
'straight-corners:rounded-none',
|
||||
'text-base',
|
||||
'font-medium',
|
||||
'text-tint-strong',
|
||||
'hover:bg-tint-hover',
|
||||
'group',
|
||||
active
|
||||
? ['is-active', 'bg-primary', 'text-contrast-primary', 'hover:bg-primary-hover']
|
||||
: null
|
||||
active && [
|
||||
'is-active',
|
||||
'bg-primary',
|
||||
'text-primary-strong',
|
||||
'hover:bg-primary-hover',
|
||||
]
|
||||
)}
|
||||
insights={{
|
||||
type: 'search_open_result',
|
||||
@@ -56,8 +62,8 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
>
|
||||
<div className="size-4">
|
||||
<Icon
|
||||
icon="file-lines"
|
||||
className={tcls('size-4', active ? 'text-primary' : 'text-tint-subtle')}
|
||||
icon="file"
|
||||
className={tcls('size-4', active ? 'text-primary-subtle' : 'text-tint-subtle')}
|
||||
/>
|
||||
</div>
|
||||
<div className={tcls('flex', 'flex-col', 'w-full')}>
|
||||
@@ -65,7 +71,8 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
<div
|
||||
className={tcls(
|
||||
'text-xs',
|
||||
'opacity-6',
|
||||
active ? 'text-primary-subtle' : 'text-neutral-subtle',
|
||||
// 'opacity-6',
|
||||
'contrast-more:opacity-11',
|
||||
'font-normal',
|
||||
'uppercase',
|
||||
@@ -103,19 +110,15 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
) : null}
|
||||
<HighlightQuery query={query} text={item.title} />
|
||||
</div>
|
||||
<div
|
||||
className={tcls(
|
||||
'p-2',
|
||||
'rounded',
|
||||
'straight-corners:rounded-none',
|
||||
active ? ['bg-primary-solid', 'text-contrast-primary-solid'] : ['opacity-6']
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={active ? 'arrow-turn-down-left' : 'chevron-right'}
|
||||
className={tcls('size-4')}
|
||||
{active ? (
|
||||
<Button
|
||||
icon="arrow-turn-down-left"
|
||||
size="small"
|
||||
label={tString(language, 'view')}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Icon icon="chevron-right" className="size-4 text-tint-subtle/6" />
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Icon } from '@gitbook/icons';
|
||||
import React from 'react';
|
||||
|
||||
import { t, useLanguage } from '@/intl/client';
|
||||
import { t, tString, useLanguage } from '@/intl/client';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
import { Button, Link } from '../primitives';
|
||||
import { useSearchLink } from './useSearch';
|
||||
|
||||
export const SearchQuestionResultItem = React.forwardRef(function SearchQuestionResultItem(
|
||||
@@ -28,14 +28,16 @@ export const SearchQuestionResultItem = React.forwardRef(function SearchQuestion
|
||||
className={tcls(
|
||||
'flex',
|
||||
'px-4',
|
||||
recommended ? ['py-2', 'text-tint'] : 'py-4',
|
||||
'py-2',
|
||||
'text-tint',
|
||||
'rounded-lg',
|
||||
'straight-corners:rounded-none',
|
||||
'hover:bg-tint-hover',
|
||||
'first:mt-0',
|
||||
'last:pb-3',
|
||||
'gap-4',
|
||||
active && [
|
||||
'is-active',
|
||||
'bg-primary',
|
||||
'text-contrast-primary',
|
||||
'text-primary-strong',
|
||||
'hover:bg-primary-hover',
|
||||
]
|
||||
)}
|
||||
@@ -50,7 +52,6 @@ export const SearchQuestionResultItem = React.forwardRef(function SearchQuestion
|
||||
'size-4',
|
||||
'shrink-0',
|
||||
'mt-1.5',
|
||||
'mr-4',
|
||||
active ? ['text-primary'] : ['text-tint-subtle']
|
||||
)}
|
||||
/>
|
||||
@@ -66,19 +67,16 @@ export const SearchQuestionResultItem = React.forwardRef(function SearchQuestion
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={tcls(
|
||||
'p-2',
|
||||
'rounded',
|
||||
'self-center',
|
||||
'straight-corners:rounded-none',
|
||||
active ? ['bg-primary-solid', 'text-contrast-primary-solid'] : ['opacity-6']
|
||||
<div className="self-center">
|
||||
{active ? (
|
||||
<Button
|
||||
icon="arrow-turn-down-left"
|
||||
size="small"
|
||||
label={tString(language, 'search')}
|
||||
/>
|
||||
) : (
|
||||
<Icon icon="chevron-right" className="size-4 text-tint-subtle/6" />
|
||||
)}
|
||||
>
|
||||
<Icon
|
||||
icon={active ? 'arrow-turn-down-left' : 'chevron-right'}
|
||||
className={tcls('size-4')}
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -44,7 +44,6 @@ let cachedRecommendedQuestions: null | ResultType[] = null;
|
||||
*/
|
||||
export const SearchResults = React.forwardRef(function SearchResults(
|
||||
props: {
|
||||
children?: React.ReactNode;
|
||||
query: string;
|
||||
global: boolean;
|
||||
withAsk: boolean;
|
||||
@@ -52,7 +51,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
|
||||
},
|
||||
ref: React.Ref<SearchResultsRef>
|
||||
) {
|
||||
const { children, query, withAsk, global, onSwitchToAsk } = props;
|
||||
const { query, withAsk, global, onSwitchToAsk } = props;
|
||||
|
||||
const language = useLanguage();
|
||||
const trackEvent = useTrackEvent();
|
||||
@@ -150,12 +149,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
|
||||
};
|
||||
}, [query, global, withAsk, trackEvent]);
|
||||
|
||||
const results: ResultType[] = React.useMemo(() => {
|
||||
if (!withAsk) {
|
||||
return resultsState.results;
|
||||
}
|
||||
return withQuestionResult(resultsState.results, query);
|
||||
}, [resultsState.results, query, withAsk]);
|
||||
const results: ResultType[] = React.useMemo(() => resultsState.results, [resultsState.results]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!query) {
|
||||
@@ -216,92 +210,87 @@ export const SearchResults = React.forwardRef(function SearchResults(
|
||||
|
||||
if (resultsState.fetching) {
|
||||
return (
|
||||
<div className={tcls('flex', 'items-center', 'justify-center', 'py-8')}>
|
||||
<Loading className={tcls('w-6', 'text-primary')} />
|
||||
<div className={tcls('flex', 'items-center', 'justify-center', 'p-8')}>
|
||||
<Loading className={tcls('w-6', 'text-primary-subtle')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const noResults = (
|
||||
<div className={tcls('text', 'text-tint', 'p-8', 'text-center')}>
|
||||
<div className={tcls('text', 'text-tint', 'text-center', 'p-8')}>
|
||||
{t(language, 'search_no_results', query)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={tcls('overflow-auto')}>
|
||||
{children}
|
||||
{results.length === 0 ? (
|
||||
query ? (
|
||||
noResults
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
<div data-testid="search-results">
|
||||
{results.map((item, index) => {
|
||||
switch (item.type) {
|
||||
case 'page': {
|
||||
return (
|
||||
<SearchPageResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
query={query}
|
||||
item={item}
|
||||
active={index === cursor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'question': {
|
||||
return (
|
||||
<SearchQuestionResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
question={query}
|
||||
active={index === cursor}
|
||||
onClick={onSwitchToAsk}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'recommended-question': {
|
||||
return (
|
||||
<SearchQuestionResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
question={item.question}
|
||||
active={index === cursor}
|
||||
onClick={onSwitchToAsk}
|
||||
recommended
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'section': {
|
||||
return (
|
||||
<SearchSectionResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
query={query}
|
||||
item={item}
|
||||
active={index === cursor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
assertNever(item);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
{!results.some((result) => result.type !== 'question') && noResults}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
return results.length === 0 ? (
|
||||
query ? (
|
||||
noResults
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col gap-2 p-4" data-testid="search-results">
|
||||
{results.map((item, index) => {
|
||||
switch (item.type) {
|
||||
case 'page': {
|
||||
return (
|
||||
<SearchPageResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
query={query}
|
||||
item={item}
|
||||
active={index === cursor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'question': {
|
||||
return (
|
||||
<SearchQuestionResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
question={query}
|
||||
active={index === cursor}
|
||||
onClick={onSwitchToAsk}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'recommended-question': {
|
||||
return (
|
||||
<SearchQuestionResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
question={item.question}
|
||||
active={index === cursor}
|
||||
onClick={onSwitchToAsk}
|
||||
recommended
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'section': {
|
||||
return (
|
||||
<SearchSectionResultItem
|
||||
ref={(ref) => {
|
||||
refs.current[index] = ref;
|
||||
}}
|
||||
key={item.id}
|
||||
query={query}
|
||||
item={item}
|
||||
active={index === cursor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:
|
||||
assertNever(item);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
{!results.some((result) => result.type !== 'question') && noResults}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import React from 'react';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { Link } from '../primitives';
|
||||
import { tString, useLanguage } from '@/intl/client';
|
||||
import { Button, Link } from '../primitives';
|
||||
import { HighlightQuery } from './HighlightQuery';
|
||||
import type { ComputedSectionResult } from './server-actions';
|
||||
|
||||
@@ -16,13 +17,15 @@ export const SearchSectionResultItem = React.forwardRef(function SearchSectionRe
|
||||
ref: React.Ref<HTMLAnchorElement>
|
||||
) {
|
||||
const { query, item, active } = props;
|
||||
const language = useLanguage();
|
||||
|
||||
return (
|
||||
<Link
|
||||
ref={ref}
|
||||
href={item.href}
|
||||
className={tcls(
|
||||
'[&:has(+:not(&))]:mb-6',
|
||||
// '[&:has(+:not(&))]:mb-6',
|
||||
'-mt-2',
|
||||
'flex',
|
||||
'items-center',
|
||||
'pl-6',
|
||||
@@ -33,6 +36,8 @@ export const SearchSectionResultItem = React.forwardRef(function SearchSectionRe
|
||||
'font-normal',
|
||||
'py-2',
|
||||
'group',
|
||||
'rounded-lg',
|
||||
'straight-corners:rounded-none',
|
||||
active && [
|
||||
'is-active',
|
||||
'bg-primary',
|
||||
@@ -72,20 +77,15 @@ export const SearchSectionResultItem = React.forwardRef(function SearchSectionRe
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div
|
||||
className={tcls(
|
||||
'p-2',
|
||||
'rounded',
|
||||
'straight-corners:rounded-none',
|
||||
'bg-primary-solid',
|
||||
'text-contrast-primary-solid',
|
||||
'hidden',
|
||||
'sm:block',
|
||||
active ? ['opacity-11', 'block'] : ['opacity-0']
|
||||
)}
|
||||
>
|
||||
<Icon icon="arrow-turn-down-left" className={tcls('size-4')} />
|
||||
</div>
|
||||
{active ? (
|
||||
<Button
|
||||
icon="arrow-turn-down-left"
|
||||
size="small"
|
||||
label={tString(language, 'view')}
|
||||
/>
|
||||
) : (
|
||||
<Icon icon="chevron-right" className="size-4 text-tint-subtle/6" />
|
||||
)}
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ export const en = {
|
||||
switch_to_light_theme: 'Switch to light theme',
|
||||
switch_to_system_theme: 'Switch to system theme',
|
||||
search: 'Search',
|
||||
view: 'View',
|
||||
search_or_ask: 'Ask or search',
|
||||
search_input_placeholder: 'Search content',
|
||||
search_ask_input_placeholder: 'Search content or ask a question',
|
||||
|
||||
Reference in New Issue
Block a user