From a5b4c67e58de0fb634cd9b9e2cf58f4b06ecfc07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 14 Apr 2020 10:11:52 +0200 Subject: [PATCH 001/171] chore: add NVM file --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..d4c43fa7 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +12.14.1 \ No newline at end of file From 6d0cc25e653e8e9880cccd9a8003f0fa409223d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 14 Apr 2020 10:12:01 +0200 Subject: [PATCH 002/171] chore: add Prettier file --- .prettierrc | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..833f03b6 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "proseWrap": "never", + "trailingComma": "es5" +} From 8fb1977d772d2f15238fbf29e522529c244eaa40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Chalifour?= Date: Tue, 14 Apr 2020 10:12:09 +0200 Subject: [PATCH 003/171] chore: run Prettier --- .babelrc | 27 +-- README.md | 32 +-- public/index.html | 15 +- src/app.js | 62 +++-- src/app.scss | 34 ++- src/config.example.js | 102 ++++---- src/config.js | 117 +++++----- src/index.js | 8 +- src/main/Main.js | 12 +- src/main/MainDesktop.js | 38 +-- src/main/MainMobile.js | 59 +++-- src/mobile/RefinementsSidebar.js | 14 +- src/mobile/SortBy.js | 83 +++---- src/mobile/SortsSidebar.js | 19 +- src/shared/Analytics.js | 34 +-- src/shared/Banner.js | 48 ++-- src/shared/Configuration.js | 22 +- src/shared/CurrentRefinementsTags.js | 82 ++++--- src/shared/CustomStats.js | 47 ++-- src/shared/FakeSearchBar.js | 24 +- src/shared/InfiniteHits.js | 271 ++++++++++++---------- src/shared/QueryRulesBanner.js | 28 +-- src/shared/QueryRulesHandler.js | 35 +-- src/shared/SearchBar.js | 54 +++-- src/shared/Tools.js | 174 ++++++++------ src/shared/refinements/RefinementBasic.js | 6 +- src/shared/refinements/RefinementColor.js | 6 +- src/shared/refinements/RefinementPrice.js | 6 +- src/shared/refinements/RefinementSize.js | 13 +- src/shared/refinements/Refinements.js | 32 +-- src/shared/toggables/ToggablePanel.js | 62 ++--- src/shared/toggables/ToggableSidebar.js | 100 ++++---- webpack.config.js | 36 ++- 33 files changed, 938 insertions(+), 764 deletions(-) diff --git a/.babelrc b/.babelrc index 9ad5a1f0..a1f05c77 100644 --- a/.babelrc +++ b/.babelrc @@ -1,18 +1,13 @@ { - "presets": [ - "@babel/preset-env", - "@babel/preset-react" - ], - "plugins": [ - "@babel/plugin-proposal-class-properties", - [ - "transform-react-remove-prop-types", - { - "mode": "wrap", - "ignoreFilenames": [ - "node_modules" - ] - } - ] + "presets": ["@babel/preset-env", "@babel/preset-react"], + "plugins": [ + "@babel/plugin-proposal-class-properties", + [ + "transform-react-remove-prop-types", + { + "mode": "wrap", + "ignoreFilenames": ["node_modules"] + } ] -} \ No newline at end of file + ] +} diff --git a/README.md b/README.md index 3eaa2c0f..cda1f994 100644 --- a/README.md +++ b/README.md @@ -26,25 +26,25 @@ Follow the few steps described below to start using E-Commerce Unified UI. The `src/config.js` file describe the multiple available options to be used with E-Commerce Unified UI. -| Option Name | Type | Required | Comments | -|--------------------------|----------|----------|--------------------------------------------------------------------------------------| -| appId | `string` | Yes | Set your Algolia Application ID (accessible from the Dashboard) | -| searchApiKey | `string` | Yes | Set your Algolia Search API Key (accessible from the Dashboard) | -| indexName | `string` | Yes | Set your Algolia Index name | -| placeholderId | `string` | Yes | Set the DOM element that triggers the Search Results overlay | -| querySuggestions | `bool` | No | Enable the Query Suggestions feature | -| suggestions | `object` | No | Set the Query Suggestions options (`appId`, `apiKey`, `indexName`, `maxSuggestions`) | -| googleAnalytics | `bool` | No | Enable Google Analytics tracking (Google script need to be included on your page) | -| instantSearchConfigure | `object` | No | Parameters to pass to the InstantSearch widget | -| hits | `object` | Yes | Set the Hits option (`hitsPerPage` and `render()`) | -| refinements | `object` | No | Set the different Refinements available | -| sorts | `object` | No | Set the different Sorts available | -| translations | `object` | No | Set the different translations to be used | +| Option Name | Type | Required | Comments | +| --- | --- | --- | --- | +| appId | `string` | Yes | Set your Algolia Application ID (accessible from the Dashboard) | +| searchApiKey | `string` | Yes | Set your Algolia Search API Key (accessible from the Dashboard) | +| indexName | `string` | Yes | Set your Algolia Index name | +| placeholderId | `string` | Yes | Set the DOM element that triggers the Search Results overlay | +| querySuggestions | `bool` | No | Enable the Query Suggestions feature | +| suggestions | `object` | No | Set the Query Suggestions options (`appId`, `apiKey`, `indexName`, `maxSuggestions`) | +| googleAnalytics | `bool` | No | Enable Google Analytics tracking (Google script need to be included on your page) | +| instantSearchConfigure | `object` | No | Parameters to pass to the InstantSearch widget | +| hits | `object` | Yes | Set the Hits option (`hitsPerPage` and `render()`) | +| refinements | `object` | No | Set the different Refinements available | +| sorts | `object` | No | Set the different Sorts available | +| translations | `object` | No | Set the different translations to be used | ### Disclaimer > E-Commerce Unified UI is made accessible to you for trial and/or experimentation purposes. You may decide to use it or not. You are aware that use of E-Commerce Unified UI in production may increase your consumption of the Service, including [Queries Per Second](https://www.algolia.com/doc/faq/monitoring/which-queries-are-counted-as-part-of-the-max-qps-computations/). -> Algolia does not support E-Commerce Unified UI, and may discontinue it at any time at its sole discretion; configurations and/or customisations entered by you into E-Commerce Unified UI may be permanently lost. Any feedback (including source code) you may provide to us regarding E-Commerce Unified UI may be used by Algolia to improve the Service. +> Algolia does not support E-Commerce Unified UI, and may discontinue it at any time at its sole discretion; configurations and/or customisations entered by you into E-Commerce Unified UI may be permanently lost. Any feedback (including source code) you may provide to us regarding E-Commerce Unified UI may be used by Algolia to improve the Service. -> E-Commerce Unified UI source code is provided “as is” and “as available” without any warranty of any kind. Algolia disclaims all obligation and liability for any harm or damage arising out of or in connection with E-Commerce Unified UI. For purposes of our [SLA](https://www.algolia.com/policies/sla), the E-Commerce Unified UI is not an “API Client”. \ No newline at end of file +> E-Commerce Unified UI source code is provided “as is” and “as available” without any warranty of any kind. Algolia disclaims all obligation and liability for any harm or damage arising out of or in connection with E-Commerce Unified UI. For purposes of our [SLA](https://www.algolia.com/policies/sla), the E-Commerce Unified UI is not an “API Client”. diff --git a/public/index.html b/public/index.html index 17e9841f..6a523c57 100644 --- a/public/index.html +++ b/public/index.html @@ -3,12 +3,16 @@ - + E-Comm Unified UI -
+
diff --git a/src/App.js b/src/App.js index c57d9d1f..5c6f05c2 100644 --- a/src/App.js +++ b/src/App.js @@ -15,8 +15,10 @@ import { Refinements, SearchBox, Stats, + CancelButton, } from './components'; +import './theme.scss'; import './App.scss'; export function App(props) { @@ -47,9 +49,9 @@ export function App(props) { React.useEffect(() => { if (isOverlayShowing === true) { - document.body.classList.add('with-euip-modal-open'); + document.body.classList.add('Unified--open'); } else { - document.body.classList.remove('with-euip-modal-open'); + document.body.classList.remove('Unified--open'); setSearchState(getStateFromUrl({})); props.history.push('', searchState); } @@ -61,42 +63,68 @@ export function App(props) { } }, [searchState.query]); + React.useEffect(() => { + function onKeydown(event) { + if (event.key === 'Escape') { + setIsOverlayShowing(false); + } + } + + window.addEventListener('keydown', onKeydown); + + return () => { + window.removeEventListener('keydown', onKeydown); + }; + }, [setIsOverlayShowing]); + return ( <> setIsOverlayShowing(true)} /> {isOverlayShowing && createPortal( -
- - - - {/* @TODO: see how this can be used */} - {/* */} - -
-
-
- -
+ <> +
+ +
+ + + -
+
+
- - - - + + +
+ +
+
+ +
+ +
+ + + + +
-
-
-
, + +
+ , document.body )} diff --git a/src/App.scss b/src/App.scss index 5739154e..b6a690dd 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1,4 +1,398 @@ +.Unified-Container * { + outline-color: var(--algolia-theme-primary); +} + .Unified-Container { + background: #fff; + border-radius: 2px; + bottom: 0; + box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.62), + inset 0 0 1px rgba(255, 255, 255, 0.9); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + left: 0; + margin: 2rem; + overflow: scroll; + position: fixed; + right: 0; + top: 0; +} + +.Unified-Overlay { + background: rgba(0, 0, 0, 0.5); + bottom: 0; + left: 0; + position: fixed; + right: 0; + top: 0; +} + +h2 { + color: #21243d; + font-family: Hind, sans-serif; + font-size: 1.5rem; + font-weight: bold; +} + +/* Header */ + +.Unified-Header { + align-items: center; + background: var(--algolia-theme-primary); + background-image: linear-gradient( + 180deg, + rgba(0, 0, 0, 0) 54%, + rgba(0, 0, 0, 0.1) 100% + ); + color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + padding: 1.5rem; +} + +.Unified-CancelButton { + align-items: center; + background: #303030; + border: none; + color: #cfcfcf; + cursor: pointer; + display: flex; + height: 65px; + justify-content: center; + position: absolute; + right: 0; + top: 0; + width: 65px; +} + +.Unified-CancelButton:hover { + color: #fff; +} + +/* Query Suggestions */ + +.Unified-QuerySuggestions { + align-items: center; + color: #fff; + display: flex; + padding-top: 1.5rem; +} + +.Unified-QuerySuggestions-label { + color: rgba(255, 255, 255, 0.8); + font-weight: bold; + padding-right: 1rem; +} + +.Unified-QuerySuggestions-list { + display: flex; +} + +.Unified-QuerySuggestions-item:not(:last-of-type) { + margin-right: 1rem; +} + +.Unified-QuerySuggestions-button { + background: none; + border: none; + color: inherit; + cursor: pointer; + letter-spacing: 0.03rem; + padding: 0; + white-space: nowrap; +} + +.Unified-QuerySuggestions-button em, +.Unified-QuerySuggestions-button mark { + background: none; + color: inherit; + font-weight: bold; + padding: 0; +} + +/* Containers */ + +.Unified-Content { + display: flex; + margin: 0 auto; + max-width: 1300px; + padding: 2rem 1rem; +} + +.Unified-LeftPanel { + flex: 1; + margin-right: 60px; + max-width: 260px; +} + +// .container-header { +// align-items: center; +// display: flex; +// justify-content: space-between; +// min-height: 80px; +// } + +.Unified-RightPanel { + flex: 3; +} + +// .container-options { +// border-bottom: 1px solid #ebecf3; +// display: flex; +// justify-content: flex-end; +// margin-bottom: 30px; +// padding: 30px 0; +// } + +// .container-options .container-option:not(:first-child) { +// margin-left: 48px; +// } + +// .container-options select { +// min-width: 100px; +// } + +// .container-footer { +// margin: 4rem 0; +// } + +/* Styles the SFFV highlightings */ + +em, +mark { + background: rgba(226, 164, 0, 0.4); + font-style: normal; +} + +/* Clear refinements container */ + +// .clear-filters { +// align-items: center; +// display: flex; +// } + +// .clear-filters svg { +// margin-right: 8px; +// } + +/* Panel */ + +.Unified-Content .ais-Panel { + border-top: 1px solid #ebecf3; + padding-bottom: 2rem; + padding-top: 2rem; +} + +.Unified-Content .ais-Panel-header { + font-family: Hind, sans-serif; +} + +/* Search box */ + +.Unified-Header .ais-SearchBox { + max-width: 740px; + width: 100%; +} + +.Unified-Header .ais-SearchBox .ais-SearchBox-inputContainer { + background: #ebecf3; + background-image: linear-gradient( + 180deg, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.8) 100% + ); + border-radius: 8px; + box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2); + color: #21243d; + font-family: Hind, sans-serif; + height: 100%; + height: 64px; +} + +.Unified-Header .ais-SearchBox-input, +.Unified-Header .ais-SearchBox-completion { + background: none; + height: 100%; + /* + The "Hind" font family is vertically off-balance. + Adding 4px of padding top makes it more vertically aligned. + */ + padding: 4px 48px 0 64px; + position: absolute; + width: 100%; +} + +.Unified-Header .ais-SearchBox-submit, +.Unified-Header .ais-SearchBox-reset { + appearance: none; + background: none; + border: none; +} + +.Unified-Header .ais-SearchBox-reset { + cursor: pointer; +} + +.Unified-Header .ais-SearchBox-submit { + padding: 4px 1rem 0 2rem; + width: 64px; +} + +.Unified-Header .ais-SearchBox .ais-SearchBox-input::placeholder { + color: #21243d; + opacity: 1; /* Firefox */ +} + +.ais-SearchBox-input:-ms-input-placeholder { + color: #21243d; +} + +.ais-SearchBox-input::-ms-input-placeholder { + color: #21243d; +} + +.ais-SearchBox-submit { + color: var(--algolia-theme-primary); +} + +.ais-RefinementList .ais-SearchBox-input { + font-family: Hind, sans-serif; + /* + The "Hind" font family is vertically off-balance. + Adding some padding top makes it more vertically aligned. + */ + padding-top: 2px; +} + +/* Stats */ + +.ais-Stats-text { + font-size: 24px; + font-weight: bold; +} + +.ais-Stats-mainText { + color: var(--algolia-theme-primary); + font: inherit; +} + +/* Refinement List */ + +.ais-RefinementList-showMore { + background: none; + border: none; + color: var(--algolia-theme-secondary); + cursor: pointer; + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.04rem; + margin: 0; + padding: 0; + text-transform: uppercase; +} + +/* ToggleRefinement */ + +.ais-ToggleRefinement-label { + display: flex; + flex-direction: row-reverse; + justify-content: space-between; +} + +.ais-ToggleRefinement-checkbox { + font: inherit; + margin-left: 1rem; + margin-right: 0; + position: relative; +} + +.ais-ToggleRefinement-checkbox:checked::before { + color: var(--algolia-theme-primary); +} + +.ais-ToggleRefinement-checkbox::before { + align-items: center; + color: rgba(33, 36, 61, 0.32); + content: 'No'; + display: flex; + font-size: 0.8rem; + height: 16px; + position: absolute; + right: 38px; +} + +.ais-ToggleRefinement-checkbox:checked::before { + content: 'Yes'; +} + +.ais-ToggleRefinement-count { + display: none; +} + +/* RatingMenu */ + +.ais-RatingMenu-item:not(.ais-RatingMenu-item--selected) { + opacity: 0.5; +} + +.ais-RatingMenu-starIcon { + margin-right: 0.5rem; +} + +/* Hits */ + +.hit { + color: #21243d; + font-size: 14px; + line-height: 18px; +} + +.hit h1 { + font-size: 14px; +} + +.hit-header { + align-items: center; + display: flex; + justify-content: center; + margin: auto; + padding-bottom: 0.5rem; +} + +.hit-footer { + padding-top: 0.5rem; +} + +.hit-body { + overflow-wrap: break-word; + word-break: break-word; +} + +.hit-image { + height: auto; + max-height: 100%; + max-width: 100%; +} + +.hit-category { + color: #21243d; + font-size: 12px; + font-weight: 600; + line-height: 1; + margin-bottom: 8px; + opacity: 0.7; + text-transform: uppercase; +} + +.hit-description { + margin-top: 2px; +} + +.hit-currency { + color: var(--algolia-theme-primary); + font-size: 13px; + font-weight: 600; + padding-right: 2px; } diff --git a/src/components/Banner.js b/src/components/Banner.js index c4f00851..97a3033a 100644 --- a/src/components/Banner.js +++ b/src/components/Banner.js @@ -12,7 +12,7 @@ export const Banner = () => { return ( - {title} + {title} ); }); diff --git a/src/components/CancelButton.js b/src/components/CancelButton.js new file mode 100644 index 00000000..a43566e2 --- /dev/null +++ b/src/components/CancelButton.js @@ -0,0 +1,16 @@ +import React from 'react'; + +export function CancelButton(props) { + return ( + + + + ); +} diff --git a/src/components/FakeSearchBar.js b/src/components/FakeSearchBar.js index ce5c1482..7cd35f00 100644 --- a/src/components/FakeSearchBar.js +++ b/src/components/FakeSearchBar.js @@ -6,7 +6,6 @@ import config from '../config'; export const FakeSearchBar = ({ onClick }) => { return ReactDOM.createPortal( { diff --git a/src/components/Panel.js b/src/components/Panel.js new file mode 100644 index 00000000..7428941b --- /dev/null +++ b/src/components/Panel.js @@ -0,0 +1,52 @@ +import React from 'react'; + +export function Panel({ + isOpened: initialIsOpened = true, + header, + footer, + children, + ...rest +}) { + const [isOpened, setIsOpened] = React.useState(initialIsOpened); + + return ( +
+
+ {header} + + +
+ +
{children}
+ + {footer &&
{footer}
} +
+ ); +} diff --git a/src/components/RefinementBasic.js b/src/components/RefinementBasic.js index d26c2aad..81d67aae 100644 --- a/src/components/RefinementBasic.js +++ b/src/components/RefinementBasic.js @@ -1,8 +1,10 @@ import React from 'react'; import { RefinementList } from 'react-instantsearch-dom'; -import { withPanel } from '../hoc/withPanel'; +import { Panel } from './Panel'; -export const RefinementBasic = withPanel((props) => ( - -)); +export const RefinementBasic = (props) => ( + + + +); diff --git a/src/components/RefinementColor.js b/src/components/RefinementColor.js index 675a5b69..b7ef60ff 100644 --- a/src/components/RefinementColor.js +++ b/src/components/RefinementColor.js @@ -1,8 +1,10 @@ import React from 'react'; import ColorRefinementList from 'instantsearch-color-refinement-list-react'; -import { withPanel } from '../hoc/withPanel'; +import { Panel } from './Panel'; -export const RefinementColor = withPanel((props) => ( - -)); +export const RefinementColor = (props) => ( + + + +); diff --git a/src/components/RefinementPrice.js b/src/components/RefinementPrice.js index 3d54fbaa..a11014fe 100644 --- a/src/components/RefinementPrice.js +++ b/src/components/RefinementPrice.js @@ -1,8 +1,10 @@ import React from 'react'; import RheostatRangeSlider from 'instantsearch-rheostat-range-slider-react'; -import { withPanel } from '../hoc/withPanel'; +import { Panel } from './Panel'; -export const RefinementPrice = withPanel((props) => ( - -)); +export const RefinementPrice = (props) => ( + + + +); diff --git a/src/components/RefinementSize.js b/src/components/RefinementSize.js index c7c91411..0355fe53 100644 --- a/src/components/RefinementSize.js +++ b/src/components/RefinementSize.js @@ -1,12 +1,14 @@ import React from 'react'; import GroupSizeRefinementList from 'instantsearch-group-size-refinement-list-react'; -import { withPanel } from '../hoc/withPanel'; +import { Panel } from './Panel'; -export const RefinementSize = withPanel((props) => ( - new RegExp(pattern))} - /> -)); +export const RefinementSize = (props) => ( + + new RegExp(pattern))} + /> + +); diff --git a/src/components/SearchBox/PredictiveSearchBox.js b/src/components/SearchBox/PredictiveSearchBox.js index 6df3f3f2..c871fe3a 100644 --- a/src/components/SearchBox/PredictiveSearchBox.js +++ b/src/components/SearchBox/PredictiveSearchBox.js @@ -9,7 +9,6 @@ import { import { ReverseHighlight } from './ReverseHighlight'; export const PredictiveSearchBox = connectSearchBox((props) => { - const [showSuggestion, setShowSuggestion] = React.useState(false); const [suggestion, setSuggestion] = React.useState(null); const inputRef = React.createRef(null); @@ -23,6 +22,7 @@ export const PredictiveSearchBox = connectSearchBox((props) => { function onReset(event) { event.preventDefault(); + props.refine(''); if (inputRef.current) { inputRef.current.focus(); @@ -30,7 +30,7 @@ export const PredictiveSearchBox = connectSearchBox((props) => { } return ( -