feat(v5): Add new footerAction prop (#2952)
* feat(v5): Add new footerAction prop * Resolve PR comments
This commit is contained in:
parent
4e44b558e1
commit
b28bc84904
20 changed files with 428 additions and 99 deletions
14
.changeset/sixty-lights-try.md
Normal file
14
.changeset/sixty-lights-try.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
"@docsearch/react": patch
|
||||||
|
"@docsearch/css": patch
|
||||||
|
"@docsearch/js": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
feat(v5): add customizable footer action
|
||||||
|
|
||||||
|
- New `footerAction` prop renders a custom action in the modal footer,
|
||||||
|
before the Algolia logo, inside `.DocSearch-Footer-Action`
|
||||||
|
- `@docsearch/js` supports `footerAction` via template patterns (html helper,
|
||||||
|
JSX, or function-based)
|
||||||
|
- Fixes typing differences between `@docsearch/react` and `@docsearch/js`
|
||||||
|
- Restyle the footer with a `.DocSearch-Footer-Actions` wrapper
|
||||||
22
bun.lock
22
bun.lock
|
|
@ -31,7 +31,7 @@
|
||||||
},
|
},
|
||||||
"adapters/docusaurus-theme-search-algolia": {
|
"adapters/docusaurus-theme-search-algolia": {
|
||||||
"name": "@docsearch/docusaurus-adapter",
|
"name": "@docsearch/docusaurus-adapter",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@algolia/autocomplete-core": "1.19.2",
|
"@algolia/autocomplete-core": "1.19.2",
|
||||||
"@docsearch/core": "workspace:*",
|
"@docsearch/core": "workspace:*",
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
},
|
},
|
||||||
"packages/docsearch-core": {
|
"packages/docsearch-core": {
|
||||||
"name": "@docsearch/core",
|
"name": "@docsearch/core",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "6.6.3",
|
"@testing-library/jest-dom": "6.6.3",
|
||||||
"@testing-library/react": "16.2.0",
|
"@testing-library/react": "16.2.0",
|
||||||
|
|
@ -137,7 +137,7 @@
|
||||||
},
|
},
|
||||||
"packages/docsearch-css": {
|
"packages/docsearch-css": {
|
||||||
"name": "@docsearch/css",
|
"name": "@docsearch/css",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browserslist": "4.28.2",
|
"browserslist": "4.28.2",
|
||||||
"lightningcss": "1.32.0",
|
"lightningcss": "1.32.0",
|
||||||
|
|
@ -146,7 +146,7 @@
|
||||||
},
|
},
|
||||||
"packages/docsearch-js": {
|
"packages/docsearch-js": {
|
||||||
"name": "@docsearch/js",
|
"name": "@docsearch/js",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/core": "4.6.0",
|
"@docsearch/core": "4.6.0",
|
||||||
"@docsearch/react": "4.6.0",
|
"@docsearch/react": "4.6.0",
|
||||||
|
|
@ -160,7 +160,7 @@
|
||||||
},
|
},
|
||||||
"packages/docsearch-modal": {
|
"packages/docsearch-modal": {
|
||||||
"name": "@docsearch/modal",
|
"name": "@docsearch/modal",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/core": "4.6.0",
|
"@docsearch/core": "4.6.0",
|
||||||
"@docsearch/react": "4.6.0",
|
"@docsearch/react": "4.6.0",
|
||||||
|
|
@ -185,13 +185,13 @@
|
||||||
},
|
},
|
||||||
"packages/docsearch-react": {
|
"packages/docsearch-react": {
|
||||||
"name": "@docsearch/react",
|
"name": "@docsearch/react",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/react": "^2.0.30",
|
"@ai-sdk/react": "^2.0.30",
|
||||||
"@algolia/autocomplete-core": "1.19.2",
|
"@algolia/autocomplete-core": "1.19.2",
|
||||||
"@base-ui/react": "^1.5.0",
|
"@base-ui/react": "^1.5.0",
|
||||||
"@docsearch/core": "5.0.0-beta.1",
|
"@docsearch/core": "5.0.0-beta.2",
|
||||||
"@docsearch/css": "5.0.0-beta.1",
|
"@docsearch/css": "5.0.0-beta.2",
|
||||||
"ai": "^5.0.30",
|
"ai": "^5.0.30",
|
||||||
"algoliasearch": "^5.28.0",
|
"algoliasearch": "^5.28.0",
|
||||||
"marked": "^16.3.0",
|
"marked": "^16.3.0",
|
||||||
|
|
@ -199,7 +199,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@algolia/autocomplete-core": "1.19.2",
|
"@algolia/autocomplete-core": "1.19.2",
|
||||||
"@docsearch/core": "5.0.0-beta.1",
|
"@docsearch/core": "5.0.0-beta.2",
|
||||||
"@testing-library/jest-dom": "6.6.3",
|
"@testing-library/jest-dom": "6.6.3",
|
||||||
"@testing-library/react": "16.2.0",
|
"@testing-library/react": "16.2.0",
|
||||||
"preact": "11.0.0-beta.0",
|
"preact": "11.0.0-beta.0",
|
||||||
|
|
@ -222,7 +222,7 @@
|
||||||
},
|
},
|
||||||
"packages/docsearch-sidepanel": {
|
"packages/docsearch-sidepanel": {
|
||||||
"name": "@docsearch/sidepanel",
|
"name": "@docsearch/sidepanel",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/core": "4.6.0",
|
"@docsearch/core": "4.6.0",
|
||||||
"@docsearch/css": "4.6.0",
|
"@docsearch/css": "4.6.0",
|
||||||
|
|
@ -248,7 +248,7 @@
|
||||||
},
|
},
|
||||||
"packages/docsearch-sidepanel-js": {
|
"packages/docsearch-sidepanel-js": {
|
||||||
"name": "@docsearch/sidepanel-js",
|
"name": "@docsearch/sidepanel-js",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/core": "4.6.0",
|
"@docsearch/core": "4.6.0",
|
||||||
"@docsearch/react": "4.6.0",
|
"@docsearch/react": "4.6.0",
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docsearch/css": "5.0.0-beta.2",
|
"@docsearch/css": "workspace:*",
|
||||||
"@docsearch/js": "5.0.0-beta.2",
|
"@docsearch/js": "workspace:*",
|
||||||
"@docsearch/sidepanel-js": "5.0.0-beta.2"
|
"@docsearch/sidepanel-js": "workspace:*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vite": "^6.0.7"
|
"vite": "^6.0.7"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import docsearch, {
|
import docsearch, { type DocSearchInstance } from '@docsearch/js';
|
||||||
type DocSearchInstance,
|
|
||||||
type TemplateHelpers,
|
|
||||||
} from '@docsearch/js';
|
|
||||||
import sidepanel, { type SidepanelInstance } from '@docsearch/sidepanel-js';
|
import sidepanel, { type SidepanelInstance } from '@docsearch/sidepanel-js';
|
||||||
|
|
||||||
import './app.css';
|
import './app.css';
|
||||||
|
|
@ -91,10 +88,7 @@ docsearchInstance = docsearch({
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('[demo-js] docsearch onClose()');
|
console.log('[demo-js] docsearch onClose()');
|
||||||
},
|
},
|
||||||
resultsFooterComponent: ({ state }, helpers?: TemplateHelpers) => {
|
resultsFooterComponent: ({ state }, { html }) => {
|
||||||
const { html } = helpers || {};
|
|
||||||
if (!html) return null;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="DocSearch-HitsFooter">
|
<div class="DocSearch-HitsFooter">
|
||||||
<a href="https://docsearch.algolia.com/apply" target="_blank">
|
<a href="https://docsearch.algolia.com/apply" target="_blank">
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ const customTools: ToolCalls = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FooterAction = (): JSX.Element => {
|
||||||
|
return <span>Bonjour</span>;
|
||||||
|
};
|
||||||
|
|
||||||
export default function BasicAskAI({
|
export default function BasicAskAI({
|
||||||
theme,
|
theme,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -50,6 +54,7 @@ export default function BasicAskAI({
|
||||||
translations={{ button: { buttonText: 'Search with Ask AI' } }}
|
translations={{ button: { buttonText: 'Search with Ask AI' } }}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
resultBadgeKey="type"
|
resultBadgeKey="type"
|
||||||
|
footerAction={<FooterAction />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1034,7 +1034,6 @@
|
||||||
border-block-start: 1px solid var(--docsearch-subtle-color);
|
border-block-start: 1px solid var(--docsearch-subtle-color);
|
||||||
border-radius: 0 0 var(--docsearch-modal-radius) var(--docsearch-modal-radius);
|
border-radius: 0 0 var(--docsearch-modal-radius) var(--docsearch-modal-radius);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row-reverse;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: var(--docsearch-footer-height);
|
height: var(--docsearch-footer-height);
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -1091,6 +1090,23 @@
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.DocSearch-Footer-Actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@media screen and (width > 768px) {
|
||||||
|
justify-content: unset;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:has(.DocSearch-Footer-Action)) .DocSearch-Logo {
|
||||||
|
margin-inline-start: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide element accessibly, so that it is still accessible to
|
/* Hide element accessibly, so that it is still accessible to
|
||||||
assistive tech users */
|
assistive tech users */
|
||||||
.DocSearch-VisuallyHiddenForAccessibility {
|
.DocSearch-VisuallyHiddenForAccessibility {
|
||||||
|
|
|
||||||
103
packages/docsearch-js/src/__tests__/createDocSearch.test.tsx
Normal file
103
packages/docsearch-js/src/__tests__/createDocSearch.test.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { createElement, type JSX } from 'preact';
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { createDocSearch, type DocSearchInstance } from '../createDocSearch';
|
||||||
|
import type { FooterActionFn as KeywordFooterActionFn } from '../docsearch';
|
||||||
|
import type { FooterActionFn as AskAiFooterActionFn } from '../index';
|
||||||
|
|
||||||
|
interface TestComponentProps {
|
||||||
|
footerAction?: JSX.Element | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestComponent({ footerAction }: TestComponentProps): JSX.Element {
|
||||||
|
return createElement('div', null, footerAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
const askAiFooterAction: AskAiFooterActionFn = (_props, { html }) =>
|
||||||
|
html`<button type="button">Ask AI footer action</button>`;
|
||||||
|
|
||||||
|
const keywordFooterAction: KeywordFooterActionFn = (_props, { html }) =>
|
||||||
|
html`<button type="button">Keyword footer action</button>`;
|
||||||
|
|
||||||
|
describe('createDocSearch', () => {
|
||||||
|
let instance: DocSearchInstance | undefined;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
instance?.destroy();
|
||||||
|
instance = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adapts footerAction templates for the rendered component', () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
const footerAction = (): JSX.Element =>
|
||||||
|
createElement('button', { type: 'button' }, 'Footer action');
|
||||||
|
const docsearch = createDocSearch<TestComponentProps>(
|
||||||
|
TestComponent,
|
||||||
|
'test'
|
||||||
|
);
|
||||||
|
|
||||||
|
instance = docsearch({ container, footerAction });
|
||||||
|
|
||||||
|
expect(container.textContent).toContain('Footer action');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('provides the html helper to footerAction templates', () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
const docsearch = createDocSearch<TestComponentProps>(
|
||||||
|
TestComponent,
|
||||||
|
'test'
|
||||||
|
);
|
||||||
|
|
||||||
|
instance = docsearch({ container, footerAction: askAiFooterAction });
|
||||||
|
|
||||||
|
expect(container.textContent).toContain('Ask AI footer action');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts footerAction templates from the keyword-only entry point', () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
const docsearch = createDocSearch<TestComponentProps>(
|
||||||
|
TestComponent,
|
||||||
|
'test'
|
||||||
|
);
|
||||||
|
|
||||||
|
instance = docsearch({ container, footerAction: keywordFooterAction });
|
||||||
|
|
||||||
|
expect(container.textContent).toContain('Keyword footer action');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders string and component footerAction template returns', () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
const docsearch = createDocSearch<TestComponentProps>(
|
||||||
|
TestComponent,
|
||||||
|
'test'
|
||||||
|
);
|
||||||
|
|
||||||
|
instance = docsearch({
|
||||||
|
container,
|
||||||
|
footerAction: () => () =>
|
||||||
|
createElement('button', { type: 'button' }, 'Component footer action'),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.textContent).toContain('Component footer action');
|
||||||
|
|
||||||
|
instance.destroy();
|
||||||
|
instance = docsearch({
|
||||||
|
container,
|
||||||
|
footerAction: () => 'Text footer action',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.textContent).toContain('Text footer action');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports footerAction templates that return null', () => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
const docsearch = createDocSearch<TestComponentProps>(
|
||||||
|
TestComponent,
|
||||||
|
'test'
|
||||||
|
);
|
||||||
|
|
||||||
|
instance = docsearch({ container, footerAction: () => null });
|
||||||
|
|
||||||
|
expect(container.textContent).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,8 @@
|
||||||
import type { DocSearchRef, InitialAskAiMessage } from '@docsearch/core';
|
import type { DocSearchRef, InitialAskAiMessage } from '@docsearch/core';
|
||||||
|
import type {
|
||||||
|
ResultsFooterComponentProps,
|
||||||
|
HitComponentProps,
|
||||||
|
} from '@docsearch/react';
|
||||||
import htm from 'htm';
|
import htm from 'htm';
|
||||||
import type { ComponentType, JSX, Attributes } from 'preact';
|
import type { ComponentType, JSX, Attributes } from 'preact';
|
||||||
import {
|
import {
|
||||||
|
|
@ -25,10 +29,64 @@ export interface DocSearchCallbacks {
|
||||||
interceptAskAiEvent?: (initialMessage: InitialAskAiMessage) => boolean | void;
|
interceptAskAiEvent?: (initialMessage: InitialAskAiMessage) => boolean | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TemplateHelpers = { html: typeof html };
|
||||||
|
|
||||||
|
// Defines the public facing interface for each "template" function
|
||||||
|
type TemplateFnReturnType = JSX.Element | string | (() => JSX.Element) | null;
|
||||||
|
|
||||||
|
export type HitComponentFn = (
|
||||||
|
props: HitComponentProps,
|
||||||
|
helpers: TemplateHelpers
|
||||||
|
) => TemplateFnReturnType;
|
||||||
|
|
||||||
|
export type ResultsFooterComponentFn = (
|
||||||
|
props: ResultsFooterComponentProps,
|
||||||
|
helpers: TemplateHelpers
|
||||||
|
) => TemplateFnReturnType;
|
||||||
|
|
||||||
|
export type FooterActionFn = (
|
||||||
|
props: never,
|
||||||
|
helpers: TemplateHelpers
|
||||||
|
) => TemplateFnReturnType;
|
||||||
|
|
||||||
export type DocSearchProps<TProps> = DocSearchCallbacks &
|
export type DocSearchProps<TProps> = DocSearchCallbacks &
|
||||||
Omit<TProps, 'onSidepanelClose' | 'onSidepanelOpen'> & {
|
Omit<
|
||||||
|
TProps,
|
||||||
|
| 'onSidepanelClose'
|
||||||
|
| 'onSidepanelOpen'
|
||||||
|
| 'hitComponent'
|
||||||
|
| 'resultsFooterComponent'
|
||||||
|
| 'footerAction'
|
||||||
|
> & {
|
||||||
container: HTMLElement | string;
|
container: HTMLElement | string;
|
||||||
environment?: typeof window;
|
environment?: typeof window;
|
||||||
|
/**
|
||||||
|
* Custom component to render an individual hit. Supports template patterns:
|
||||||
|
*
|
||||||
|
* - HTML strings with html helper: (props, { html }) => html`<div>...</div>`
|
||||||
|
* - JSX templates: (props) => <div>...</div>
|
||||||
|
* - Function-based templates: (props) => string | JSX.Element | Function.
|
||||||
|
*/
|
||||||
|
hitComponent?: HitComponentFn;
|
||||||
|
/**
|
||||||
|
* Custom component rendered at the bottom of the results panel. Supports
|
||||||
|
* template patterns:
|
||||||
|
*
|
||||||
|
* - HTML strings with html helper: (props, { html }) => html`<div>...</div>`
|
||||||
|
* - JSX templates: (props) => <div>...</div>
|
||||||
|
* - Function-based templates: (props) => string | JSX.Element | Function.
|
||||||
|
*/
|
||||||
|
resultsFooterComponent?: ResultsFooterComponentFn;
|
||||||
|
/**
|
||||||
|
* A custom action that can be rendered in the Modal's footer before the
|
||||||
|
* Algolia logo. The component will be rendered as a child of `<div
|
||||||
|
* className="DocSearch-Footer-Action" />`. Supports template patterns:
|
||||||
|
*
|
||||||
|
* - HTML strings with html helper: (props, { html }) => html`<div>...</div>`
|
||||||
|
* - JSX templates: (props) => <div>...</div>
|
||||||
|
* - Function-based templates: (props) => string | JSX.Element | Function.
|
||||||
|
*/
|
||||||
|
footerAction?: FooterActionFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getHTMLElement(
|
function getHTMLElement(
|
||||||
|
|
@ -51,14 +109,12 @@ const html = htm.bind(createElement) as unknown as (
|
||||||
...values: unknown[]
|
...values: unknown[]
|
||||||
) => JSX.Element;
|
) => JSX.Element;
|
||||||
|
|
||||||
export type TemplateHelpers = Record<string, unknown> & { html: typeof html };
|
|
||||||
|
|
||||||
function createTemplateFunction<
|
function createTemplateFunction<
|
||||||
P extends Record<string, unknown>,
|
P = Record<string, unknown>,
|
||||||
R = JSX.Element | string | (() => JSX.Element),
|
R = TemplateFnReturnType,
|
||||||
>(
|
>(
|
||||||
original: ((props: P, helpers?: TemplateHelpers) => R) | undefined
|
original: ((props: P, helpers: TemplateHelpers) => R) | undefined
|
||||||
): ((props: P) => JSX.Element) | undefined {
|
): ((props: P) => JSX.Element | null) | undefined {
|
||||||
if (!original) return undefined;
|
if (!original) return undefined;
|
||||||
|
|
||||||
return (props: P) => {
|
return (props: P) => {
|
||||||
|
|
@ -73,15 +129,10 @@ function createTemplateFunction<
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentProps {
|
interface ComponentProps {
|
||||||
hitComponent?: (
|
hitComponent?: HitComponentFn;
|
||||||
props: Record<string, unknown>,
|
resultsFooterComponent?: ResultsFooterComponentFn;
|
||||||
helpers?: TemplateHelpers
|
|
||||||
) => JSX.Element;
|
|
||||||
resultsFooterComponent?: (
|
|
||||||
props: Record<string, unknown>,
|
|
||||||
helpers?: TemplateHelpers
|
|
||||||
) => JSX.Element | null;
|
|
||||||
transformSearchClient?: (searchClient: unknown) => unknown;
|
transformSearchClient?: (searchClient: unknown) => unknown;
|
||||||
|
footerAction?: FooterActionFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDocSearch<TComponentProps, TInputProps = TComponentProps>(
|
export function createDocSearch<TComponentProps, TInputProps = TComponentProps>(
|
||||||
|
|
@ -96,6 +147,7 @@ export function createDocSearch<TComponentProps, TInputProps = TComponentProps>(
|
||||||
transformSearchClient,
|
transformSearchClient,
|
||||||
hitComponent,
|
hitComponent,
|
||||||
resultsFooterComponent,
|
resultsFooterComponent,
|
||||||
|
footerAction,
|
||||||
...rest
|
...rest
|
||||||
} = input;
|
} = input;
|
||||||
const containerElement = getHTMLElement(
|
const containerElement = getHTMLElement(
|
||||||
|
|
@ -105,11 +157,19 @@ export function createDocSearch<TComponentProps, TInputProps = TComponentProps>(
|
||||||
const ref = createRef<DocSearchRef>();
|
const ref = createRef<DocSearchRef>();
|
||||||
let isReady = false;
|
let isReady = false;
|
||||||
|
|
||||||
|
const FooterAction = createTemplateFunction(footerAction);
|
||||||
|
|
||||||
const props: TComponentProps = {
|
const props: TComponentProps = {
|
||||||
...rest,
|
...rest,
|
||||||
ref,
|
ref,
|
||||||
hitComponent: createTemplateFunction(hitComponent),
|
hitComponent: createTemplateFunction<HitComponentProps>(hitComponent),
|
||||||
resultsFooterComponent: createTemplateFunction(resultsFooterComponent),
|
resultsFooterComponent:
|
||||||
|
createTemplateFunction<ResultsFooterComponentProps>(
|
||||||
|
resultsFooterComponent
|
||||||
|
),
|
||||||
|
footerAction: FooterAction
|
||||||
|
? createElement(FooterAction, null)
|
||||||
|
: undefined,
|
||||||
transformSearchClient: (searchClient: unknown): unknown => {
|
transformSearchClient: (searchClient: unknown): unknown => {
|
||||||
if (
|
if (
|
||||||
typeof searchClient === 'object' &&
|
typeof searchClient === 'object' &&
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
export type {
|
||||||
|
HitComponentFn,
|
||||||
|
ResultsFooterComponentFn,
|
||||||
|
FooterActionFn,
|
||||||
|
TemplateHelpers,
|
||||||
|
} from './createDocSearch';
|
||||||
export { docsearch as default } from './docsearchComponent';
|
export { docsearch as default } from './docsearchComponent';
|
||||||
export type {
|
export type {
|
||||||
DocSearchCallbacks,
|
DocSearchCallbacks,
|
||||||
DocSearchInstance,
|
DocSearchInstance,
|
||||||
DocSearchProps,
|
DocSearchProps,
|
||||||
TemplateHelpers,
|
|
||||||
} from './docsearchComponent';
|
} from './docsearchComponent';
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@ import {
|
||||||
type DocSearchProps as CreateDocSearchProps,
|
type DocSearchProps as CreateDocSearchProps,
|
||||||
} from './createDocSearch';
|
} from './createDocSearch';
|
||||||
|
|
||||||
export type {
|
export type { DocSearchCallbacks, DocSearchInstance } from './createDocSearch';
|
||||||
DocSearchCallbacks,
|
|
||||||
DocSearchInstance,
|
|
||||||
TemplateHelpers,
|
|
||||||
} from './createDocSearch';
|
|
||||||
export type DocSearchAIProps = CreateDocSearchProps<DocSearchComponentProps>;
|
export type DocSearchAIProps = CreateDocSearchProps<DocSearchComponentProps>;
|
||||||
|
|
||||||
export const docsearchAi: (allProps: DocSearchAIProps) => DocSearchInstance =
|
export const docsearchAi: (allProps: DocSearchAIProps) => DocSearchInstance =
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,7 @@ import {
|
||||||
type DocSearchProps as CreateDocSearchProps,
|
type DocSearchProps as CreateDocSearchProps,
|
||||||
} from './createDocSearch';
|
} from './createDocSearch';
|
||||||
|
|
||||||
export type {
|
export type { DocSearchCallbacks, DocSearchInstance } from './createDocSearch';
|
||||||
DocSearchCallbacks,
|
|
||||||
DocSearchInstance,
|
|
||||||
TemplateHelpers,
|
|
||||||
} from './createDocSearch';
|
|
||||||
export type DocSearchProps = CreateDocSearchProps<DocSearchComponentProps>;
|
export type DocSearchProps = CreateDocSearchProps<DocSearchComponentProps>;
|
||||||
|
|
||||||
export const docsearch: (allProps: DocSearchProps) => DocSearchInstance =
|
export const docsearch: (allProps: DocSearchProps) => DocSearchInstance =
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
|
export type {
|
||||||
|
HitComponentFn,
|
||||||
|
ResultsFooterComponentFn,
|
||||||
|
FooterActionFn,
|
||||||
|
TemplateHelpers,
|
||||||
|
} from './createDocSearch';
|
||||||
export { docsearchAi as default } from './docsearchAi';
|
export { docsearchAi as default } from './docsearchAi';
|
||||||
export type {
|
export type {
|
||||||
DocSearchAIProps as DocSearchProps,
|
DocSearchAIProps as DocSearchProps,
|
||||||
DocSearchInstance,
|
DocSearchInstance,
|
||||||
DocSearchCallbacks,
|
DocSearchCallbacks,
|
||||||
TemplateHelpers,
|
|
||||||
} from './docsearchAi';
|
} from './docsearchAi';
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,15 @@ export interface DocSearchFacet {
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HitComponentProps {
|
||||||
|
hit: InternalDocSearchHit | StoredDocSearchHit;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResultsFooterComponentProps {
|
||||||
|
state: AutocompleteState<InternalDocSearchHit>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DocSearchProps {
|
export interface DocSearchProps {
|
||||||
/** Algolia application id used by the search client. */
|
/** Algolia application id used by the search client. */
|
||||||
appId: string;
|
appId: string;
|
||||||
|
|
@ -69,38 +78,18 @@ export interface DocSearchProps {
|
||||||
maxResultsPerGroup?: number;
|
maxResultsPerGroup?: number;
|
||||||
/** Hook to post-process hits before rendering. */
|
/** Hook to post-process hits before rendering. */
|
||||||
transformItems?: (items: DocSearchHit[]) => DocSearchHit[];
|
transformItems?: (items: DocSearchHit[]) => DocSearchHit[];
|
||||||
/**
|
/** Custom component to render an individual hit. */
|
||||||
* Custom component to render an individual hit. Supports template patterns:
|
hitComponent?: (props: HitComponentProps) => JSX.Element;
|
||||||
*
|
/** Custom component rendered at the bottom of the results panel. */
|
||||||
* - HTML strings with html helper: (props, { html }) => html`<div>...</div>`
|
|
||||||
* - JSX templates: (props) => <div>...</div>
|
|
||||||
* - Function-based templates: (props) => string | JSX.Element | Function.
|
|
||||||
*/
|
|
||||||
hitComponent?: (
|
|
||||||
props: {
|
|
||||||
hit: InternalDocSearchHit | StoredDocSearchHit;
|
|
||||||
children: React.ReactNode;
|
|
||||||
},
|
|
||||||
helpers?: {
|
|
||||||
html: (template: TemplateStringsArray, ...values: any[]) => any;
|
|
||||||
}
|
|
||||||
) => JSX.Element;
|
|
||||||
/**
|
|
||||||
* Custom component rendered at the bottom of the results panel. Supports
|
|
||||||
* template patterns:
|
|
||||||
*
|
|
||||||
* - HTML strings with html helper: (props, { html }) => html`<div>...</div>`
|
|
||||||
* - JSX templates: (props) => <div>...</div>
|
|
||||||
* - Function-based templates: (props) => string | JSX.Element | Function.
|
|
||||||
*/
|
|
||||||
resultsFooterComponent?: (
|
resultsFooterComponent?: (
|
||||||
props: {
|
props: ResultsFooterComponentProps
|
||||||
state: AutocompleteState<InternalDocSearchHit>;
|
|
||||||
},
|
|
||||||
helpers?: {
|
|
||||||
html: (template: TemplateStringsArray, ...values: any[]) => any;
|
|
||||||
}
|
|
||||||
) => JSX.Element | null;
|
) => JSX.Element | null;
|
||||||
|
/**
|
||||||
|
* A custom action that can be rendered in the Modal's footer before the
|
||||||
|
* Algolia logo. The component will be rendered as a child of `<div
|
||||||
|
* className="DocSearch-Footer-Action" />`.
|
||||||
|
*/
|
||||||
|
footerAction?: React.ReactNode;
|
||||||
/** Hook to wrap or modify the algolia search client. */
|
/** Hook to wrap or modify the algolia search client. */
|
||||||
transformSearchClient?: (
|
transformSearchClient?: (
|
||||||
searchClient: DocSearchTransformClient
|
searchClient: DocSearchTransformClient
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ export function DocSearchAskAiModal({
|
||||||
indices,
|
indices,
|
||||||
facets,
|
facets,
|
||||||
isHybridModeSupported = false,
|
isHybridModeSupported = false,
|
||||||
|
footerAction,
|
||||||
...props
|
...props
|
||||||
}: DocSearchAskAiModalProps): JSX.Element {
|
}: DocSearchAskAiModalProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
|
|
@ -645,6 +646,7 @@ export function DocSearchAskAiModal({
|
||||||
<Footer
|
<Footer
|
||||||
translations={footerTranslations}
|
translations={footerTranslations}
|
||||||
isAskAiActive={isAskAiActive}
|
isAskAiActive={isAskAiActive}
|
||||||
|
footerAction={footerAction}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ export function DocSearchModal({
|
||||||
recentSearchesWithFavoritesLimit = 4,
|
recentSearchesWithFavoritesLimit = 4,
|
||||||
indices,
|
indices,
|
||||||
facets,
|
facets,
|
||||||
|
footerAction,
|
||||||
...props
|
...props
|
||||||
}: DocSearchModalProps): JSX.Element {
|
}: DocSearchModalProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
|
|
@ -296,7 +297,9 @@ export function DocSearchModal({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
footer={<Footer translations={footerTranslations} />}
|
footer={
|
||||||
|
<Footer translations={footerTranslations} footerAction={footerAction} />
|
||||||
|
}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export type FooterTranslations = Partial<{
|
||||||
type FooterProps = Partial<{
|
type FooterProps = Partial<{
|
||||||
translations: FooterTranslations;
|
translations: FooterTranslations;
|
||||||
isAskAiActive: boolean;
|
isAskAiActive: boolean;
|
||||||
|
footerAction: React.ReactNode;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
interface CommandIconProps {
|
interface CommandIconProps {
|
||||||
|
|
@ -50,6 +51,7 @@ function CommandIcon(props: CommandIconProps): JSX.Element {
|
||||||
export function Footer({
|
export function Footer({
|
||||||
translations = {},
|
translations = {},
|
||||||
isAskAiActive = false,
|
isAskAiActive = false,
|
||||||
|
footerAction,
|
||||||
}: FooterProps): JSX.Element {
|
}: FooterProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
selectText = 'Select',
|
selectText = 'Select',
|
||||||
|
|
@ -64,11 +66,13 @@ export function Footer({
|
||||||
poweredByText = 'Powered by',
|
poweredByText = 'Powered by',
|
||||||
} = translations;
|
} = translations;
|
||||||
|
|
||||||
|
const hasFooterAction =
|
||||||
|
footerAction !== null &&
|
||||||
|
footerAction !== undefined &&
|
||||||
|
typeof footerAction !== 'boolean';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="DocSearch-Logo">
|
|
||||||
<AlgoliaLogo translations={{ poweredByText }} />
|
|
||||||
</div>
|
|
||||||
<ul className="DocSearch-Commands">
|
<ul className="DocSearch-Commands">
|
||||||
<li>
|
<li>
|
||||||
<kbd className="DocSearch-Commands-Key">
|
<kbd className="DocSearch-Commands-Key">
|
||||||
|
|
@ -105,6 +109,14 @@ export function Footer({
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div className="DocSearch-Footer-Actions">
|
||||||
|
{hasFooterAction && (
|
||||||
|
<div className="DocSearch-Footer-Action">{footerAction}</div>
|
||||||
|
)}
|
||||||
|
<div className="DocSearch-Logo">
|
||||||
|
<AlgoliaLogo translations={{ poweredByText }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,14 @@ function DocSearchAI(props: Partial<DocSearchAIProps>): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FooterAction(): JSX.Element {
|
||||||
|
return <button type="button">Footer action</button>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EmptyFooterAction(): null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// mock empty response
|
// mock empty response
|
||||||
function noResultSearch(_queries: any, _requestOptions?: any): Promise<any> {
|
function noResultSearch(_queries: any, _requestOptions?: any): Promise<any> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
|
@ -96,6 +104,80 @@ describe('api', () => {
|
||||||
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
|
expect(document.querySelector(docSearchSelector)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('footerAction', () => {
|
||||||
|
it('renders the action in the standard modal', async () => {
|
||||||
|
render(<DocSearch footerAction={<FooterAction />} />);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(await screen.findByText('Search'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('button', { name: 'Footer action' })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render an empty action', async () => {
|
||||||
|
render(<DocSearch footerAction={<EmptyFooterAction />} />);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(await screen.findByText('Search'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByRole('button', { name: 'Footer action' })
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render an action wrapper when no action is provided', async () => {
|
||||||
|
render(<DocSearch />);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(await screen.findByText('Search'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.querySelector('.DocSearch-Footer-Action')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render an action wrapper for a boolean action', async () => {
|
||||||
|
render(<DocSearch footerAction={false} />);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(await screen.findByText('Search'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
document.querySelector('.DocSearch-Footer-Action')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders numeric footer actions', async () => {
|
||||||
|
render(<DocSearch footerAction={0} />);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(await screen.findByText('Search'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(document.querySelector('.DocSearch-Footer-Action')).toHaveTextContent(
|
||||||
|
'0'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the action in the Ask AI modal', async () => {
|
||||||
|
render(<DocSearchAI footerAction={<FooterAction />} />);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(await screen.findByText('Search'));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await screen.findByRole('button', { name: 'Footer action' })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('translations', () => {
|
describe('translations', () => {
|
||||||
it('overrides the default DocSearchButton text', () => {
|
it('overrides the default DocSearchButton text', () => {
|
||||||
render(
|
render(
|
||||||
|
|
|
||||||
|
|
@ -176,16 +176,22 @@ Function that transforms hits before grouping and rendering. Defaults to the ide
|
||||||
|
|
||||||
### `hitComponent`
|
### `hitComponent`
|
||||||
|
|
||||||
> `type: template function` | **optional**
|
> `type: ({ hit, children }, { html }) => JSX.Element` | **optional**
|
||||||
|
|
||||||
Template for a result link. Defaults to the built-in hit template. See [Templates](#templates).
|
Template for a result link. Defaults to the built-in hit template. See [Templates](#templates).
|
||||||
|
|
||||||
### `resultsFooterComponent`
|
### `resultsFooterComponent`
|
||||||
|
|
||||||
> `type: template function` | **optional**
|
> `type: ({ state }, { html }) => JSX.Element | null` | **optional**
|
||||||
|
|
||||||
Template below the result collections. There's no default. See [Templates](#templates).
|
Template below the result collections. There's no default. See [Templates](#templates).
|
||||||
|
|
||||||
|
### `footerAction`
|
||||||
|
|
||||||
|
> `type: (_, { html }) => JSX.Element | null` | **optional**
|
||||||
|
|
||||||
|
Optional function to render a custom action in the Modal's footer. It will be rendered in line with the Algolia "powered by" logo. See [Templates](#templates).
|
||||||
|
|
||||||
### `transformSearchClient`
|
### `transformSearchClient`
|
||||||
|
|
||||||
> `type: (client) => client` | **optional**
|
> `type: (client) => client` | **optional**
|
||||||
|
|
@ -290,16 +296,26 @@ Set `translations.modal.resultsScreen.resultBadgeLabelText` to describe the badg
|
||||||
|
|
||||||
### Templates
|
### Templates
|
||||||
|
|
||||||
`hitComponent` receives `{ hit, children }`. `resultsFooterComponent` receives `{ state }`. JavaScript templates can return a Preact element, a string, or a component function. The optional second argument provides an `html` tagged-template helper.
|
- `hitComponent` receives `{ hit, children }`.
|
||||||
|
- `resultsFooterComponent` receives `{ state }`.
|
||||||
|
- `footerAction` currently does not receive any extra props.
|
||||||
|
|
||||||
|
JavaScript templates can return a Preact element, a string, or a component function. The optional second argument provides an `html` tagged-template helper.
|
||||||
|
|
||||||
```js title="docsearch-options.js"
|
```js title="docsearch-options.js"
|
||||||
hitComponent({ hit, children }, { html }) {
|
docsearch({
|
||||||
return html`<a href=${hit.url} data-result-type=${hit.type}>${children}</a>`;
|
hitComponent({ hit, children }, { html }) {
|
||||||
}
|
return html`<a href=${hit.url} data-result-type=${hit.type}>${children}</a>`;
|
||||||
|
},
|
||||||
|
resultsFooterComponent({ state }, { html }) {
|
||||||
|
return html`<p>No results found for query: ${state.query}</p>`;
|
||||||
|
},
|
||||||
|
footerAction(_, { html }) {
|
||||||
|
return html`<a href="https://algolia.com">Our other project</a>`;
|
||||||
|
},
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
String returns render as text, not HTML.
|
|
||||||
|
|
||||||
## Ask AI options
|
## Ask AI options
|
||||||
|
|
||||||
Ask AI is available from the default `@docsearch/js` entry. Its `askAi` option is required by that entry's `DocSearchProps` type.
|
Ask AI is available from the default `@docsearch/js` entry. Its `askAi` option is required by that entry's `DocSearchProps` type.
|
||||||
|
|
|
||||||
|
|
@ -121,16 +121,22 @@ Receives the modal portal. Defaults to `document.body`.
|
||||||
|
|
||||||
#### `hitComponent`
|
#### `hitComponent`
|
||||||
|
|
||||||
> `type: (props, helpers?) => JSX.Element` | **optional**
|
> `type: (props) => JSX.Element` | **optional**
|
||||||
|
|
||||||
Renders an individual result. Defaults to the built-in hit component.
|
Renders an individual result. Defaults to the built-in hit component.
|
||||||
|
|
||||||
#### `resultsFooterComponent`
|
#### `resultsFooterComponent`
|
||||||
|
|
||||||
> `type: (props, helpers?) => JSX.Element | null` | **optional**
|
> `type: (props) => JSX.Element | null` | **optional**
|
||||||
|
|
||||||
Renders content below the results. Defaults to `null`.
|
Renders content below the results. Defaults to `null`.
|
||||||
|
|
||||||
|
#### `footerAction`
|
||||||
|
|
||||||
|
> `type: React.ReactNode` | **optional**
|
||||||
|
|
||||||
|
Renders a custom element in the modal footer, before the "Powered by Algolia" logo. Passing `null` renders nothing.
|
||||||
|
|
||||||
#### `disableUserPersonalization`
|
#### `disableUserPersonalization`
|
||||||
|
|
||||||
> `type: boolean` | **optional**
|
> `type: boolean` | **optional**
|
||||||
|
|
|
||||||
|
|
@ -95,12 +95,12 @@ Transforms hits before DocSearch groups and renders them. Defaults to the identi
|
||||||
|
|
||||||
### `hitComponent`
|
### `hitComponent`
|
||||||
|
|
||||||
> `type: React.ComponentType<HitProps>` | **optional**
|
> `type: ({ hit, children }: HitComponentProps) => JSX.Element` | **optional**
|
||||||
|
|
||||||
Renders one result link. The default component renders the standard result content.
|
Renders one result link. The default component renders the standard result content.
|
||||||
|
|
||||||
```tsx title="Hit.tsx"
|
```tsx title="Hit.tsx"
|
||||||
function Hit({ hit, children }: HitProps): JSX.Element {
|
function Hit({ hit, children }: HitComponentProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<a href={hit.url} data-result-type={hit.type}>
|
<a href={hit.url} data-result-type={hit.type}>
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -113,10 +113,35 @@ Preserve `children` to retain the default hit content. `hit` is an `InternalDocS
|
||||||
|
|
||||||
### `resultsFooterComponent`
|
### `resultsFooterComponent`
|
||||||
|
|
||||||
> `type: React.ComponentType<ResultsFooterProps>` | **optional**
|
> `type: ({ state }: ResultsFooterComponentProps) => JSX.Element | null` | **optional**
|
||||||
|
|
||||||
Renders below result collections. It receives the current Autocomplete state. By default, DocSearch doesn't render a footer.
|
Renders below result collections. It receives the current Autocomplete state. By default, DocSearch doesn't render a footer.
|
||||||
|
|
||||||
|
### `footerAction`
|
||||||
|
|
||||||
|
> `type: React.ReactNode` | **optional**
|
||||||
|
|
||||||
|
Renders a custom element in the modal footer, before the "Powered by Algolia" logo. Passing `null` renders nothing.
|
||||||
|
|
||||||
|
```tsx title="Search.tsx"
|
||||||
|
function FooterAction(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<a href="https://algolia.com">
|
||||||
|
Our other project
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Search() {
|
||||||
|
return (
|
||||||
|
<DocSearch
|
||||||
|
// ...
|
||||||
|
footerAction={<FooterAction />}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `transformSearchClient`
|
### `transformSearchClient`
|
||||||
|
|
||||||
> `type: (client: SearchClient) => SearchClient` | **optional**
|
> `type: (client: SearchClient) => SearchClient` | **optional**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue