diff --git a/packages/headless/src/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.ts b/packages/headless/src/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.ts new file mode 100644 index 00000000000..6557cfc3c12 --- /dev/null +++ b/packages/headless/src/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.ts @@ -0,0 +1,34 @@ +import {ensureAtLeastOneSolutionType} from '../../../../app/commerce-ssr-engine/common.js'; +import { + ControllerDefinitionOption, + SolutionType, + SubControllerDefinitionWithoutProps, +} from '../../../../app/commerce-ssr-engine/types/common.js'; +import {buildProductListing} from '../../product-listing/headless-product-listing.js'; +import {buildSearch} from '../../search/headless-search.js'; +import { + BreadcrumbManager, + BreadcrumbManagerState, +} from './headless-core-breadcrumb-manager.js'; + +export type {BreadcrumbManager, BreadcrumbManagerState}; + +/** + * Defines a `BreadcrumbManager` controller instance. + * + * @returns The `BreadcrumbManager` controller definition. + * + * @internal + */ +export function defineBreadcrumbManager< + TOptions extends ControllerDefinitionOption | undefined, +>(options?: TOptions) { + ensureAtLeastOneSolutionType(options); + return { + ...options, + build: (engine, solutionType) => + solutionType === SolutionType.listing + ? buildProductListing(engine).breadcrumbManager() + : buildSearch(engine).breadcrumbManager(), + } as SubControllerDefinitionWithoutProps; +} diff --git a/packages/headless/src/controllers/commerce/core/facets/date/headless-commerce-date-facet.ts b/packages/headless/src/controllers/commerce/core/facets/date/headless-commerce-date-facet.ts index a964a5612ff..0cfe44a9ab0 100644 --- a/packages/headless/src/controllers/commerce/core/facets/date/headless-commerce-date-facet.ts +++ b/packages/headless/src/controllers/commerce/core/facets/date/headless-commerce-date-facet.ts @@ -15,6 +15,8 @@ import { buildCoreCommerceFacet, } from '../headless-core-commerce-facet.js'; +export type {DateFacetValue}; + export type DateFacetOptions = Omit< CoreCommerceFacetOptions, 'toggleSelectActionCreator' | 'toggleExcludeActionCreator' diff --git a/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts b/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts index d42a68dcbac..89ba23b3c66 100644 --- a/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts +++ b/packages/headless/src/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.ts @@ -38,6 +38,7 @@ import { import { DateFacet, DateFacetState, + DateFacetValue, getDateFacetState, } from '../date/headless-commerce-date-facet.js'; import { @@ -53,6 +54,7 @@ import { import { getNumericFacetState, NumericFacet, + NumericFacetValue, NumericFacetState, } from '../numeric/headless-commerce-numeric-facet.js'; import { @@ -72,8 +74,10 @@ export type { CategoryFacetValue, CategoryFacetSearchResult, DateFacet, + DateFacetValue, DateFacetState, NumericFacet, + NumericFacetValue, NumericFacetState, RegularFacet, RegularFacetState, diff --git a/packages/headless/src/controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet.ts b/packages/headless/src/controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet.ts index 4e6f21834ae..fcd3cf1ce2c 100644 --- a/packages/headless/src/controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet.ts +++ b/packages/headless/src/controllers/commerce/core/facets/numeric/headless-commerce-numeric-facet.ts @@ -20,6 +20,8 @@ import { buildCoreCommerceFacet, } from '../headless-core-commerce-facet.js'; +export type {NumericFacetValue}; + export type NumericFacetOptions = Omit< CoreCommerceFacetOptions, 'toggleSelectActionCreator' | 'toggleExcludeActionCreator' diff --git a/packages/headless/src/ssr-commerce.index.ts b/packages/headless/src/ssr-commerce.index.ts index 42685abaa17..9c153b8b379 100644 --- a/packages/headless/src/ssr-commerce.index.ts +++ b/packages/headless/src/ssr-commerce.index.ts @@ -65,10 +65,12 @@ export type { CategoryFacetValue, CategoryFacetSearchResult, DateFacet, + DateFacetValue, DateFacetState, FacetGenerator, FacetGeneratorState, NumericFacet, + NumericFacetValue, NumericFacetState, RegularFacet, RegularFacetState, @@ -149,6 +151,12 @@ export type { } from './controllers/commerce/core/sort/headless-core-commerce-sort.ssr.js'; export {defineSort} from './controllers/commerce/core/sort/headless-core-commerce-sort.ssr.js'; +export type { + BreadcrumbManager, + BreadcrumbManagerState, +} from './controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.js'; +export {defineBreadcrumbManager} from './controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.js'; + export type { Summary, ProductListingSummaryState, diff --git a/packages/samples/headless-ssr-commerce/app/_components/breadcrumb-manager.tsx b/packages/samples/headless-ssr-commerce/app/_components/breadcrumb-manager.tsx new file mode 100644 index 00000000000..0ea61e0d277 --- /dev/null +++ b/packages/samples/headless-ssr-commerce/app/_components/breadcrumb-manager.tsx @@ -0,0 +1,81 @@ +import { + BreadcrumbManagerState, + NumericFacetValue, + DateFacetValue, + CategoryFacetValue, + BreadcrumbManager as HeadlessBreadcrumbManager, + RegularFacetValue, +} from '@coveo/headless/ssr-commerce'; +import {useEffect, useState} from 'react'; + +interface BreadcrumbManagerProps { + staticState: BreadcrumbManagerState; + controller?: HeadlessBreadcrumbManager; +} + +export default function BreadcrumbManager(props: BreadcrumbManagerProps) { + const {staticState, controller} = props; + + const [state, setState] = useState(staticState); + + useEffect(() => { + controller?.subscribe(() => setState(controller.state)); + }, [controller]); + + const renderBreadcrumbValue = ( + value: + | CategoryFacetValue + | RegularFacetValue + | NumericFacetValue + | DateFacetValue, + type: string + ) => { + switch (type) { + case 'hierarchical': + return (value as CategoryFacetValue).path.join(' > '); + case 'regular': + return (value as RegularFacetValue).value; + case 'numericalRange': + return ( + (value as NumericFacetValue).start + + ' - ' + + (value as NumericFacetValue).end + ); + case 'dateRange': + return ( + (value as DateFacetValue).start + + ' - ' + + (value as DateFacetValue).end + ); + default: + return null; + } + }; + + return ( +
+
+ +
+ +
+ ); +} diff --git a/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx b/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx index f4e554f08be..05612968ed2 100644 --- a/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx +++ b/packages/samples/headless-ssr-commerce/app/_components/pages/listing-page.tsx @@ -7,6 +7,7 @@ import { ListingHydratedState, ListingStaticState, } from '../../_lib/commerce-engine'; +import BreadcrumbManager from '../breadcrumb-manager'; import Cart from '../cart'; import FacetGenerator from '../facets/facet-generator'; import Pagination from '../pagination'; @@ -63,6 +64,10 @@ export default function ListingPage({ hydratedState?.controllers.instantProducts } /> + +