Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,33 +1,65 @@
/** @jsx h */

import {
ArrowRightIcon,
ChevronLeftIcon,
ChevronRightIcon,
createButtonComponent,
} from 'instantsearch-ui-components';
import { h } from 'preact';
import { useMemo } from 'preact/hooks';

import TemplateComponent from '../../components/Template/Template';
import { carousel } from '../../templates';

import type { IndexUiState, IndexWidget } from '../../types';
import type { ChatTemplates, UserClientSideToolWithTemplate } from './chat';
/** @jsx createElement */
import { createButtonComponent } from '../../Button';
import { createCarouselComponent, generateCarouselId } from '../../Carousel';
import { ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon } from '../icons';

import type {
ClientSideToolComponentProps,
ComponentProps,
RecordWithObjectID,
} from 'instantsearch-ui-components';
Renderer,
} from '../../../types';
import type { CarouselProps } from '../../Carousel';
import type { ClientSideToolComponentProps } from '../types';

type SearchToolInput = {
query: string;
number_of_results?: number;
facet_filters?: string[][];
};

type HeaderProps = {
showViewAll: boolean;
canScrollLeft: boolean;
canScrollRight: boolean;
scrollLeft: () => void;
scrollRight: () => void;
nbHits?: number;
input?: SearchToolInput;
hitsPerPage?: number;
setIndexUiState: (v: object) => void;
indexUiState: object;
onClose: () => void;
getSearchPageURL?: (nextUiState: object) => string;
};

export type SearchIndexToolProps<THit extends RecordWithObjectID> = {
useMemo: <TType>(factory: () => TType, inputs: readonly unknown[]) => TType;
useRef: <TType>(initialValue: TType) => { current: TType };
useState: <TType>(
initialState: TType
) => [TType, (newState: TType) => unknown];
getSearchPageURL?: (nextUiState: object) => string;
toolProps: ClientSideToolComponentProps;
itemComponent?: CarouselProps<THit>['itemComponent'];
headerComponent?: (props: HeaderProps) => JSX.Element;
headerProps: Omit<
// @ts-expect-error
ComponentProps<ReturnType<typeof createHeaderComponent>>,
| 'nbHits'
| 'query'
| 'hitsPerPage'
| 'setIndexUiState'
| 'indexUiState'
| 'getSearchPageURL'
| 'onClose'
>;
};

function generateIndexUiState(input: SearchToolInput) {
const indexUiState: IndexUiState = {};
const indexUiState: {
query?: string;
refinementList?: { [key: string]: string[] };
} = {};

if (input.query) {
indexUiState.query = input.query;
Expand All @@ -50,91 +82,11 @@ function generateIndexUiState(input: SearchToolInput) {
return indexUiState;
}

export function createCarouselTool<
THit extends RecordWithObjectID = RecordWithObjectID
>(
showViewAll: boolean,
templates: ChatTemplates<THit>,
getSearchPageURL?: (nextUiState: IndexUiState) => string
): UserClientSideToolWithTemplate {
const Button = createButtonComponent({
createElement: h,
});

function SearchLayoutComponent({
message,
indexUiState,
setIndexUiState,
onClose,
}: ClientSideToolComponentProps) {
const input = message?.input as SearchToolInput | undefined;
function createHeaderComponent({ createElement }: Renderer) {
const Button = createButtonComponent({ createElement });

const output = message?.output as
| {
hits?: Array<RecordWithObjectID<THit>>;
nbHits?: number;
}
| undefined;

const items = output?.hits || [];

const MemoedHeaderComponent = useMemo(() => {
return (
props: Omit<
// @ts-expect-error
ComponentProps<typeof HeaderComponent>,
| 'nbHits'
| 'query'
| 'hitsPerPage'
| 'setIndexUiState'
| 'indexUiState'
| 'getSearchPageURL'
| 'onClose'
>
) => (
// @ts-expect-error
<HeaderComponent
nbHits={output?.nbHits}
input={input}
hitsPerPage={items.length}
setIndexUiState={setIndexUiState}
indexUiState={indexUiState}
getSearchPageURL={getSearchPageURL}
onClose={onClose}
{...props}
/>
);
}, [
items.length,
input,
output?.nbHits,
setIndexUiState,
indexUiState,
onClose,
]);

return carousel({
showNavigation: false,
templates: {
header: MemoedHeaderComponent,
},
})({
items,
templates: {
item: ({ item }) => (
<TemplateComponent
templates={templates}
templateKey="item"
data={item}
rootTagName="fragment"
/>
),
},
sendEvent: () => {},
});
}

function HeaderComponent({
return function HeaderComponent({
showViewAll,
canScrollLeft,
canScrollRight,
scrollLeft,
Expand All @@ -145,21 +97,8 @@ export function createCarouselTool<
setIndexUiState,
indexUiState,
onClose,
// eslint-disable-next-line no-shadow
getSearchPageURL,
}: {
canScrollLeft: boolean;
canScrollRight: boolean;
scrollLeft: () => void;
scrollRight: () => void;
nbHits?: number;
input?: SearchToolInput;
hitsPerPage?: number;
setIndexUiState: IndexWidget['setIndexUiState'];
indexUiState: IndexUiState;
onClose: () => void;
getSearchPageURL?: (nextUiState: IndexUiState) => string;
}) {
}: HeaderProps) {
if ((hitsPerPage ?? 0) < 1) {
return null;
}
Expand Down Expand Up @@ -203,7 +142,7 @@ export function createCarouselTool<
className="ais-ChatToolSearchIndexCarouselHeaderViewAll"
>
View all
<ArrowRightIcon createElement={h} />
<ArrowRightIcon createElement={createElement} />
</Button>
)}
</div>
Expand All @@ -218,7 +157,7 @@ export function createCarouselTool<
disabled={!canScrollLeft}
className="ais-ChatToolSearchIndexCarouselHeaderScrollButton"
>
<ChevronLeftIcon createElement={h} />
<ChevronLeftIcon createElement={createElement} />
</Button>
<Button
variant="outline"
Expand All @@ -228,15 +167,110 @@ export function createCarouselTool<
disabled={!canScrollRight}
className="ais-ChatToolSearchIndexCarouselHeaderScrollButton"
>
<ChevronRightIcon createElement={h} />
<ChevronRightIcon createElement={createElement} />
</Button>
</div>
)}
</div>
);
}
};
}

