diff --git a/docs/search-ui-react.citationprops.citation.md b/docs/search-ui-react.citationprops.citation.md new file mode 100644 index 00000000..69061d9d --- /dev/null +++ b/docs/search-ui-react.citationprops.citation.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [CitationProps](./search-ui-react.citationprops.md) > [citation](./search-ui-react.citationprops.citation.md) + +## CitationProps.citation property + +**Signature:** + +```typescript +citation: string; +``` diff --git a/docs/search-ui-react.citationprops.cssclasses.md b/docs/search-ui-react.citationprops.cssclasses.md new file mode 100644 index 00000000..89ccdc34 --- /dev/null +++ b/docs/search-ui-react.citationprops.cssclasses.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [CitationProps](./search-ui-react.citationprops.md) > [cssClasses](./search-ui-react.citationprops.cssclasses.md) + +## CitationProps.cssClasses property + +**Signature:** + +```typescript +cssClasses: GenerativeDirectAnswerCssClasses; +``` diff --git a/docs/search-ui-react.citationprops.md b/docs/search-ui-react.citationprops.md new file mode 100644 index 00000000..fd7d8f1f --- /dev/null +++ b/docs/search-ui-react.citationprops.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [CitationProps](./search-ui-react.citationprops.md) + +## CitationProps interface + +Props for citation card. + +**Signature:** + +```typescript +interface CitationProps +``` + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [citation](./search-ui-react.citationprops.citation.md) | | string | | +| [cssClasses](./search-ui-react.citationprops.cssclasses.md) | | [GenerativeDirectAnswerCssClasses](./search-ui-react.generativedirectanswercssclasses.md) | | +| [searchResults](./search-ui-react.citationprops.searchresults.md) | | Result\[\] | | + diff --git a/docs/search-ui-react.citationprops.searchresults.md b/docs/search-ui-react.citationprops.searchresults.md new file mode 100644 index 00000000..1f7bf0d6 --- /dev/null +++ b/docs/search-ui-react.citationprops.searchresults.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [CitationProps](./search-ui-react.citationprops.md) > [searchResults](./search-ui-react.citationprops.searchresults.md) + +## CitationProps.searchResults property + +**Signature:** + +```typescript +searchResults: Result[]; +``` diff --git a/docs/search-ui-react.generativedirectanswer.md b/docs/search-ui-react.generativedirectanswer.md index 5e979889..d3baea94 100644 --- a/docs/search-ui-react.generativedirectanswer.md +++ b/docs/search-ui-react.generativedirectanswer.md @@ -9,14 +9,14 @@ Displays the AI generated answer of a generative direct answer. **Signature:** ```typescript -declare function GenerativeDirectAnswer({ customCssClasses, answerHeader, citationsHeader }: GenerativeDirectAnswerProps): JSX.Element | null; +declare function GenerativeDirectAnswer({ customCssClasses, answerHeader, citationsHeader, CitationCard }: GenerativeDirectAnswerProps): JSX.Element | null; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| { customCssClasses, answerHeader, citationsHeader } | [GenerativeDirectAnswerProps](./search-ui-react.generativedirectanswerprops.md) | | +| { customCssClasses, answerHeader, citationsHeader, CitationCard } | [GenerativeDirectAnswerProps](./search-ui-react.generativedirectanswerprops.md) | | **Returns:** diff --git a/docs/search-ui-react.generativedirectanswerprops.citationcard.md b/docs/search-ui-react.generativedirectanswerprops.citationcard.md new file mode 100644 index 00000000..6ff169b2 --- /dev/null +++ b/docs/search-ui-react.generativedirectanswerprops.citationcard.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@yext/search-ui-react](./search-ui-react.md) > [GenerativeDirectAnswerProps](./search-ui-react.generativedirectanswerprops.md) > [CitationCard](./search-ui-react.generativedirectanswerprops.citationcard.md) + +## GenerativeDirectAnswerProps.CitationCard property + +The component for citation card + +**Signature:** + +```typescript +CitationCard?: (props: CitationProps) => JSX.Element | null; +``` diff --git a/docs/search-ui-react.generativedirectanswerprops.md b/docs/search-ui-react.generativedirectanswerprops.md index 5387445c..1f1aeb09 100644 --- a/docs/search-ui-react.generativedirectanswerprops.md +++ b/docs/search-ui-react.generativedirectanswerprops.md @@ -17,6 +17,7 @@ interface GenerativeDirectAnswerProps | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [answerHeader?](./search-ui-react.generativedirectanswerprops.answerheader.md) | | string \| JSX.Element | _(Optional)_ The header for the answer section of the generative direct answer. | +| [CitationCard?](./search-ui-react.generativedirectanswerprops.citationcard.md) | | (props: [CitationProps](./search-ui-react.citationprops.md)) => JSX.Element \| null | _(Optional)_ The component for citation card | | [citationsHeader?](./search-ui-react.generativedirectanswerprops.citationsheader.md) | | string \| JSX.Element | _(Optional)_ The header for the citations section of the generative direct answer. | | [customCssClasses?](./search-ui-react.generativedirectanswerprops.customcssclasses.md) | | [GenerativeDirectAnswerCssClasses](./search-ui-react.generativedirectanswercssclasses.md) | _(Optional)_ CSS classes for customizing the component styling. | diff --git a/docs/search-ui-react.md b/docs/search-ui-react.md index 8ad22583..6c99f746 100644 --- a/docs/search-ui-react.md +++ b/docs/search-ui-react.md @@ -20,7 +20,7 @@ | [Facets(props)](./search-ui-react.facets.md) | A component that displays all facets applicable to the current vertical search. | | [FilterDivider({ className })](./search-ui-react.filterdivider.md) | A divider component used to separate NumericalFacets, HierarchicalFacets, StandardFacets, and StaticFilters. | | [FilterSearch({ searchFields, label, placeholder, searchOnSelect, onSelect, onDropdownInputChange, afterDropdownInputFocus, sectioned, customCssClasses })](./search-ui-react.filtersearch.md) | A component which allows a user to search for filters associated with specific entities and fields. | -| [GenerativeDirectAnswer({ customCssClasses, answerHeader, citationsHeader })](./search-ui-react.generativedirectanswer.md) | Displays the AI generated answer of a generative direct answer. | +| [GenerativeDirectAnswer({ customCssClasses, answerHeader, citationsHeader, CitationCard })](./search-ui-react.generativedirectanswer.md) | Displays the AI generated answer of a generative direct answer. | | [Geolocation\_2({ geolocationOptions, radius, label, GeolocationIcon, handleClick, customCssClasses, })](./search-ui-react.geolocation_2.md) | A React Component which collects location information to create a location filter and perform a new search. | | [getSearchIntents(searchActions)](./search-ui-react.getsearchintents.md) | Get search intents of the current query stored in headless using autocomplete request. | | [getUserLocation(geolocationOptions)](./search-ui-react.getuserlocation.md) | Retrieves user's location using navigator.geolocation API. | @@ -63,6 +63,7 @@ | [ApplyFiltersButtonProps](./search-ui-react.applyfiltersbuttonprops.md) | Props for [ApplyFiltersButton()](./search-ui-react.applyfiltersbutton.md) | | [AutocompleteResultCssClasses](./search-ui-react.autocompleteresultcssclasses.md) | The CSS class interface for the Autocomplete Result. | | [CardProps](./search-ui-react.cardprops.md) | The props provided to every [CardComponent](./search-ui-react.cardcomponent.md). | +| [CitationProps](./search-ui-react.citationprops.md) | Props for citation card. | | [Coordinate](./search-ui-react.coordinate.md) | Coordinate use to represent the result's location on a map. | | [CtaData](./search-ui-react.ctadata.md) | The shape of a StandardCard CTA field's data. | | [DirectAnswerCssClasses](./search-ui-react.directanswercssclasses.md) | The CSS class interface for [DirectAnswer()](./search-ui-react.directanswer.md). | diff --git a/etc/search-ui-react.api.md b/etc/search-ui-react.api.md index ca1b8d47..3b160df3 100644 --- a/etc/search-ui-react.api.md +++ b/etc/search-ui-react.api.md @@ -136,6 +136,16 @@ export interface CardProps { result: Result; } +// @public +export interface CitationProps { + // (undocumented) + citation: string; + // (undocumented) + cssClasses: GenerativeDirectAnswerCssClasses; + // (undocumented) + searchResults: Result[]; +} + // @public export const ComponentsContentPath = "node_modules/@yext/search-ui-react/lib/**/*.{js,jsx}"; @@ -313,7 +323,7 @@ export interface FilterSearchProps { export type FocusedItemData = Record; // @public -export function GenerativeDirectAnswer({ customCssClasses, answerHeader, citationsHeader }: GenerativeDirectAnswerProps): JSX.Element | null; +export function GenerativeDirectAnswer({ customCssClasses, answerHeader, citationsHeader, CitationCard }: GenerativeDirectAnswerProps): JSX.Element | null; // @public export interface GenerativeDirectAnswerCssClasses { @@ -338,6 +348,7 @@ export interface GenerativeDirectAnswerCssClasses { // @public export interface GenerativeDirectAnswerProps { answerHeader?: string | JSX.Element; + CitationCard?: (props: CitationProps) => JSX.Element | null; citationsHeader?: string | JSX.Element; customCssClasses?: GenerativeDirectAnswerCssClasses; } diff --git a/src/components/GenerativeDirectAnswer.tsx b/src/components/GenerativeDirectAnswer.tsx index ef1b734c..31dab23f 100644 --- a/src/components/GenerativeDirectAnswer.tsx +++ b/src/components/GenerativeDirectAnswer.tsx @@ -2,6 +2,7 @@ import { GenerativeDirectAnswerResponse, useSearchActions, useSearchState, + SearchTypeEnum, Result } from '@yext/search-headless-react'; import { useComposedCssClasses } from '../hooks'; @@ -31,7 +32,7 @@ const builtInCssClasses: Readonly = { answerText: 'mt-4', divider: 'border-b border-gray-200 w-full pb-6 mb-6', citationsContainer: 'mt-4 flex overflow-x-auto gap-4', - citation: 'p-4 border border-gray-200 rounded-lg shadow-sm bg-slate-100 flex flex-col grow-0 shrink-0 basis-64 text-sm text-neutral', + citation: 'p-4 border border-gray-200 rounded-lg shadow-sm bg-slate-100 flex flex-col grow-0 shrink-0 basis-64 text-sm text-neutral overflow-x-auto', citationTitle: 'font-bold', citationSnippet: 'line-clamp-2 text-ellipsis break-words' }; @@ -45,9 +46,11 @@ export interface GenerativeDirectAnswerProps { /** CSS classes for customizing the component styling. */ customCssClasses?: GenerativeDirectAnswerCssClasses, /** The header for the answer section of the generative direct answer. */ - answerHeader?: string | JSX.Element + answerHeader?: string | JSX.Element, /** The header for the citations section of the generative direct answer. */ - citationsHeader?: string | JSX.Element + citationsHeader?: string | JSX.Element, + /** The component for citation card */ + CitationCard?: (props: CitationProps) => JSX.Element | null } /** @@ -61,20 +64,22 @@ export interface GenerativeDirectAnswerProps { export function GenerativeDirectAnswer({ customCssClasses, answerHeader, - citationsHeader + citationsHeader, + CitationCard }: GenerativeDirectAnswerProps): JSX.Element | null { const cssClasses = useComposedCssClasses(builtInCssClasses, customCssClasses); + const isUniversal = useSearchState(state => state.meta.searchType) === SearchTypeEnum.Universal; const universalResults = useSearchState(state => state.universal); const verticalResults = useSearchState(state => state.vertical); const searchResults: Result[] | undefined = React.useMemo(() => { - if (universalResults) { + if (isUniversal) { return universalResults.verticals?.flatMap(v => v.results); - } else if (verticalResults) { + } else { return verticalResults.results; } - }, [universalResults, verticalResults]); + }, [isUniversal, universalResults, verticalResults]); const searchActions = useSearchActions(); const gdaResponse = useSearchState(state => state.generativeDirectAnswer?.response); @@ -89,21 +94,21 @@ export function GenerativeDirectAnswer({ if (!searchResults?.length || isLoading || !gdaResponse || gdaResponse.resultStatus !== 'SUCCESS') { return null; - } + } return (
- +
); } -interface AnswerProps extends GenerativeDirectAnswerProps { +interface AnswerProps { gdaResponse: GenerativeDirectAnswerResponse, cssClasses: GenerativeDirectAnswerCssClasses, - answerHeader?: string | JSX.Element, + answerHeader?: string | JSX.Element } /** @@ -113,7 +118,7 @@ function Answer(props: AnswerProps) { const { gdaResponse, cssClasses, - answerHeader, + answerHeader = 'AI Generated Answer' } = props; return <>
@@ -123,11 +128,12 @@ function Answer(props: AnswerProps) { ; } -interface CitationsProps extends GenerativeDirectAnswerProps { +interface CitationsProps { gdaResponse: GenerativeDirectAnswerResponse, cssClasses: GenerativeDirectAnswerCssClasses, citationsHeader?: string | JSX.Element, - searchResults: Result[] + searchResults: Result[], + CitationCard?: (props: CitationProps) => JSX.Element | null } /** @@ -137,8 +143,9 @@ function Citations(props: CitationsProps) { const { gdaResponse, cssClasses, - citationsHeader, - searchResults + citationsHeader = `Sources (${gdaResponse.citations.length})`, + searchResults, + CitationCard = Citation } = props; if (!gdaResponse.citations.length) { return null; @@ -149,18 +156,34 @@ function Citations(props: CitationsProps) {
{gdaResponse.citations.map( - citation => Citation(searchResults, citation, cssClasses))} + citation => )}
; } -function Citation(searchResults: Result[], citation: string, cssClasses: GenerativeDirectAnswerCssClasses) { +/** + * Props for citation card. + * + * @public + */ +export interface CitationProps { + searchResults: Result[], + citation: string, + cssClasses: GenerativeDirectAnswerCssClasses +} + +function Citation(props: CitationProps) { + const { + searchResults, + citation, + cssClasses + } = props; const rawResult: Result | undefined = searchResults.find(r => r.rawData.uid === citation); if (!rawResult) { return null; } - return
+ return
{rawResult.rawData.name}
{rawResult.rawData.description}
; diff --git a/src/components/index.ts b/src/components/index.ts index 526ae7f6..854f4f35 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -180,4 +180,5 @@ export { GenerativeDirectAnswer, GenerativeDirectAnswerCssClasses, GenerativeDirectAnswerProps, + CitationProps } from './GenerativeDirectAnswer'; \ No newline at end of file diff --git a/test-site/src/pages/UniversalPage.tsx b/test-site/src/pages/UniversalPage.tsx index d1f1ec90..179e3063 100644 --- a/test-site/src/pages/UniversalPage.tsx +++ b/test-site/src/pages/UniversalPage.tsx @@ -1,4 +1,4 @@ -import { provideHeadless, useSearchActions } from '@yext/search-headless-react'; +import { provideHeadless, useSearchActions, Result } from '@yext/search-headless-react'; import { DirectAnswer, DropdownItem, @@ -7,7 +7,8 @@ import { SpellCheck, UniversalResults, VisualAutocompleteConfig, - GenerativeDirectAnswer + GenerativeDirectAnswer, + CitationProps } from '@yext/search-ui-react'; import classNames from 'classnames'; import { useLayoutEffect } from 'react'; @@ -59,6 +60,24 @@ const customSearchBarCss = { searchBarContainer: 'mb-3 text-emerald-800' }; +function CustomCitationCard(props: CitationProps): JSX.Element | null { + const { + searchResults, + citation, + cssClasses + } = props; + const rawResult: Result | undefined = searchResults.find((r: Result) => r.rawData.uid === citation); + + if (!rawResult) { + return null; + } + + return
+ {typeof rawResult.rawData.id === 'string' &&
{rawResult.rawData.id}
} + {typeof rawResult.rawData.type === 'string' &&
{rawResult.rawData.type}
} +
; +} + export default function UniversalPage(): JSX.Element { const searchActions = useSearchActions(); useLayoutEffect(() => { @@ -73,7 +92,7 @@ export default function UniversalPage(): JSX.Element { customCssClasses={customSearchBarCss} /> - +