From 2f9a8f86fc9fec4d4fdd1346a7450767ef65b8e5 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Tue, 6 Aug 2024 08:54:14 -0400 Subject: [PATCH 01/11] Add eslint config file https://coveord.atlassian.net/browse/KIT-3431 --- packages/samples/headless-commerce-react/.eslintrc.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/samples/headless-commerce-react/.eslintrc.json diff --git a/packages/samples/headless-commerce-react/.eslintrc.json b/packages/samples/headless-commerce-react/.eslintrc.json new file mode 100644 index 00000000000..f2b963db013 --- /dev/null +++ b/packages/samples/headless-commerce-react/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "root": true +} From ec0172f5cd58bb5fa8399e783a7bacf48d5d99ae Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Tue, 6 Aug 2024 08:54:56 -0400 Subject: [PATCH 02/11] Add instant products in sample https://coveord.atlassian.net/browse/KIT-3431 --- .../instant-products/instant-products.tsx | 53 ++++++++++++++ .../src/components/search-box/search-box.tsx | 70 +++++++++++++++---- .../standalone-search-box.tsx | 65 +++++++++++++---- .../src/layout/layout.tsx | 8 ++- .../src/pages/search-page.tsx | 13 +++- 5 files changed, 178 insertions(+), 31 deletions(-) create mode 100644 packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx diff --git a/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx b/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx new file mode 100644 index 00000000000..0df293b0272 --- /dev/null +++ b/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx @@ -0,0 +1,53 @@ +import { + InstantProducts as HeadlessInstantProducts, + Product, +} from '@coveo/headless/commerce'; +import {useEffect, useState} from 'react'; + +interface IInstantProductProps { + controller: HeadlessInstantProducts; + navigate: (pathName: string) => void; +} + +export default function InstantProducts(props: IInstantProductProps) { + const {controller, navigate} = props; + const [state, setState] = useState(controller.state); + + useEffect( + () => controller.subscribe(() => setState({...controller.state})), + [controller] + ); + + if (state.products.length === 0) { + return null; + } + + const clickProduct = (product: Product) => { + controller.interactiveProduct({options: {product}}).select(); + + // Normally here, you would simply navigate to product.clickUri. + const productId = product.ec_product_id ?? product.permanentid; + const productName = product.ec_name ?? product.permanentid; + const productPrice = product.ec_promo_price ?? product.ec_price ?? NaN; + navigate(`/product/${productId}/${productName}/${productPrice}`); + // In this sample project, we navigate to a custom URL because the app doesn't have access to a commerce backend + // service to retrieve detailed product information from for the purpose of rendering a product description page + // (PDP). + // Therefore, we encode bare-minimum product information in the URL, and use it to render the PDP. + // This is by no means a realistic scenario. + }; + + return ( + <> + + + ); +} diff --git a/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx b/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx index e41751edcc8..c468984290f 100644 --- a/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx +++ b/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx @@ -1,12 +1,18 @@ -import {SearchBox as HeadlessSearchBox} from '@coveo/headless/commerce'; +import { + SearchBox as HeadlessSearchBox, + InstantProducts as HeadlessInstantProducts, +} from '@coveo/headless/commerce'; import {useEffect, useState} from 'react'; +import InstantProducts from '../instant-products/instant-products'; interface ISearchBoxProps { controller: HeadlessSearchBox; + instantProductsController: HeadlessInstantProducts; + navigate: (pathName: string) => void; } export default function SearchBox(props: ISearchBoxProps) { - const {controller} = props; + const {controller, instantProductsController, navigate} = props; const [state, setState] = useState(controller.state); @@ -15,12 +21,17 @@ export default function SearchBox(props: ISearchBoxProps) { controller.subscribe(() => setState(controller.state)); }, [controller]); + const onSearchBoxInputChange = (e: React.ChangeEvent) => { + controller.updateText(e.target.value); + instantProductsController.updateQuery(e.target.value); + }; + return (
controller.updateText(e.target.value)} + onChange={(e) => onSearchBoxInputChange(e)} > {state.value !== '' && ( @@ -28,18 +39,47 @@ export default function SearchBox(props: ISearchBoxProps) { )} - {state.suggestions.length > 0 && ( -
    - {state.suggestions.map((suggestion, index) => ( -
  • - -
  • - ))} -
- )} + + + + + + + + + + + + + +
Query suggestionsInstant products
+ {state.suggestions.length > 0 && ( +
    + {state.suggestions.map((suggestion, index) => ( +
  • + +
  • + ))} +
+ )} +
+ +
); } diff --git a/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx b/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx index a6a6c3c87c1..e89880e644a 100644 --- a/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx +++ b/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx @@ -1,13 +1,18 @@ -import {StandaloneSearchBox as HeadlessStandaloneSearchBox} from '@coveo/headless/commerce'; +import { + StandaloneSearchBox as HeadlessStandaloneSearchBox, + InstantProducts as HeadlessInstantProducts, +} from '@coveo/headless/commerce'; import {useEffect, useState} from 'react'; +import InstantProducts from '../instant-products/instant-products'; interface IStandaloneSearchBoxProps { navigate: (url: string) => void; controller: HeadlessStandaloneSearchBox; + instantProductsController: HeadlessInstantProducts; } export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) { - const {navigate, controller} = props; + const {navigate, controller, instantProductsController} = props; const [state, setState] = useState(controller.state); @@ -25,12 +30,17 @@ export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) { } }, [state.redirectTo, navigate, state.value, controller]); + const onSearchBoxInputChange = (e: React.ChangeEvent) => { + controller.updateText(e.target.value); + instantProductsController.updateQuery(e.target.value); + }; + return (
controller.updateText(e.target.value)} + onChange={(e) => onSearchBoxInputChange(e)} > {state.value !== '' && ( @@ -39,16 +49,45 @@ export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) { )} {state.suggestions.length > 0 && ( -
    - {state.suggestions.map((suggestion, index) => ( -
  • - -
  • - ))} -
+ + + + + + + + + + + + + +
Query suggestionsInstant products
+
    + {state.suggestions.map((suggestion, index) => ( +
  • + +
  • + ))} +
+
+ +
)}
); diff --git a/packages/samples/headless-commerce-react/src/layout/layout.tsx b/packages/samples/headless-commerce-react/src/layout/layout.tsx index cb900bfe3ed..3410fa9cfd1 100644 --- a/packages/samples/headless-commerce-react/src/layout/layout.tsx +++ b/packages/samples/headless-commerce-react/src/layout/layout.tsx @@ -1,5 +1,6 @@ import { buildCart, + buildInstantProducts, buildStandaloneSearchBox, CommerceEngine, } from '@coveo/headless/commerce'; @@ -17,6 +18,8 @@ interface ILayoutProps { export default function Layout(props: ILayoutProps) { const {engine, isPending, navigate, children} = props; + const standaloneSearchBoxId = 'standalone-search-box'; + return (
@@ -93,10 +96,13 @@ export default function Layout(props: ILayoutProps) { controller={buildStandaloneSearchBox(engine, { options: { redirectionUrl: '/search', - id: 'standalone-search-box', + id: standaloneSearchBoxId, highlightOptions, }, })} + instantProductsController={buildInstantProducts(engine, { + options: {searchBoxId: standaloneSearchBoxId}, + })} /> )}
diff --git a/packages/samples/headless-commerce-react/src/pages/search-page.tsx b/packages/samples/headless-commerce-react/src/pages/search-page.tsx index b188b8faaff..f42ce256acf 100644 --- a/packages/samples/headless-commerce-react/src/pages/search-page.tsx +++ b/packages/samples/headless-commerce-react/src/pages/search-page.tsx @@ -1,4 +1,5 @@ import { + buildInstantProducts, buildSearch, buildSearchBox, Cart, @@ -25,8 +26,10 @@ export default function Search(props: ISearchProps) { contextController.setView({url}); const searchController = buildSearch(engine); + + const searchBoxId = 'search-box'; const searchBoxController = buildSearchBox(engine, { - options: {id: 'search-box', highlightOptions}, + options: {id: searchBoxId, highlightOptions}, }); const bindUrlManager = useCallback(() => { @@ -84,7 +87,13 @@ export default function Search(props: ISearchProps) { return (
- +

Search

Date: Tue, 13 Aug 2024 10:42:13 -0400 Subject: [PATCH 03/11] https://coveord.atlassian.net/browse/KIT-3431 --- .../instant-products/instant-products.tsx | 18 +- .../src/components/search-box/search-box.tsx | 189 +++++++++++++----- .../standalone-search-box.tsx | 188 ++++++++++++----- .../headless-commerce-react/src/index.css | 2 +- 4 files changed, 286 insertions(+), 111 deletions(-) diff --git a/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx b/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx index 0df293b0272..0064c94696e 100644 --- a/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx +++ b/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx @@ -18,11 +18,11 @@ export default function InstantProducts(props: IInstantProductProps) { [controller] ); - if (state.products.length === 0) { + if (state.products.length === 0 || !state.query) { return null; } - const clickProduct = (product: Product) => { + const onClickProduct = (product: Product) => { controller.interactiveProduct({options: {product}}).select(); // Normally here, you would simply navigate to product.clickUri. @@ -38,16 +38,24 @@ export default function InstantProducts(props: IInstantProductProps) { }; return ( - <> +
+ {state.products.length === 0 && ( + + No instant products for query {state.query} + + )} +

+ Instant products for query {state.query} +

    {state.products.map((product, index) => (
  • -
  • ))}
- +
); } diff --git a/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx b/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx index c468984290f..b06aec65cdd 100644 --- a/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx +++ b/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx @@ -1,8 +1,9 @@ import { SearchBox as HeadlessSearchBox, InstantProducts as HeadlessInstantProducts, + Suggestion, } from '@coveo/headless/commerce'; -import {useEffect, useState} from 'react'; +import {useEffect, useRef, useState} from 'react'; import InstantProducts from '../instant-products/instant-products'; interface ISearchBoxProps { @@ -15,71 +16,153 @@ export default function SearchBox(props: ISearchBoxProps) { const {controller, instantProductsController, navigate} = props; const [state, setState] = useState(controller.state); + const [isDropdownVisible, setIsDropdownVisible] = useState(false); + + const searchInputRef = useRef(null); useEffect(() => { - controller.state.value && controller.clear(); - controller.subscribe(() => setState(controller.state)); + controller.subscribe(() => setState({...controller.state})); }, [controller]); + const focusSearchBoxInput = () => { + searchInputRef.current!.focus(); + }; + + const hideDropdown = () => { + setIsDropdownVisible(false); + }; + + const showDropdown = () => { + setIsDropdownVisible(true); + }; + const onSearchBoxInputChange = (e: React.ChangeEvent) => { + if (e.target.value === '') { + hideDropdown(); + controller.clear(); + return; + } + controller.updateText(e.target.value); instantProductsController.updateQuery(e.target.value); + controller.showSuggestions(); + showDropdown(); + }; + + const onSearchBoxInputKeyDown = ( + e: React.KeyboardEvent + ) => { + switch (e.key) { + case 'Escape': + if (isDropdownVisible) { + hideDropdown(); + break; + } + if (state.value !== '') { + controller.clear(); + instantProductsController.updateQuery(state.value); + break; + } + break; + case 'Enter': + hideDropdown(); + controller.submit(); + break; + default: + break; + } + }; + + const onClickSearchBoxClear = () => { + focusSearchBoxInput(); + hideDropdown(); + controller.clear(); + instantProductsController.updateQuery(state.value); + }; + + const onClickSearchBoxSubmit = () => { + controller.submit(); + focusSearchBoxInput(); + hideDropdown(); + }; + + const onFocusSuggestion = (suggestion: Suggestion) => { + instantProductsController.updateQuery(suggestion.rawValue); + }; + + const onSelectSuggestion = (suggestion: Suggestion) => { + controller.selectSuggestion(suggestion.rawValue); + hideDropdown(); + }; + + const renderDropdown = () => { + return ( +
+ {state.suggestions.length > 0 && ( +
+

Query suggestions

+
    + {state.suggestions.map((suggestion) => ( +
  • + +
  • + ))} +
+
+ )} + +
+ +
+
+ ); }; return ( -
+ onSearchBoxInputChange(e)} > - {state.value !== '' && ( - - - - )} - - - - - - - - - - - - - - -
Query suggestionsInstant products
- {state.suggestions.length > 0 && ( -
    - {state.suggestions.map((suggestion, index) => ( -
  • - -
  • - ))} -
- )} -
- -
-
+ + + {isDropdownVisible && renderDropdown()} + ); } diff --git a/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx b/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx index e89880e644a..e19859bfebd 100644 --- a/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx +++ b/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx @@ -1,8 +1,9 @@ import { StandaloneSearchBox as HeadlessStandaloneSearchBox, InstantProducts as HeadlessInstantProducts, + Suggestion, } from '@coveo/headless/commerce'; -import {useEffect, useState} from 'react'; +import {useEffect, useRef, useState} from 'react'; import InstantProducts from '../instant-products/instant-products'; interface IStandaloneSearchBoxProps { @@ -15,10 +16,13 @@ export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) { const {navigate, controller, instantProductsController} = props; const [state, setState] = useState(controller.state); + const [isDropdownVisible, setIsDropdownVisible] = useState(false); + + const searchInputRef = useRef(null); useEffect(() => { controller.state.value && controller.clear(); - controller.subscribe(() => setState(controller.state)); + controller.subscribe(() => setState({...controller.state})); }, [controller]); useEffect(() => { @@ -30,65 +34,145 @@ export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) { } }, [state.redirectTo, navigate, state.value, controller]); + const focusSearchBoxInput = () => { + searchInputRef.current!.focus(); + }; + + const hideDropdown = () => { + setIsDropdownVisible(false); + }; + + const showDropdown = () => { + setIsDropdownVisible(true); + }; + const onSearchBoxInputChange = (e: React.ChangeEvent) => { + if (e.target.value === '') { + hideDropdown(); + controller.clear(); + return; + } + controller.updateText(e.target.value); instantProductsController.updateQuery(e.target.value); + controller.showSuggestions(); + showDropdown(); + }; + + const onSearchBoxInputKeyDown = ( + e: React.KeyboardEvent + ) => { + switch (e.key) { + case 'Escape': + if (isDropdownVisible) { + hideDropdown(); + break; + } + if (state.value !== '') { + controller.clear(); + instantProductsController.updateQuery(state.value); + break; + } + break; + case 'Enter': + hideDropdown(); + controller.submit(); + break; + default: + break; + } + }; + + const onClickSearchBoxClear = () => { + focusSearchBoxInput(); + hideDropdown(); + controller.clear(); + instantProductsController.updateQuery(state.value); + }; + + const onClickSearchBoxSubmit = () => { + controller.submit(); + focusSearchBoxInput(); + hideDropdown(); + }; + + const onFocusSuggestion = (suggestion: Suggestion) => { + instantProductsController.updateQuery(suggestion.rawValue); + }; + + const onSelectSuggestion = (suggestion: Suggestion) => { + controller.selectSuggestion(suggestion.rawValue); + hideDropdown(); + }; + + const renderDropdown = () => { + return ( +
+ {state.suggestions.length > 0 && ( +
+

Query suggestions

+
    + {state.suggestions.map((suggestion) => ( +
  • + +
  • + ))} +
+
+ )} + +
+ +
+
+ ); }; return ( -
+ onSearchBoxInputChange(e)} > - {state.value !== '' && ( - - - - )} - - {state.suggestions.length > 0 && ( - - - - - - - - - - - - - -
Query suggestionsInstant products
-
    - {state.suggestions.map((suggestion, index) => ( -
  • - -
  • - ))} -
-
- -
- )} -
+ + + {isDropdownVisible && renderDropdown()} + ); } diff --git a/packages/samples/headless-commerce-react/src/index.css b/packages/samples/headless-commerce-react/src/index.css index 7323ae85c54..afd0f7965af 100644 --- a/packages/samples/headless-commerce-react/src/index.css +++ b/packages/samples/headless-commerce-react/src/index.css @@ -1,5 +1,5 @@ body { - margin: 0; + margin: 1rem; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; From 8790fbbba579e7bff71661880e75ffaec34f82bb Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 08:59:42 -0400 Subject: [PATCH 04/11] docs(headless commerce react samples): add note regarding context + url manager (#4276) https://coveord.atlassian.net/browse/KIT-3444 --- .../headless-commerce-react/src/pages/cart-page.tsx | 4 ++++ .../headless-commerce-react/src/pages/home-page.tsx | 4 ++++ .../src/pages/product-description-page.tsx | 4 ++++ .../src/pages/product-listing-page.tsx | 10 ++++++++++ .../headless-commerce-react/src/pages/search-page.tsx | 10 ++++++++++ 5 files changed, 32 insertions(+) diff --git a/packages/samples/headless-commerce-react/src/pages/cart-page.tsx b/packages/samples/headless-commerce-react/src/pages/cart-page.tsx index 50cea6edac9..16d4898f6c0 100644 --- a/packages/samples/headless-commerce-react/src/pages/cart-page.tsx +++ b/packages/samples/headless-commerce-react/src/pages/cart-page.tsx @@ -24,6 +24,10 @@ export default function CartPage(props: ICartPageProps) { }); useEffect(() => { + /** + * It is important to call the `Context` controller's `setView` method with the current URL when a page is loaded, + * as the Commerce API requires this information to function properly. + */ contextController.setView({url}); if ( diff --git a/packages/samples/headless-commerce-react/src/pages/home-page.tsx b/packages/samples/headless-commerce-react/src/pages/home-page.tsx index 9c34fde235d..4c445dbb5cc 100644 --- a/packages/samples/headless-commerce-react/src/pages/home-page.tsx +++ b/packages/samples/headless-commerce-react/src/pages/home-page.tsx @@ -23,6 +23,10 @@ export default function HomePage(props: IHomePageProps) { }); useEffect(() => { + /** + * It is important to call the `Context` controller's `setView` method with the current URL when a page is loaded, + * as the Commerce API requires this information to function properly. + */ contextController.setView({url}); if ( diff --git a/packages/samples/headless-commerce-react/src/pages/product-description-page.tsx b/packages/samples/headless-commerce-react/src/pages/product-description-page.tsx index c290c6c79de..d6413fd2513 100644 --- a/packages/samples/headless-commerce-react/src/pages/product-description-page.tsx +++ b/packages/samples/headless-commerce-react/src/pages/product-description-page.tsx @@ -46,6 +46,10 @@ export default function ProductDescriptionPage( const product = loadProduct(); useEffect(() => { + /** + * It is important to call the `Context` controller's `setView` method with the current URL when a page is loaded, + * as the Commerce API requires this information to function properly. + */ contextController.setView({url}); }, [contextController, url]); diff --git a/packages/samples/headless-commerce-react/src/pages/product-listing-page.tsx b/packages/samples/headless-commerce-react/src/pages/product-listing-page.tsx index a827542a63b..720a374412a 100644 --- a/packages/samples/headless-commerce-react/src/pages/product-listing-page.tsx +++ b/packages/samples/headless-commerce-react/src/pages/product-listing-page.tsx @@ -52,6 +52,16 @@ export default function ProductListingPage(props: IProductListingPageProps) { }, [productListingController]); useEffect(() => { + /** + * It is important to call the `Context` controller's `setView` method with the current URL when a page is loaded, + * as the Commerce API requires this information to function properly. + * + * Note, however, that calling this method will reset the query, pagination, sort, and facets. + * + * This means that on a search or listing page, you must call this method BEFORE you bind the URL manager. + * Otherwise, the URL manager will restore the state from the URL parameters, and then this state will get + * immediately reset when the `setView` method is called. + */ contextController.setView({url}); const unsubscribe = bindUrlManager(); diff --git a/packages/samples/headless-commerce-react/src/pages/search-page.tsx b/packages/samples/headless-commerce-react/src/pages/search-page.tsx index f42ce256acf..58e76f09665 100644 --- a/packages/samples/headless-commerce-react/src/pages/search-page.tsx +++ b/packages/samples/headless-commerce-react/src/pages/search-page.tsx @@ -61,6 +61,16 @@ export default function Search(props: ISearchProps) { }, [searchController]); useEffect(() => { + /** + * It is important to call the `Context` controller's `setView` method with the current URL when a page is loaded, + * as the Commerce API requires this information to function properly. + * + * Note, however, that calling this method will reset the query, pagination, sort, and facets. + * + * This means that on a search or listing page, you must call this method BEFORE you bind the URL manager. + * Otherwise, the URL manager will restore the state from the URL parameters, and then this state will get + * immediately reset when the `setView` method is called. + */ contextController.setView({url}); const unsubscribe = bindUrlManager(); From b5c868c6205ae54c397f1f621050069edd12584d Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 09:00:27 -0400 Subject: [PATCH 05/11] docs(headless commerce react samples): add products per page (#4275) https://coveord.atlassian.net/browse/KIT-3436 --- .../products-per-page/products-per-page.tsx | 47 +++++++++++++++++++ .../search-and-listing-interface.css | 9 ++++ .../search-and-listing-interface.tsx | 13 +++-- 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 packages/samples/headless-commerce-react/src/components/products-per-page/products-per-page.tsx diff --git a/packages/samples/headless-commerce-react/src/components/products-per-page/products-per-page.tsx b/packages/samples/headless-commerce-react/src/components/products-per-page/products-per-page.tsx new file mode 100644 index 00000000000..a9bf608fc25 --- /dev/null +++ b/packages/samples/headless-commerce-react/src/components/products-per-page/products-per-page.tsx @@ -0,0 +1,47 @@ +import {Pagination} from '@coveo/headless/commerce'; +import {useEffect, useState} from 'react'; + +interface IProductsPerPageProps { + controller: Pagination; +} + +export default function ProductsPerPage(props: IProductsPerPageProps) { + const {controller} = props; + + const [state, setState] = useState(controller.state); + + useEffect(() => { + controller.subscribe(() => setState(controller.state)); + }, [controller]); + + const options = [5, 10, 20, 50]; + return ( +
+ + {options.map((pageSize) => ( + + ))} + +
+ ); +} diff --git a/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.css b/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.css index d9b8b6316ab..b94783dac96 100644 --- a/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.css +++ b/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.css @@ -1,7 +1,16 @@ .column { float: left; +} + +.small { + width: 25%; +} +.medium { width: 50%; } +.large { + width: 75%; +} .row:after { content: ''; diff --git a/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.tsx b/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.tsx index c05cfcee9c5..7f5d2145055 100644 --- a/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.tsx +++ b/packages/samples/headless-commerce-react/src/components/use-cases/search-and-listing-interface/search-and-listing-interface.tsx @@ -7,6 +7,7 @@ import {useState, useEffect} from 'react'; import BreadcrumbManager from '../../breadcrumb-manager/breadcrumb-manager'; import FacetGenerator from '../../facets/facet-generator/facet-generator'; import ProductList from '../../product-list/product-list'; +import ProductsPerPage from '../../products-per-page/products-per-page'; import ShowMore from '../../show-more/show-more'; import Sort from '../../sort/sort'; import Summary from '../../summary/summary'; @@ -37,23 +38,27 @@ export default function SearchAndListingInterface( return (
-
+
+ + +
+
-
+
- - + + {/**/}
From 25367645a6a3069713729bb13247500737d15be7 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 09:01:07 -0400 Subject: [PATCH 06/11] docs(headless commerce react samples): add regular and category facet search (#4274) https://coveord.atlassian.net/browse/KIT-3432 --- .../breadcrumb-manager/breadcrumb-manager.tsx | 11 +- .../facets/category-facet/category-facet.tsx | 256 +++++++++++++++--- .../facet-generator/facet-generator.tsx | 4 +- .../facets/regular-facet/regular-facet.tsx | 229 +++++++++++++--- 4 files changed, 412 insertions(+), 88 deletions(-) diff --git a/packages/samples/headless-commerce-react/src/components/breadcrumb-manager/breadcrumb-manager.tsx b/packages/samples/headless-commerce-react/src/components/breadcrumb-manager/breadcrumb-manager.tsx index 020c674f40d..6bb726765a3 100644 --- a/packages/samples/headless-commerce-react/src/components/breadcrumb-manager/breadcrumb-manager.tsx +++ b/packages/samples/headless-commerce-react/src/components/breadcrumb-manager/breadcrumb-manager.tsx @@ -18,7 +18,7 @@ export default function BreadcrumbManager(props: BreadcrumbManagerProps) { const [state, setState] = useState(controller.state); useEffect(() => { - controller.subscribe(() => setState(controller.state)); + controller.subscribe(() => setState({...controller.state})); }, [controller]); if (!state.hasBreadcrumbs) { @@ -57,14 +57,17 @@ export default function BreadcrumbManager(props: BreadcrumbManagerProps) {
    - {state.facetBreadcrumbs.map((facetBreadcrumb, index) => { + {state.facetBreadcrumbs.map((facetBreadcrumb) => { return ( -
  • +
  • {facetBreadcrumb.values.map((value, index) => { return ( + {state.facetSearch.isLoading && ( + + {' '} + Facet search is loading... + + )} + + ); + }; + + const renderFacetSearchResults = () => { + return state.facetSearch.values.length === 0 ? ( + + No results for {state.facetSearch.query} + + ) : ( +
      + {state.facetSearch.values.map((value) => ( +
    • onClickFacetSearchResult(value)} + style={{width: 'fit-content'}} + > + + + + {' '} + ({value.count}) + +
    • + ))} +
    + ); + }; + + const renderActiveFacetValueTree = () => { if (!state.hasActiveValues) { return null; } @@ -33,32 +160,51 @@ export default function CategoryFacet(props: ICategoryFacetProps) { const activeValueChildren = ancestry[ancestry.length - 1]?.children ?? []; return ( -
      - {ancestry.map((ancestor) => { +
        + {ancestry.map((ancestryValue) => { + const checkboxId = `ancestryFacetValueCheckbox-${ancestryValue.value}`; return ( -
      • +
      • toggleSelectFacetValue(ancestryValue)} type="checkbox" - checked={controller.isValueSelected(ancestor)} - onChange={() => toggleSelect(ancestor)} > - - ({ancestor.numberOfResults}) +
      • ); })} {activeValueChildren.length > 0 && ( -
          - {activeValueChildren.map((leaf) => { +
            + {activeValueChildren.map((child) => { + const checkboxId = `facetValueChildCheckbox-${child.value}`; return ( -
          • +
          • toggleSelect(leaf)} + className="FacetValueCheckbox" + id={checkboxId} + type="checkbox" + onChange={() => toggleSelectFacetValue(child)} > - - ({leaf.numberOfResults}) +
          • ); })} @@ -77,12 +223,12 @@ export default function CategoryFacet(props: ICategoryFacetProps) {
              {state.values.map((root) => { return ( -
            • +
            • toggleSelect(root)} + onChange={() => toggleSelectFacetValue(root)} > @@ -96,32 +242,52 @@ export default function CategoryFacet(props: ICategoryFacetProps) { ); }; + const renderFacetValues = () => { + return ( +
              + + {renderRootValues()} + {renderActiveFacetValueTree()} + + +
              + ); + }; + return ( -
            • -

              {state.displayName ?? state.facetId}

              - - {renderRootValues()} - {renderAncestry()} - - -
            • +
              + + {state.displayName ?? state.facetId} + + {renderFacetSearchControls()} + {showFacetSearchResults + ? renderFacetSearchResults() + : renderFacetValues()} +
              ); } diff --git a/packages/samples/headless-commerce-react/src/components/facets/facet-generator/facet-generator.tsx b/packages/samples/headless-commerce-react/src/components/facets/facet-generator/facet-generator.tsx index ac8010db4e2..58d40f04877 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/facet-generator/facet-generator.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/facet-generator/facet-generator.tsx @@ -25,7 +25,7 @@ export default function FacetGenerator(props: IFacetGeneratorProps) { } return ( -
                +
              + ); } diff --git a/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx index b6ac1eea17e..28e5cffada6 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx @@ -1,5 +1,8 @@ -import {RegularFacet as HeadlessRegularFacet} from '@coveo/headless/commerce'; -import {useEffect, useState} from 'react'; +import { + RegularFacet as HeadlessRegularFacet, + RegularFacetValue, +} from '@coveo/headless/commerce'; +import {useEffect, useRef, useState} from 'react'; interface IRegularFacetProps { controller: HeadlessRegularFacet; @@ -9,26 +12,134 @@ export default function RegularFacet(props: IRegularFacetProps) { const {controller} = props; const [state, setState] = useState(controller.state); + const [showFacetSearchResults, setShowFacetSearchResults] = useState(false); + + const facetSearchInputRef = useRef(null); useEffect(() => { controller.subscribe(() => setState(controller.state)); }, [controller]); - const renderFacetValues = () => { + const focusFacetSearchInput = (): void => { + facetSearchInputRef.current!.focus(); + }; + + const onChangeFacetSearchInput = ( + e: React.ChangeEvent + ): void => { + if (e.target.value === '') { + setShowFacetSearchResults(false); + controller.facetSearch.clear(); + return; + } + + controller.facetSearch.updateText(e.target.value); + controller.facetSearch.search(); + setShowFacetSearchResults(true); + }; + + const highlightFacetSearchResult = (displayValue: string): string => { + const query = state.facetSearch.query; + const regex = new RegExp(query, 'gi'); + return displayValue.replace(regex, (match) => `${match}`); + }; + + // TODO: export BaseFacetSearchResult type from @coveo/headless/commerce + const onClickFacetSearchResult = (value: { + displayValue: string; + rawValue: string; + count: number; + }): void => { + controller.facetSearch.select(value); + controller.facetSearch.clear(); + setShowFacetSearchResults(false); + focusFacetSearchInput(); + }; + + const onClickFacetSearchClear = (): void => { + setShowFacetSearchResults(false); + controller.facetSearch.clear(); + focusFacetSearchInput(); + }; + + const onClickClearSelectedFacetValues = (): void => { + controller.deselectAll(); + focusFacetSearchInput(); + }; + + const onClickFacetValue = (facetValue: RegularFacetValue): void => { + controller.toggleSelect(facetValue); + focusFacetSearchInput(); + }; + + const renderFacetSearchControls = () => { return ( -
                - {state.values.map((value) => ( -
              • + + + + + {state.facetSearch.isLoading && ( + + {' '} + Facet search is loading... + + )} + + ); + }; + + const renderFacetSearchResults = () => { + return state.facetSearch.values.length === 0 ? ( + + No results for {state.facetSearch.query} + + ) : ( +
                  + {state.facetSearch.values.map((value) => ( +
                • onClickFacetSearchResult(value)} + style={{width: 'fit-content'}} + > controller.toggleSelect(value)} > - - + + {' '} - ({value.numberOfResults}) + ({value.count})
                • ))} @@ -36,31 +147,75 @@ export default function RegularFacet(props: IRegularFacetProps) { ); }; + const renderFacetValues = () => { + return ( +
                  + + {state.isLoading && ( + Facet is loading... + )} +
                    + {state.values.map((value) => ( +
                  • + onClickFacetValue(value)} + type="checkbox" + > + +
                  • + ))} +
                  + + +
                  + ); + }; + return ( -
                • -

                  {state.displayName ?? state.facetId}

                  - - {renderFacetValues()} - - -
                • +
                  + + {state.displayName ?? state.facetId} + + {renderFacetSearchControls()} + {showFacetSearchResults + ? renderFacetSearchResults() + : renderFacetValues()} +
                  ); } From a09fb3542c9cdb229fec22560a18874327f742e5 Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 09:01:55 -0400 Subject: [PATCH 07/11] docs(headless commerce react samples): add manual numeric facet range example + support query in summary (#4273) https://coveord.atlassian.net/browse/KIT-3437 --- .../facets/numeric-facet/numeric-facet.tsx | 225 ++++++++++++++---- .../src/components/summary/summary.tsx | 33 ++- 2 files changed, 206 insertions(+), 52 deletions(-) diff --git a/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx index 59f6f1008f0..0ffe77db9b1 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx @@ -1,5 +1,5 @@ import {NumericFacet as HeadlessNumericFacet} from '@coveo/headless/commerce'; -import {useEffect, useState} from 'react'; +import {useEffect, useRef, useState} from 'react'; interface INumericFacetProps { controller: HeadlessNumericFacet; @@ -9,60 +9,195 @@ export default function NumericFacet(props: INumericFacetProps) { const {controller} = props; const [state, setState] = useState(controller.state); + const [currentManualRange, setCurrentManualRange] = useState({ + start: + controller.state.manualRange?.start ?? + controller.state.domain?.min ?? + controller.state.values[0]?.start ?? + 0, + end: + controller.state.manualRange?.end ?? + controller.state.domain?.max ?? + controller.state.values[0]?.end ?? + 0, + }); + + const manualRangeStartInputRef = useRef(null); useEffect(() => { - controller.subscribe(() => setState(controller.state)); + controller.subscribe(() => { + setState(controller.state), + setCurrentManualRange({ + start: + controller.state.manualRange?.start ?? + controller.state.domain?.min ?? + controller.state.values[0]?.start ?? + 0, + end: + controller.state.manualRange?.end ?? + controller.state.domain?.max ?? + controller.state.values[0]?.end ?? + 0, + }); + }); }, [controller]); + const focusManualRangeStartInput = (): void => { + manualRangeStartInputRef.current!.focus(); + }; + + const invalidRange = + currentManualRange.start >= currentManualRange.end || + isNaN(currentManualRange.start) || + isNaN(currentManualRange.end); + + const onChangeManualRangeStart = (e: React.ChangeEvent) => { + setCurrentManualRange({ + start: Number.parseInt(e.target.value), + end: currentManualRange.end, + }); + }; + + const onChangeManualRangeEnd = (e: React.ChangeEvent) => { + setCurrentManualRange({ + start: currentManualRange.start, + end: Number.parseInt(e.target.value), + }); + }; + + const onClickManualRangeSelect = () => { + const start = + state.domain && currentManualRange.start < state.domain.min + ? state.domain.min + : currentManualRange.start; + const end = + state.domain && currentManualRange.end > state.domain.max + ? state.domain.max + : currentManualRange.end; + controller.setRanges([ + { + start, + end, + endInclusive: true, + state: 'selected', + }, + ]); + focusManualRangeStartInput(); + }; + + const onClickClearSelectedFacetValues = (): void => { + controller.deselectAll(); + focusManualRangeStartInput(); + }; + + const renderManualRangeControls = () => { + return ( +
                  + + + + + +
                  + ); + }; + const renderFacetValues = () => { return ( -
                    - {state.values.map((value, index) => ( -
                  • - controller.toggleSelect(value)} - > - - - {' '} - ({value.numberOfResults}) - -
                  • - ))} -
                  +
                  + + {state.isLoading && Facet is loading...} +
                    + {state.values.map((value, index) => { + const checkboxId = `${value.start}-${value.end}-${value.endInclusive}`; + return ( +
                  • + controller.toggleSelect(value)} + > + +
                  • + ); + })} +
                  + + +
                  ); }; return ( -
                • -

                  {state.displayName ?? state.facetId}

                  - +
                  + + {state.displayName ?? state.facetId} + + {renderManualRangeControls()} {renderFacetValues()} - - -
                • + ); } diff --git a/packages/samples/headless-commerce-react/src/components/summary/summary.tsx b/packages/samples/headless-commerce-react/src/components/summary/summary.tsx index 31be502caa1..bbc94418103 100644 --- a/packages/samples/headless-commerce-react/src/components/summary/summary.tsx +++ b/packages/samples/headless-commerce-react/src/components/summary/summary.tsx @@ -14,14 +14,33 @@ export default function Summary(props: ISummaryProps) { controller.subscribe(() => setState(controller.state)); }, [controller]); - const getSummaryText = () => { + const renderQuerySummary = () => { + if (!('query' in state) || state.query === '') { + return null; + } + + return ( + + {' '} + for query {state.query as string} + + ); + }; + + const renderSummary = () => { + if (state.isLoading) { + return Loading...; + } + const {firstProduct, lastProduct, totalNumberOfProducts} = state; - return `Showing results ${firstProduct} - ${lastProduct} of ${totalNumberOfProducts}`; + return ( + + Showing products {firstProduct} - {lastProduct} of{' '} + {totalNumberOfProducts} + {renderQuerySummary()} + + ); }; - return ( -
                  - {getSummaryText()} -
                  - ); + return
                  {renderSummary()}
                  ; } From 0bbb80478ee967b26bb7dfae44ae11da8ddea10d Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 10:43:02 -0400 Subject: [PATCH 08/11] Improve date facet https://coveord.atlassian.net/browse/KIT-3431 --- .../facets/date-facet/date-facet.tsx | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/packages/samples/headless-commerce-react/src/components/facets/date-facet/date-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/date-facet/date-facet.tsx index d3a4dfb3c43..214707e5e00 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/date-facet/date-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/date-facet/date-facet.tsx @@ -17,52 +17,65 @@ export default function DateFacet(props: IDateFacetProps) { const renderFacetValues = () => { return (
                    - {state.values.map((value, index) => ( -
                  • - controller.toggleSelect(value)} - > - - - {' '} - ({value.numberOfResults}) - -
                  • - ))} + {state.values.map((value) => { + const id = `${value.start}-${value.end}-${value.endInclusive}`; + return ( +
                  • + controller.toggleSelect(value)} + type="checkbox" + > + + + {' '} + ({value.numberOfResults}) + +
                  • + ); + })}
                  ); }; return ( -
                • -

                  {state.displayName ?? state.facetId}

                  +
                  + + {state.displayName ?? state.facetId} + + {state.isLoading && ( + Facet is loading... + )} {renderFacetValues()} -
                • + ); } From 2e040969a4988abf24fb33dc78fa62d208bc7f0d Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 10:43:49 -0400 Subject: [PATCH 09/11] Improvements to facet components https://coveord.atlassian.net/browse/KIT-3431 --- .../facets/category-facet/category-facet.tsx | 20 +++++++++---------- .../facets/numeric-facet/numeric-facet.tsx | 9 ++++----- .../facets/regular-facet/regular-facet.tsx | 19 +++++------------- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx index 77530a99196..18af174b776 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx @@ -58,7 +58,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { focusFacetSearchInput(); }; - const onClickClearSelectedFacetValues = (): void => { + const onClickClearSelectedFacetValue = (): void => { controller.deselectAll(); focusFacetSearchInput(); }; @@ -79,6 +79,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { X @@ -124,6 +121,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { @@ -171,6 +169,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { toggleSelectFacetValue(ancestryValue)} type="checkbox" @@ -194,6 +193,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { toggleSelectFacetValue(child)} @@ -228,6 +228,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { className="FacetValueCheckbox" type="checkbox" checked={false} + disabled={state.isLoading} onChange={() => toggleSelectFacetValue(root)} > @@ -246,11 +247,10 @@ export default function CategoryFacet(props: ICategoryFacetProps) { return (
                  @@ -271,7 +270,6 @@ export default function CategoryFacet(props: ICategoryFacetProps) { className="FacetShowLess" disabled={state.isLoading || !state.canShowLessValues} onClick={controller.showLessValues} - title="Show less facet values" > - diff --git a/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx index 0ffe77db9b1..c60c17c74b2 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/numeric-facet/numeric-facet.tsx @@ -99,6 +99,7 @@ export default function NumericFacet(props: INumericFacetProps) { ✓ @@ -138,7 +139,6 @@ export default function NumericFacet(props: INumericFacetProps) { className="FacetClear" disabled={state.isLoading || !state.hasActiveValues} onClick={onClickClearSelectedFacetValues} - title="Clear selected facet values" type="reset" > X @@ -151,6 +151,7 @@ export default function NumericFacet(props: INumericFacetProps) {
                • + @@ -183,7 +183,6 @@ export default function NumericFacet(props: INumericFacetProps) { className="FacetShowLess" disabled={state.isLoading || !state.canShowLessValues} onClick={controller.showLessValues} - title="Show less facet values" > - diff --git a/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx index 28e5cffada6..2f7dddb64a2 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx @@ -1,5 +1,6 @@ import { RegularFacet as HeadlessRegularFacet, + RegularFacetSearchResult, RegularFacetValue, } from '@coveo/headless/commerce'; import {useEffect, useRef, useState} from 'react'; @@ -44,12 +45,7 @@ export default function RegularFacet(props: IRegularFacetProps) { return displayValue.replace(regex, (match) => `${match}`); }; - // TODO: export BaseFacetSearchResult type from @coveo/headless/commerce - const onClickFacetSearchResult = (value: { - displayValue: string; - rawValue: string; - count: number; - }): void => { + const onClickFacetSearchResult = (value: RegularFacetSearchResult): void => { controller.facetSearch.select(value); controller.facetSearch.clear(); setShowFacetSearchResults(false); @@ -81,6 +77,7 @@ export default function RegularFacet(props: IRegularFacetProps) { X @@ -126,6 +119,7 @@ export default function RegularFacet(props: IRegularFacetProps) { @@ -155,7 +149,6 @@ export default function RegularFacet(props: IRegularFacetProps) { className="FacetClearSelected" disabled={state.isLoading || !state.hasActiveValues} onClick={onClickClearSelectedFacetValues} - title="Clear selected facet values" type="reset" > X @@ -190,7 +183,6 @@ export default function RegularFacet(props: IRegularFacetProps) { className="FacetShowMore" disabled={state.isLoading || !state.canShowMoreValues} onClick={controller.showMoreValues} - title="Show more facet values" > + @@ -199,7 +191,6 @@ export default function RegularFacet(props: IRegularFacetProps) { className="FacetShowLess" disabled={state.isLoading || !state.canShowLessValues} onClick={controller.showLessValues} - title="Show less facet values" > - From 1623b0816e5ad03615066c91bcf541252c2a83de Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 10:52:05 -0400 Subject: [PATCH 10/11] Use ternary operator to render no instant products section https://coveord.atlassian.net/browse/KIT-3431 --- .../instant-products/instant-products.tsx | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx b/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx index 0064c94696e..d4507642585 100644 --- a/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx +++ b/packages/samples/headless-commerce-react/src/components/instant-products/instant-products.tsx @@ -39,23 +39,26 @@ export default function InstantProducts(props: IInstantProductProps) { return (
                  - {state.products.length === 0 && ( - + {state.products.length === 0 ? ( +

                  No instant products for query {state.query} - +

                  + ) : ( + <> +

                  + Instant products for query {state.query} +

                  +
                    + {state.products.map((product, index) => ( +
                  • + +
                  • + ))} +
                  + )} -

                  - Instant products for query {state.query} -

                  -
                    - {state.products.map((product, index) => ( -
                  • - -
                  • - ))} -
                  ); } From 85a18f01403d7a53c080a007a0f02ba634b8427b Mon Sep 17 00:00:00 2001 From: Frederic Beaudoin Date: Mon, 26 Aug 2024 13:02:16 -0400 Subject: [PATCH 11/11] Replace search elements with divs https://coveord.atlassian.net/browse/KIT-3431 --- .../src/components/facets/category-facet/category-facet.tsx | 4 ++-- .../src/components/facets/regular-facet/regular-facet.tsx | 4 ++-- .../src/components/search-box/search-box.tsx | 4 ++-- .../standalone-search-box/standalone-search-box.tsx | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx index 18af174b776..9a0e2f05dbd 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/category-facet/category-facet.tsx @@ -72,7 +72,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { const renderFacetSearchControls = () => { return ( - +
                  @@ -100,7 +100,7 @@ export default function CategoryFacet(props: ICategoryFacetProps) { Facet search is loading... )} - +
                  ); }; diff --git a/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx b/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx index 2f7dddb64a2..e4bb91a4a80 100644 --- a/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx +++ b/packages/samples/headless-commerce-react/src/components/facets/regular-facet/regular-facet.tsx @@ -70,7 +70,7 @@ export default function RegularFacet(props: IRegularFacetProps) { const renderFacetSearchControls = () => { return ( - +
                  @@ -98,7 +98,7 @@ export default function RegularFacet(props: IRegularFacetProps) { Facet search is loading... )} - +
                  ); }; diff --git a/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx b/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx index b06aec65cdd..ae0a9416602 100644 --- a/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx +++ b/packages/samples/headless-commerce-react/src/components/search-box/search-box.tsx @@ -132,7 +132,7 @@ export default function SearchBox(props: ISearchBoxProps) { }; return ( - +
                  {isDropdownVisible && renderDropdown()} - +
                  ); } diff --git a/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx b/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx index e19859bfebd..a3cb82f450b 100644 --- a/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx +++ b/packages/samples/headless-commerce-react/src/components/standalone-search-box/standalone-search-box.tsx @@ -142,7 +142,7 @@ export default function StandaloneSearchBox(props: IStandaloneSearchBoxProps) { }; return ( - +
                  {isDropdownVisible && renderDropdown()} - +
                  ); }