export function createSearchIndexTool<THit extends RecordWithObjectID>({
createElement,
Fragment,
}: Renderer) {
const Header = createHeaderComponent({ createElement, Fragment });
const Carousel = createCarouselComponent({ createElement, Fragment });

return function SearchIndexTool({
useMemo,
useRef,
useState,
getSearchPageURL,
itemComponent: ItemComponent,
headerComponent: HeaderComponent,
toolProps: { message, indexUiState, setIndexUiState, onClose },
headerProps,
}: SearchIndexToolProps<THit>) {
const input = message?.input as SearchToolInput | undefined;

const output = message?.output as
| {
hits?: Array<RecordWithObjectID<THit>>;
nbHits?: number;
}
| undefined;

const items = output?.hits || [];

const [canScrollLeft, setCanScrollLeft] = useState(false);
const [canScrollRight, setCanScrollRight] = useState(true);

const carouselRefs: Pick<
CarouselProps<THit>,
| 'listRef'
| 'nextButtonRef'
| 'previousButtonRef'
| 'carouselIdRef'
| 'canScrollLeft'
| 'canScrollRight'
| 'setCanScrollLeft'
| 'setCanScrollRight'
> = {
listRef: useRef(null),
nextButtonRef: useRef(null),
previousButtonRef: useRef(null),
carouselIdRef: useRef(generateCarouselId()),
canScrollLeft,
canScrollRight,
setCanScrollLeft,
setCanScrollRight,
};

const MemoedHeader = useMemo(() => {
if (HeaderComponent) {
return HeaderComponent;
}

return () => (
// @ts-expect-error
<Header
nbHits={output?.nbHits}
input={input}
hitsPerPage={items.length}
canScrollLeft={canScrollLeft}
canScrollRight={canScrollRight}
setIndexUiState={setIndexUiState}
indexUiState={indexUiState}
getSearchPageURL={getSearchPageURL}
onClose={onClose}
{...headerProps}
/>
);
}, [
HeaderComponent,
output?.nbHits,
input,
items.length,
canScrollLeft,
canScrollRight,
setIndexUiState,
indexUiState,
getSearchPageURL,
onClose,
headerProps,
]);

return {
templates: { layout: SearchLayoutComponent },
return (
<Carousel
{...carouselRefs}
items={items}
itemComponent={ItemComponent}
// @ts-expect-error
headerComponent={MemoedHeader}
sendEvent={() => {}}
/>
);
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from './chat/ChatPrompt';
export * from './chat/ChatToggleButton';
export * from './chat/icons';
export * from './chat/types';
export * from './chat/tools/SearchIndexTool';
export * from './FrequentlyBoughtTogether';
export * from './Highlight';
export * from './Hits';
Expand Down
2 changes: 1 addition & 1 deletion packages/instantsearch.js/src/widgets/chat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
createDocumentationMessageGenerator,
} from '../../lib/utils';

import { createCarouselTool } from './searchIndexTool';
import { createCarouselTool } from './search-index-tool';

import type {
ChatRenderState,
Expand Down
Loading