fix(v5): stop truncating mobile snippets (#2958)
* fix(v5): stop truncating mobile snippets Backport of #2907.\n\nOriginal commit:9ad6d169fe* fix(v5): allow mobile hit text to wrap Completes the v5 adaptation of #2907 by overriding later v5 child-level truncation rules.\n\nOriginal commit:9ad6d169fe* chore(v5): account for mobile wrapping CSS Updates the CSS size budget for the v5 adaptation of #2907.\n\nOriginal commit:9ad6d169fe--------- Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com>
This commit is contained in:
parent
9a1b3e4c6c
commit
4e44b558e1
12 changed files with 88 additions and 80 deletions
6
.changeset/long-snippets-wrap.md
Normal file
6
.changeset/long-snippets-wrap.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@docsearch/react": patch
|
||||
"@docsearch/css": patch
|
||||
---
|
||||
|
||||
Stop over-truncating mobile snippets and allow long search hits to wrap.
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"files": [
|
||||
{
|
||||
"path": "packages/docsearch-css/dist/style.css",
|
||||
"maxSize": "8.05 kB"
|
||||
"maxSize": "8.1 kB"
|
||||
},
|
||||
{
|
||||
"path": "packages/docsearch-react/dist/umd/index.js",
|
||||
|
|
|
|||
|
|
@ -1135,10 +1135,26 @@ assistive tech users */
|
|||
|
||||
.DocSearch-Hit-content-wrapper {
|
||||
display: flex;
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
white-space: normal;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-title,
|
||||
.DocSearch-Hit-path {
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.DocSearch-Hit-Container {
|
||||
height: auto;
|
||||
min-height: var(--docsearch-hit-height);
|
||||
padding-block: 8px;
|
||||
}
|
||||
|
||||
.DocSearch-Modal {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
|
|
|
|||
|
|
@ -142,14 +142,8 @@ export function DocSearchAskAiModal({
|
|||
'Ask another question...';
|
||||
}
|
||||
|
||||
const {
|
||||
containerRef,
|
||||
modalRef,
|
||||
formElementRef,
|
||||
dropdownRef,
|
||||
inputRef,
|
||||
snippetLength,
|
||||
} = useModalRefs();
|
||||
const { containerRef, modalRef, formElementRef, dropdownRef, inputRef } =
|
||||
useModalRefs();
|
||||
const { initialQuery, initialQueryFromSelection } =
|
||||
useInitialModalQuery(initialQueryFromProp);
|
||||
|
||||
|
|
@ -417,7 +411,6 @@ export function DocSearchAskAiModal({
|
|||
setStatus,
|
||||
searchClient,
|
||||
indexes,
|
||||
snippetLength,
|
||||
insights: Boolean(insights),
|
||||
appId,
|
||||
apiKey,
|
||||
|
|
@ -460,7 +453,6 @@ export function DocSearchAskAiModal({
|
|||
inputRef,
|
||||
initialScrollY,
|
||||
modalRef,
|
||||
snippetLength,
|
||||
theme,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -95,14 +95,8 @@ export function DocSearchModal({
|
|||
props.placeholder ||
|
||||
'Search docs';
|
||||
|
||||
const {
|
||||
containerRef,
|
||||
modalRef,
|
||||
formElementRef,
|
||||
dropdownRef,
|
||||
inputRef,
|
||||
snippetLength,
|
||||
} = useModalRefs();
|
||||
const { containerRef, modalRef, formElementRef, dropdownRef, inputRef } =
|
||||
useModalRefs();
|
||||
const { initialQuery, initialQueryFromSelection } =
|
||||
useInitialModalQuery(initialQueryFromProp);
|
||||
|
||||
|
|
@ -193,7 +187,6 @@ export function DocSearchModal({
|
|||
setStatus,
|
||||
searchClient,
|
||||
indexes,
|
||||
snippetLength,
|
||||
insights: Boolean(insights),
|
||||
appId,
|
||||
apiKey,
|
||||
|
|
@ -221,7 +214,6 @@ export function DocSearchModal({
|
|||
inputRef,
|
||||
initialScrollY,
|
||||
modalRef,
|
||||
snippetLength,
|
||||
theme,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export const MAX_QUERY_SIZE = 512;
|
||||
export const SNIPPET_LENGTH = 15;
|
||||
export const SUGGESTED_QUETIONS_INDEX_NAME =
|
||||
'algolia_ask_ai_suggested_questions';
|
||||
|
|
|
|||
|
|
@ -117,7 +117,6 @@ describe('modal hooks', () => {
|
|||
expect(firstResult.formElementRef.current).toBeNull();
|
||||
expect(firstResult.dropdownRef.current).toBeNull();
|
||||
expect(firstResult.inputRef.current).toBeNull();
|
||||
expect(firstResult.snippetLength.current).toBe(15);
|
||||
|
||||
rerender(<TestComponent onResult={onResult} />);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ import { manageLocalStorageQuota } from '../utils/storage';
|
|||
export function useDocSearchModalEffects({
|
||||
initialScrollY,
|
||||
modalRef,
|
||||
snippetLength,
|
||||
theme,
|
||||
}: {
|
||||
initialScrollY: number;
|
||||
modalRef: React.RefObject<HTMLDivElement | null>;
|
||||
snippetLength: React.MutableRefObject<number>;
|
||||
theme?: DocSearchTheme;
|
||||
}): void {
|
||||
useTheme({ theme });
|
||||
|
|
@ -39,12 +37,6 @@ export function useDocSearchModalEffects({
|
|||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (window.matchMedia('(max-width: 768px)').matches) {
|
||||
snippetLength.current = 5;
|
||||
}
|
||||
}, [snippetLength]);
|
||||
|
||||
React.useEffect(() => {
|
||||
function setFullViewportHeight(): void {
|
||||
if (modalRef.current) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ export function useModalEnvironment({
|
|||
inputRef,
|
||||
initialScrollY,
|
||||
modalRef,
|
||||
snippetLength,
|
||||
theme,
|
||||
}: {
|
||||
getEnvironmentProps: AutocompleteApi<
|
||||
|
|
@ -30,7 +29,6 @@ export function useModalEnvironment({
|
|||
inputRef: React.RefObject<HTMLInputElement | null>;
|
||||
initialScrollY: number;
|
||||
modalRef: React.RefObject<HTMLDivElement | null>;
|
||||
snippetLength: React.MutableRefObject<number>;
|
||||
theme?: DocSearchTheme;
|
||||
}): void {
|
||||
useTouchEvents({
|
||||
|
|
@ -40,5 +38,5 @@ export function useModalEnvironment({
|
|||
inputElement: inputRef.current,
|
||||
});
|
||||
useTrapFocus({ container: containerRef.current });
|
||||
useDocSearchModalEffects({ initialScrollY, modalRef, snippetLength, theme });
|
||||
useDocSearchModalEffects({ initialScrollY, modalRef, theme });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,12 @@ export function useModalRefs(): {
|
|||
formElementRef: React.RefObject<HTMLDivElement | null>;
|
||||
dropdownRef: React.RefObject<HTMLDivElement | null>;
|
||||
inputRef: React.RefObject<HTMLInputElement | null>;
|
||||
snippetLength: React.MutableRefObject<number>;
|
||||
} {
|
||||
const containerRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const modalRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const formElementRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const dropdownRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
||||
const snippetLength = React.useRef<number>(15);
|
||||
|
||||
return {
|
||||
containerRef,
|
||||
|
|
@ -21,6 +19,5 @@ export function useModalRefs(): {
|
|||
formElementRef,
|
||||
dropdownRef,
|
||||
inputRef,
|
||||
snippetLength,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,55 +63,71 @@ function createHit({
|
|||
|
||||
describe('buildQuerySources', () => {
|
||||
it('creates a source per lvl0 group and scopes parents to that group', async () => {
|
||||
const search = vi.fn().mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
index: 'docs',
|
||||
nbHits: 4,
|
||||
hits: [
|
||||
createHit({
|
||||
objectID: 'guide-install',
|
||||
lvl0: 'Guides',
|
||||
lvl1: 'Installation',
|
||||
type: 'lvl1',
|
||||
}),
|
||||
createHit({
|
||||
objectID: 'guide-install-content',
|
||||
lvl0: 'Guides',
|
||||
lvl1: 'Installation',
|
||||
type: 'content',
|
||||
}),
|
||||
createHit({
|
||||
objectID: 'reference-install',
|
||||
lvl0: 'Reference',
|
||||
lvl1: 'Installation',
|
||||
type: 'lvl1',
|
||||
}),
|
||||
createHit({
|
||||
objectID: 'reference-install-content',
|
||||
lvl0: 'Reference',
|
||||
lvl1: 'Installation',
|
||||
type: 'content',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const sources = await buildQuerySources({
|
||||
query: 'install',
|
||||
state: { context: { searchSuggestions: [] } },
|
||||
setContext: vi.fn(),
|
||||
setStatus: vi.fn(),
|
||||
searchClient: {
|
||||
search: vi.fn().mockResolvedValue({
|
||||
results: [
|
||||
{
|
||||
index: 'docs',
|
||||
nbHits: 4,
|
||||
hits: [
|
||||
createHit({
|
||||
objectID: 'guide-install',
|
||||
lvl0: 'Guides',
|
||||
lvl1: 'Installation',
|
||||
type: 'lvl1',
|
||||
}),
|
||||
createHit({
|
||||
objectID: 'guide-install-content',
|
||||
lvl0: 'Guides',
|
||||
lvl1: 'Installation',
|
||||
type: 'content',
|
||||
}),
|
||||
createHit({
|
||||
objectID: 'reference-install',
|
||||
lvl0: 'Reference',
|
||||
lvl1: 'Installation',
|
||||
type: 'lvl1',
|
||||
}),
|
||||
createHit({
|
||||
objectID: 'reference-install-content',
|
||||
lvl0: 'Reference',
|
||||
lvl1: 'Installation',
|
||||
type: 'content',
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
search,
|
||||
} as any,
|
||||
indexes: [{ name: 'docs' }],
|
||||
snippetLength: { current: 15 },
|
||||
insights: false,
|
||||
saveRecentSearch: vi.fn(),
|
||||
onClose: vi.fn(),
|
||||
facetSelections: { current: {} },
|
||||
});
|
||||
|
||||
expect(search).toHaveBeenCalledWith({
|
||||
requests: [
|
||||
expect.objectContaining({
|
||||
attributesToSnippet: [
|
||||
'hierarchy.lvl1:15',
|
||||
'hierarchy.lvl2:15',
|
||||
'hierarchy.lvl3:15',
|
||||
'hierarchy.lvl4:15',
|
||||
'hierarchy.lvl5:15',
|
||||
'hierarchy.lvl6:15',
|
||||
'content:15',
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
expect(sources.map((source) => source.sourceId)).toEqual([
|
||||
'hits_docs_0',
|
||||
'hits_docs_1',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type {
|
|||
} from 'algoliasearch/lite';
|
||||
import type React from 'react';
|
||||
|
||||
import { SNIPPET_LENGTH } from '../constants';
|
||||
import type { DocSearchIndex, DocSearchProps } from '../DocSearch';
|
||||
import type {
|
||||
DocSearchHit,
|
||||
|
|
@ -135,7 +136,6 @@ export async function buildQuerySources({
|
|||
setStatus,
|
||||
searchClient,
|
||||
indexes,
|
||||
snippetLength,
|
||||
insights,
|
||||
appId,
|
||||
apiKey,
|
||||
|
|
@ -153,7 +153,6 @@ export async function buildQuerySources({
|
|||
setStatus: (status: DocSearchState<InternalDocSearchHit>['status']) => void;
|
||||
searchClient: ReturnType<typeof useSearchClient>;
|
||||
indexes: DocSearchIndex[];
|
||||
snippetLength: React.MutableRefObject<number>;
|
||||
insights: boolean;
|
||||
appId?: string;
|
||||
apiKey?: string;
|
||||
|
|
@ -191,13 +190,13 @@ export async function buildQuerySources({
|
|||
'url',
|
||||
],
|
||||
attributesToSnippet: searchParams?.attributesToSnippet ?? [
|
||||
`hierarchy.lvl1:${snippetLength.current}`,
|
||||
`hierarchy.lvl2:${snippetLength.current}`,
|
||||
`hierarchy.lvl3:${snippetLength.current}`,
|
||||
`hierarchy.lvl4:${snippetLength.current}`,
|
||||
`hierarchy.lvl5:${snippetLength.current}`,
|
||||
`hierarchy.lvl6:${snippetLength.current}`,
|
||||
`content:${snippetLength.current}`,
|
||||
`hierarchy.lvl1:${SNIPPET_LENGTH}`,
|
||||
`hierarchy.lvl2:${SNIPPET_LENGTH}`,
|
||||
`hierarchy.lvl3:${SNIPPET_LENGTH}`,
|
||||
`hierarchy.lvl4:${SNIPPET_LENGTH}`,
|
||||
`hierarchy.lvl5:${SNIPPET_LENGTH}`,
|
||||
`hierarchy.lvl6:${SNIPPET_LENGTH}`,
|
||||
`content:${SNIPPET_LENGTH}`,
|
||||
],
|
||||
snippetEllipsisText: searchParams?.snippetEllipsisText ?? '…',
|
||||
highlightPreTag: searchParams?.highlightPreTag ?? '<mark>',
|
||||
|
|
|
|||
Loading…
Reference in a new issue