-
Notifications
You must be signed in to change notification settings - Fork 121
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(react-instantsearch): migrate recommend demo (#484)
- Loading branch information
Showing
37 changed files
with
3,459 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
|
||
/.parcel-cache | ||
/dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"singleQuote": true, | ||
"proseWrap": "never", | ||
"trailingComma": "es5" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# recommend | ||
|
||
## Get started | ||
|
||
To run this project locally, install the dependencies and run the local server: | ||
|
||
```sh | ||
npm install | ||
npm start | ||
``` | ||
|
||
Alternatively, you may use [Yarn](https://http://yarnpkg.com/): | ||
|
||
```sh | ||
yarn | ||
yarn start | ||
``` | ||
|
||
Open http://localhost:1234 to see your app. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
|
||
<link rel="shortcut icon" href="favicon.png" type="image/x-icon" /> | ||
|
||
<!-- | ||
Do not use @7 in production, use a complete version like x.x.x, see website for latest version: | ||
https://community.algolia.com/react-instantsearch/Getting_started.html#load-the-algolia-theme | ||
--> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/instantsearch.css@8/themes/reset-min.css" | ||
/> | ||
|
||
<title>Algolia Recommend</title> | ||
</head> | ||
|
||
<body> | ||
<noscript>You need to enable JavaScript to run this app.</noscript> | ||
<div id="root"></div> | ||
|
||
<script type="module" src="src/index.tsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
{ | ||
"name": "recommend", | ||
"version": "0.1.0", | ||
"private": true, | ||
"scripts": { | ||
"start": "parcel index.html", | ||
"build": "parcel build index.html" | ||
}, | ||
"dependencies": { | ||
"@algolia/autocomplete-js": "1.17.1", | ||
"@algolia/autocomplete-theme-classic": "1.17.1", | ||
"@algolia/ui-components-horizontal-slider-react": "1.2.2", | ||
"@algolia/ui-components-horizontal-slider-theme": "1.2.2", | ||
"algoliasearch": "4.23.3", | ||
"react": "18.3.1", | ||
"react-dom": "18.3.1", | ||
"react-instantsearch": "7.9.0", | ||
"react-router-dom": "^6.23.1", | ||
"search-insights": "2.14.0" | ||
}, | ||
"devDependencies": { | ||
"@types/react-dom": "18.3.0", | ||
"parcel": "2.12.0", | ||
"typescript": "5.1.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
* { | ||
box-sizing: border-box; | ||
} | ||
|
||
body { | ||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | ||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', | ||
sans-serif; | ||
-webkit-font-smoothing: antialiased; | ||
-moz-osx-font-smoothing: grayscale; | ||
padding: 0.5rem; | ||
} | ||
|
||
h3 { | ||
margin-bottom: 0.5rem; | ||
margin-top: 1rem; | ||
} | ||
|
||
.container { | ||
margin: 0 auto; | ||
max-width: 1100px; | ||
} | ||
|
||
.product-image { | ||
min-width: 150px; | ||
min-height: 200px; | ||
background-color: var(--auc-primary-color); | ||
} | ||
|
||
.title a { | ||
text-decoration: none; | ||
color: var(--auc-dark-color); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
:root { | ||
--auc-primary-color: #9698c3; | ||
--auc-muted-color: #c4c4c4; | ||
--auc-dark-color: #0f0f0f; | ||
} | ||
|
||
.auc-Recommend-list { | ||
display: grid; | ||
gap: 0.5rem; | ||
list-style: none; | ||
margin: 0; | ||
outline-color: var(--auc-primary-color); | ||
outline-offset: 0.5rem; | ||
padding: 0; | ||
} | ||
|
||
.auc-Recommend-list { | ||
grid-template-columns: repeat(2, 1fr); | ||
} | ||
|
||
@media (min-width: 999px) { | ||
.auc-Recommend-list { | ||
grid-template-columns: repeat(5, 1fr); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
react-instantsearch/recommend/src/components/Autocomplete/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { | ||
autocomplete, | ||
AutocompleteOptions, | ||
getAlgoliaResults, | ||
} from '@algolia/autocomplete-js'; | ||
import React, { createElement, Fragment, useEffect, useRef } from 'react'; | ||
import { render } from 'react-dom'; | ||
|
||
import { ProductHit } from '../../types'; | ||
|
||
export function Autocomplete(props: Partial<AutocompleteOptions<ProductHit>>) { | ||
const containerRef = useRef<HTMLDivElement | null>(null); | ||
|
||
useEffect(() => { | ||
if (!containerRef.current) { | ||
return undefined; | ||
} | ||
|
||
const search = autocomplete({ | ||
container: containerRef.current, | ||
renderer: { createElement, Fragment, render: () => {} }, | ||
render({ children }, root) { | ||
render(children, root); | ||
}, | ||
...props, | ||
}); | ||
|
||
return () => { | ||
search.destroy(); | ||
}; | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
|
||
return <div ref={containerRef} />; | ||
} | ||
|
||
export { getAlgoliaResults }; |
38 changes: 38 additions & 0 deletions
38
react-instantsearch/recommend/src/components/BundleItem/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import React from 'react'; | ||
import { InsightsClient } from 'search-insights'; | ||
|
||
import { indexName } from '../../config'; | ||
import { ProductHit } from '../../types'; | ||
|
||
type BundleItemProps<TObject> = { | ||
item: TObject; | ||
onSelect(item: TObject): void; | ||
insights: InsightsClient; | ||
}; | ||
|
||
export const BundleItem: React.FC<BundleItemProps<ProductHit>> = ({ | ||
item, | ||
onSelect, | ||
insights, | ||
}) => { | ||
return ( | ||
<a | ||
className="Hit Hit-link" | ||
href={item.url} | ||
onClick={(event) => { | ||
event.preventDefault(); | ||
|
||
onSelect(item); | ||
insights('clickedObjectIDs', { | ||
objectIDs: [item.objectID], | ||
eventName: 'Product Clicked', | ||
index: indexName, | ||
}); | ||
}} | ||
> | ||
<div className="Hit-Image"> | ||
<img src={item.image_urls[0]} alt={item.name} /> | ||
</div> | ||
</a> | ||
); | ||
}; |
154 changes: 154 additions & 0 deletions
154
react-instantsearch/recommend/src/components/BundleView/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import React, { useEffect, useMemo, useState } from 'react'; | ||
|
||
import { BaseObject, BundleViewTranslations } from '../../types'; | ||
|
||
import './style.css'; | ||
|
||
function getAmountDefault<TObject extends BaseObject>(items: TObject[]) { | ||
return items.reduce((sum, current) => sum + current.price.value, 0); | ||
} | ||
|
||
function formatPriceDefault(price: number) { | ||
return `$${price}`; | ||
} | ||
|
||
type BundleViewProps<TObject> = { | ||
currentItem: TObject; | ||
formatPrice?: (price: number) => string; | ||
getAmount?(items: TObject[]): number; | ||
itemComponent({ item }): JSX.Element; | ||
items: TObject[]; | ||
translations?: BundleViewTranslations; | ||
}; | ||
|
||
export function BundleView<TObject extends BaseObject>( | ||
props: BundleViewProps<TObject> | ||
): JSX.Element { | ||
const items = Array.from(new Set([props.currentItem, ...props.items])); | ||
const formatPrice = props.formatPrice || formatPriceDefault; | ||
const getAmount = props.getAmount || getAmountDefault; | ||
|
||
const [selectedItems, setSelectedItems] = useState(() => items); | ||
const [price, setPrice] = useState(() => getAmount(selectedItems)); | ||
const translations = useMemo( | ||
() => ({ | ||
totalPrice: 'Total price', | ||
thisArticle: 'This article', | ||
addToCart: (count: number) => { | ||
if (count === 1) { | ||
return `Add ${count} product to cart`; | ||
} | ||
|
||
return `Add ${count} products to cart`; | ||
}, | ||
...props.translations, | ||
}), | ||
[props.translations] | ||
); | ||
|
||
useEffect(() => { | ||
setPrice(getAmount(selectedItems)); | ||
}, [selectedItems, getAmount]); | ||
|
||
return ( | ||
<div className="uic-BundleView-container"> | ||
<div className="uic-BundleView-items"> | ||
<ol className="uic-BundleView-list"> | ||
{items.map((item, index) => { | ||
const isSelected = Boolean( | ||
selectedItems.find((x) => x.objectID === item.objectID) | ||
); | ||
|
||
function onChange(event: React.ChangeEvent<HTMLInputElement>) { | ||
setSelectedItems((items) => | ||
event.target.checked | ||
? [...items, item] | ||
: items.filter((x) => x.objectID !== item.objectID) | ||
); | ||
} | ||
|
||
return ( | ||
<li | ||
key={item.objectID} | ||
className={[ | ||
'uic-BundleView-item', | ||
isSelected && 'uic-BundleView-item--selected', | ||
] | ||
.filter(Boolean) | ||
.join(' ')} | ||
> | ||
<div className="uic-BundleView-itemContainer"> | ||
<div className="uic-BundleView-itemContainer-hit"> | ||
<props.itemComponent item={item} /> | ||
</div> | ||
{index < items.length - 1 && ( | ||
<div className="uic-BundleView-plus"> | ||
<svg viewBox="0 0 24 24" fill="none"> | ||
<rect | ||
width="24" | ||
height="24" | ||
rx="12" | ||
fill="currentColor" | ||
/> | ||
<path | ||
fillRule="evenodd" | ||
clipRule="evenodd" | ||
d="M12 5.54169C12.3452 5.54169 12.625 5.82151 12.625 6.16669V17.8334C12.625 18.1785 12.3452 18.4584 12 18.4584C11.6548 18.4584 11.375 18.1785 11.375 17.8334V6.16669C11.375 5.82151 11.6548 5.54169 12 5.54169Z" | ||
fill="#fff" | ||
stroke="#fff" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
<path | ||
fillRule="evenodd" | ||
clipRule="evenodd" | ||
d="M5.54166 12C5.54166 11.6548 5.82148 11.375 6.16666 11.375H17.8333C18.1785 11.375 18.4583 11.6548 18.4583 12C18.4583 12.3452 18.1785 12.625 17.8333 12.625H6.16666C5.82148 12.625 5.54166 12.3452 5.54166 12Z" | ||
fill="#fff" | ||
stroke="#fff" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
</div> | ||
)} | ||
</div> | ||
|
||
<label className="uic-BundleView-label"> | ||
<input | ||
className="uic-BundleView-checkbox" | ||
type="checkbox" | ||
defaultChecked={isSelected} | ||
onChange={onChange} | ||
/> | ||
{item.objectID === props.currentItem.objectID && ( | ||
<span className="uic-BundleView-label-currentArticle"> | ||
{translations.thisArticle}: | ||
</span> | ||
)} | ||
<span className="uic-BundleView-label-name">{item.name}</span> | ||
<span className="uic-BundleView-label-price"> | ||
{formatPrice(item.price.value)} | ||
</span> | ||
</label> | ||
</li> | ||
); | ||
})} | ||
</ol> | ||
</div> | ||
|
||
{selectedItems.length > 0 && ( | ||
<div className="uic-BundleView-cart"> | ||
<div> | ||
{translations.totalPrice}:{' '} | ||
<span className="uic-BundleView-label-price"> | ||
{formatPrice(price)} | ||
</span> | ||
</div> | ||
<button className="uic-BundleView-addToCart"> | ||
{translations.addToCart(selectedItems.length)} | ||
</button> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} |
Oops, something went wrong.