diff --git a/packages/peregrine/lib/talons/FilterModal/__tests__/useFilterBlock.spec.js b/packages/peregrine/lib/talons/FilterModal/__tests__/useFilterBlock.spec.js
index 83b08a5de7..5aef1275e5 100644
--- a/packages/peregrine/lib/talons/FilterModal/__tests__/useFilterBlock.spec.js
+++ b/packages/peregrine/lib/talons/FilterModal/__tests__/useFilterBlock.spec.js
@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import { act } from 'react-test-renderer';
-
+import { MemoryRouter } from 'react-router-dom';
import { createTestInstance } from '@magento/peregrine';
import { useFilterBlock } from '../useFilterBlock';
@@ -56,7 +56,11 @@ describe('#useFilterBlock', () => {
});
it('is closed by default', () => {
- createTestInstance();
+ createTestInstance(
+
+
+
+ );
expect(log).toHaveBeenCalledWith({
handleClick: expect.any(Function),
@@ -66,7 +70,11 @@ describe('#useFilterBlock', () => {
it('is open if passed initially open', () => {
givenInitiallyOpen();
- createTestInstance();
+ createTestInstance(
+
+
+
+ );
expect(log).toHaveBeenCalledWith({
handleClick: expect.any(Function),
@@ -76,7 +84,11 @@ describe('#useFilterBlock', () => {
it('is open if items are selected', () => {
givenSelectedItems();
- createTestInstance();
+ createTestInstance(
+
+
+
+ );
expect(log).toHaveBeenCalledWith({
handleClick: expect.any(Function),
@@ -85,7 +97,11 @@ describe('#useFilterBlock', () => {
});
it('can toggle visibility', () => {
- createTestInstance();
+ createTestInstance(
+
+
+
+ );
expect(typeof handleClickProp).toBe('function');
diff --git a/packages/peregrine/lib/talons/FilterModal/helpers.js b/packages/peregrine/lib/talons/FilterModal/helpers.js
index 5d3a7b163c..cd958399c2 100644
--- a/packages/peregrine/lib/talons/FilterModal/helpers.js
+++ b/packages/peregrine/lib/talons/FilterModal/helpers.js
@@ -54,7 +54,7 @@ export const getStateFromSearch = (initialValue, filterKeys, filterItems) => {
if (existingFilter) {
items.add(existingFilter);
- } else {
+ } else if (group !== 'price') {
console.warn(
`Existing filter ${value} not found in possible filters`
);
diff --git a/packages/peregrine/lib/talons/FilterModal/useFilterBlock.js b/packages/peregrine/lib/talons/FilterModal/useFilterBlock.js
index 8691516d95..e6542aa943 100644
--- a/packages/peregrine/lib/talons/FilterModal/useFilterBlock.js
+++ b/packages/peregrine/lib/talons/FilterModal/useFilterBlock.js
@@ -1,13 +1,20 @@
import { useCallback, useState, useEffect, useMemo } from 'react';
+import { useLocation } from 'react-router-dom';
export const useFilterBlock = props => {
- const { filterState, items, initialOpen } = props;
+ const { filterState, items, initialOpen, group } = props;
+ const location = useLocation();
const hasSelected = useMemo(() => {
+ const params = new URLSearchParams(location.search);
+ //expansion of price filter dropdown
+ if (group == 'price') {
+ return params.get('price[filter]') ? true : false;
+ }
return items.some(item => {
return filterState && filterState.has(item);
});
- }, [filterState, items]);
+ }, [filterState, items, group, location.search]);
const [isExpanded, setExpanded] = useState(hasSelected || initialOpen);
diff --git a/packages/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js b/packages/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js
index fbcc2bce3f..688e957db3 100644
--- a/packages/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js
+++ b/packages/peregrine/lib/talons/FilterSidebar/useFilterSidebar.js
@@ -182,9 +182,10 @@ export const useFilterSidebar = props => {
}, [handleClose]);
const handleReset = useCallback(() => {
- filterApi.clear();
- setIsApplying(true);
- }, [filterApi, setIsApplying]);
+ //filterApi.clear();
+ //setIsApplying(true);
+ history.replace({ search: 'page=1' });
+ }, [history]);
const handleKeyDownActions = useCallback(
event => {
diff --git a/packages/peregrine/lib/talons/FormError/__tests__/__snapshots__/useFormError.spec.js.snap b/packages/peregrine/lib/talons/FormError/__tests__/__snapshots__/useFormError.spec.js.snap
index 0f2ab4c9ba..fc13cc8b6c 100644
--- a/packages/peregrine/lib/talons/FormError/__tests__/__snapshots__/useFormError.spec.js.snap
+++ b/packages/peregrine/lib/talons/FormError/__tests__/__snapshots__/useFormError.spec.js.snap
@@ -1,5 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`returns concatenated error message when allowErrorMessages 1`] = `"GraphQL Error 1, GraphQL Error 2"`;
+exports[`returns concatenated error message when allowErrorMessages 1`] = `"formError.responseError"`;
-exports[`returns general error message 1`] = `"formError.errorMessage, Generic Error"`;
+exports[`returns general error message 1`] = `"formError.responseError, Generic Error"`;
diff --git a/packages/venia-ui/lib/components/FilterModal/CurrentFilters/__tests__/currentFilter.spec.js b/packages/venia-ui/lib/components/FilterModal/CurrentFilters/__tests__/currentFilter.spec.js
index 4176744cba..def763d4f1 100644
--- a/packages/venia-ui/lib/components/FilterModal/CurrentFilters/__tests__/currentFilter.spec.js
+++ b/packages/venia-ui/lib/components/FilterModal/CurrentFilters/__tests__/currentFilter.spec.js
@@ -1,5 +1,6 @@
import React from 'react';
import { act } from 'react-test-renderer';
+import { MemoryRouter } from 'react-router-dom';
import { createTestInstance } from '@magento/peregrine';
@@ -19,7 +20,11 @@ jest.mock('../../../Trigger', () => props => );
let inputProps = {};
const Component = () => {
- return ;
+ return (
+
+
+
+ );
};
const givenDefaultValues = () => {
diff --git a/packages/venia-ui/lib/components/FilterModal/CurrentFilters/currentFilter.js b/packages/venia-ui/lib/components/FilterModal/CurrentFilters/currentFilter.js
index 8578c5546d..91a42d7950 100644
--- a/packages/venia-ui/lib/components/FilterModal/CurrentFilters/currentFilter.js
+++ b/packages/venia-ui/lib/components/FilterModal/CurrentFilters/currentFilter.js
@@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { useIntl } from 'react-intl';
import { shape, string, func } from 'prop-types';
import { X as Remove } from 'react-feather';
+import { useHistory, useLocation } from 'react-router-dom';
import { useStyle } from '../../../classify';
import Icon from '../../Icon';
@@ -12,13 +13,22 @@ const CurrentFilter = props => {
const { group, item, removeItem, onRemove } = props;
const classes = useStyle(defaultClasses, props.classes);
const { formatMessage } = useIntl();
+ const location = useLocation();
+ const history = useHistory();
const handleClick = useCallback(() => {
removeItem({ group, item });
if (typeof onRemove === 'function') {
onRemove(group, item);
}
- }, [group, item, removeItem, onRemove]);
+
+ if (group == 'price') {
+ // preserve all existing params
+ const params = new URLSearchParams(location.search);
+ params.delete('price[filter]');
+ history.replace({ search: params.toString() });
+ }
+ }, [group, item, removeItem, onRemove, history, location.search]);
const ariaLabel = formatMessage(
{
diff --git a/packages/venia-ui/lib/components/FilterModal/FilterList/__tests__/filterList.spec.js b/packages/venia-ui/lib/components/FilterModal/FilterList/__tests__/filterList.spec.js
index d13f05dec1..dba9b88aaf 100644
--- a/packages/venia-ui/lib/components/FilterModal/FilterList/__tests__/filterList.spec.js
+++ b/packages/venia-ui/lib/components/FilterModal/FilterList/__tests__/filterList.spec.js
@@ -1,4 +1,5 @@
import React from 'react';
+import { MemoryRouter } from 'react-router-dom';
import { createTestInstance } from '@magento/peregrine';
@@ -67,7 +68,11 @@ describe('#FilterList', () => {
});
it('renders without show more button', () => {
- const { root } = createTestInstance();
+ const { root } = createTestInstance(
+
+
+
+ );
expect(root.findAllByType(FilterItem)).toHaveLength(2);
expect(() => root.findByType('button')).toThrow();
@@ -76,7 +81,11 @@ describe('#FilterList', () => {
it('renders with show more button', () => {
givenShowMore();
- const { root } = createTestInstance();
+ const { root } = createTestInstance(
+
+
+
+ );
expect(() => root.findByType('button')).not.toThrow();
expect(root.findByType('button').children[0]).toBe(mockShowMore);
@@ -86,7 +95,11 @@ describe('#FilterList', () => {
givenShowMore();
givenExpanded();
- const { root } = createTestInstance();
+ const { root } = createTestInstance(
+
+
+
+ );
expect(() => root.findByType('button')).not.toThrow();
expect(root.findByType('button').children[0]).toBe(mockShowLess);
diff --git a/packages/venia-ui/lib/components/FilterModal/FilterList/filterList.js b/packages/venia-ui/lib/components/FilterModal/FilterList/filterList.js
index 0acf3d07c1..7d5332f0ed 100644
--- a/packages/venia-ui/lib/components/FilterModal/FilterList/filterList.js
+++ b/packages/venia-ui/lib/components/FilterModal/FilterList/filterList.js
@@ -1,4 +1,4 @@
-import React, { Fragment, useMemo } from 'react';
+import React, { Fragment, useMemo, useCallback } from 'react';
import { array, func, number, shape, string } from 'prop-types';
import { useIntl } from 'react-intl';
import setValidator from '@magento/peregrine/lib/validators/set';
@@ -8,6 +8,8 @@ import { useStyle } from '../../../classify';
import FilterItem from './filterItem';
import defaultClasses from './filterList.module.css';
import FilterItemRadioGroup from './filterItemRadioGroup';
+import RangeSlider from '../../RangeSlider/rangeSlider';
+import { useHistory, useLocation } from 'react-router-dom';
const labels = new WeakMap();
@@ -22,13 +24,41 @@ const FilterList = props => {
items,
onApply
} = props;
+ const { pathname, search } = useLocation();
+ const history = useHistory();
const classes = useStyle(defaultClasses, props.classes);
const talonProps = useFilterList({ filterState, items, itemCountToShow });
const { isListExpanded, handleListToggle } = talonProps;
const { formatMessage } = useIntl();
- // memoize item creation
- // search value is not referenced, so this array is stable
+ if (name === 'Price') {
+ var minRange = Number(items[0].value.split('_')[0]);
+ var maxRange = Number(items[items.length - 1].value.split('_')[1]);
+ }
+
+ const handleChange = useCallback(
+ newValue => {
+ const test = String(search).split('&');
+ const filters = test.filter(element => {
+ return !element.includes('price');
+ });
+ const newSearch = filters.join('&');
+ const nextParams = new URLSearchParams(newSearch);
+
+ const DELIMITER = ',';
+ const title = String(newValue.min) + '-' + String(newValue.max);
+ const value = String(newValue.min) + '_' + String(newValue.max);
+ nextParams.append(
+ `${group}[filter]`,
+ `${title}${DELIMITER}${value}`
+ );
+
+ history.push({ pathname, search: String(nextParams) });
+ },
+ [group, history, pathname, search]
+ );
+
+ // Memoize item creation
const itemElements = useMemo(() => {
if (filterFrontendInput === 'boolean') {
const key = `item-${group}`;
@@ -51,36 +81,44 @@ const FilterList = props => {
);
}
- return items.map((item, index) => {
- const { title, value } = item;
- const key = `item-${group}-${value}`;
-
- if (!isListExpanded && index >= itemCountToShow) {
- return null;
- }
-
- // create an element for each item
- const element = (
-
-
+
-
+
);
+ } else {
+ return items.map((item, index) => {
+ const { title, value } = item;
+ const key = `item-${group}-${value}`;
+
+ if (!isListExpanded && index >= itemCountToShow) {
+ return null;
+ }
- // associate each element with its normalized title
- // titles are not unique, so use the element as the key
- labels.set(element, title.toUpperCase());
- return element;
- });
+ const element = (
+
+
+
+ );
+ labels.set(element, title.toUpperCase());
+ return element;
+ });
+ }
}, [
classes,
filterApi,
@@ -91,7 +129,10 @@ const FilterList = props => {
items,
isListExpanded,
itemCountToShow,
- onApply
+ onApply,
+ minRange,
+ maxRange,
+ handleChange
]);
const showMoreLessItem = useMemo(() => {
diff --git a/packages/venia-ui/lib/components/FilterModal/filterBlock.js b/packages/venia-ui/lib/components/FilterModal/filterBlock.js
index d667d98810..b961c0a3ac 100644
--- a/packages/venia-ui/lib/components/FilterModal/filterBlock.js
+++ b/packages/venia-ui/lib/components/FilterModal/filterBlock.js
@@ -28,7 +28,8 @@ const FilterBlock = props => {
const talonProps = useFilterBlock({
filterState,
items,
- initialOpen
+ initialOpen,
+ group
});
const { handleClick, isExpanded } = talonProps;
const iconSrc = isExpanded ? ArrowUp : ArrowDown;
diff --git a/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js b/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js
index 9fafd2bbf0..a1b7502f20 100644
--- a/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js
+++ b/packages/venia-ui/lib/components/FilterSidebar/__tests__/filterSidebar.spec.js
@@ -1,5 +1,6 @@
import React from 'react';
import { act } from 'react-test-renderer';
+import { MemoryRouter } from 'react-router-dom';
import { createTestInstance } from '@magento/peregrine';
@@ -161,7 +162,11 @@ describe('#FilterSidebar', () => {
});
it('renders without filters', () => {
- createTestInstance();
+ createTestInstance(
+
+
+
+ );
expect(mockFilterBlock).not.toHaveBeenCalled();
expect(mockCurrentFilters).toHaveBeenCalled();
@@ -170,7 +175,11 @@ describe('#FilterSidebar', () => {
it('renders with filters and no selected filters', () => {
givenFilters();
- const { root } = createTestInstance();
+ const { root } = createTestInstance(
+
+
+
+ );
expect(() => root.findByType(LinkButton)).toThrow();
expect(mockFilterBlock).toHaveBeenCalledTimes(mockFilters.length);
@@ -179,7 +188,11 @@ describe('#FilterSidebar', () => {
it('renders with filters and selected filter', () => {
givenSelectedFilters();
- const { root } = createTestInstance();
+ const { root } = createTestInstance(
+
+
+
+ );
expect(() => root.findByType(LinkButton)).not.toThrow();
expect(mockFilterBlock).toHaveBeenCalledTimes(mockFilters.length);
@@ -188,7 +201,11 @@ describe('#FilterSidebar', () => {
it('handles when a user applies a filter and ref is not provided', () => {
givenSelectedFilters();
- const { root } = createTestInstance();
+ const { root } = createTestInstance(
+
+
+
+ );
act(() => {
root.findAllByType(FilterBlock)[0].props.onApply();
@@ -206,17 +223,22 @@ describe('#FilterSidebar', () => {
value: mockScrollTo
});
- const { root } = createTestInstance(, {
- createNodeMock: () => {
- return {
- getBoundingClientRect: mockGetBoundingClientRect.mockReturnValue(
- {
- top: 250
- }
- )
- };
+ const { root } = createTestInstance(
+
+
+ ,
+ {
+ createNodeMock: () => {
+ return {
+ getBoundingClientRect: mockGetBoundingClientRect.mockReturnValue(
+ {
+ top: 250
+ }
+ )
+ };
+ }
}
- });
+ );
act(() => {
root.findAllByType(FilterBlock)[0].props.onApply();
@@ -229,7 +251,11 @@ describe('#FilterSidebar', () => {
it('accepts configurable amount of open filters', () => {
givenFiltersAndAmountToShow();
- createTestInstance();
+ createTestInstance(
+
+
+
+ );
expect(mockFilterBlock).toHaveBeenCalledTimes(mockFilters.length);
diff --git a/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js b/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js
index 4fa266eb8a..758400e1be 100644
--- a/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js
+++ b/packages/venia-ui/lib/components/FilterSidebar/filterSidebar.js
@@ -8,6 +8,7 @@ import LinkButton from '../LinkButton';
import CurrentFilters from '../FilterModal/CurrentFilters';
import FilterBlock from '../FilterModal/filterBlock';
import defaultClasses from './filterSidebar.module.css';
+import { useLocation } from 'react-router-dom';
const SCROLL_OFFSET = 150;
@@ -31,6 +32,32 @@ const FilterSidebar = props => {
const filterRef = useRef();
const classes = useStyle(defaultClasses, props.classes);
+ const location = useLocation();
+
+ //adding the price filter values to the filterstate
+ const priceFilters = Array.from(filterItems, ([group]) => {
+ if (group == 'price') {
+ // preserve all existing params
+ const params = new URLSearchParams(location.search);
+ const uniqueKeys = new Set(params.keys());
+ // iterate over existing param keys
+ for (const key of uniqueKeys) {
+ // if a key matches a known filter, add its items to the next state
+ if (key == 'price[filter]') {
+ const value = params.get('price[filter]');
+ const item = {
+ title: value.split(',')[0],
+ value: value.split(',')[1]
+ };
+ const filterVar = new Set();
+ filterVar.add(item);
+
+ //to display the price filter value after selecting the filter
+ filterState.set('price', new Set(filterVar));
+ }
+ }
+ }
+ });
const handleApplyFilter = useCallback(
(...args) => {
@@ -115,6 +142,7 @@ const FilterSidebar = props => {
/>
+ {priceFilters}
{
+ const [minVal, setMinVal] = useState(min);
+ const [maxVal, setMaxVal] = useState(max);
+ const minValRef = useRef(min);
+ const maxValRef = useRef(max);
+ const range = useRef(null);
+
+ // Convert value to percentage for positioning
+ const getPercent = useCallback(
+ value => Math.round(((value - min) / (max - min)) * 100),
+ [min, max]
+ );
+
+ // set width of the range to decrease from the left side
+ useEffect(() => {
+ const minPercent = getPercent(minVal);
+ const maxPercent = getPercent(maxValRef.current);
+
+ if (range.current) {
+ range.current.style.left = `${minPercent}%`;
+ range.current.style.width = `${maxPercent - minPercent}%`;
+ }
+ }, [minVal, getPercent]);
+
+ // set the width of the range to decrease from right side
+ useEffect(() => {
+ const minPercent = getPercent(minValRef.current);
+ const maxPercent = getPercent(maxVal);
+
+ if (range.current) {
+ range.current.style.width = `${maxPercent - minPercent}%`;
+ }
+ }, [maxVal, getPercent]);
+
+ // Handle min and max value changes
+ useEffect(() => {
+ if (minVal !== minValRef.current || maxVal !== maxValRef.current) {
+ onChange({ min: minVal, max: maxVal });
+ minValRef.current = minVal;
+ maxValRef.current = maxVal;
+ }
+ }, [minVal, maxVal, onChange]);
+
+ return (
+
+ {/* Display Price Value */}
+
+ {minVal}
+ {maxVal}
+
+
+ {/* Style the price range slider */}
+
+
+ );
+};
+
+export default PriceRangeSlider;
diff --git a/packages/venia-ui/lib/components/RangeSlider/rangeSlider.module.css b/packages/venia-ui/lib/components/RangeSlider/rangeSlider.module.css
new file mode 100644
index 0000000000..1fe684beac
--- /dev/null
+++ b/packages/venia-ui/lib/components/RangeSlider/rangeSlider.module.css
@@ -0,0 +1,76 @@
+.slider {
+ position: relative;
+}
+
+.trackslider,
+.rangeslider {
+ position: absolute;
+}
+
+.trackslider,
+.rangeslider {
+ border-radius: 4px;
+ height: 6px;
+}
+
+.trackslider {
+ background-color: #022140;
+ width: 100%;
+ z-index: 1;
+}
+
+.rangeslider {
+ z-index: 2;
+}
+
+.thumb,
+.thumb::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ -webkit-tap-highlight-color: transparent;
+}
+
+.thumb {
+ pointer-events: none;
+ position: absolute;
+ height: 0;
+ outline: none;
+}
+
+.thumbleft {
+ z-index: 3;
+}
+
+.thumbright {
+ z-index: 4;
+}
+
+.thumb::-webkit-slider-thumb {
+ background-color: #2954fe;
+ border-radius: 50%;
+ /* box-shadow: 0 0 1px 1px #f50707; */
+ /* border: 4px solid #ff0303; */
+ cursor: pointer;
+ height: 20px;
+ width: 20px;
+ margin-top: 4.5px;
+ pointer-events: all;
+ position: relative;
+ transition: transform 0.3s ease-in-out;
+}
+
+.thumb::-webkit-slider-thumb:hover {
+ transform: scale(1.2);
+}
+
+.thumb::-moz-range-thumb {
+ background-color: #0a0a0a;
+ border-radius: 50%;
+ /* box-shadow: 0 0 1px 1px #f50707; */
+ border: 4px solid #ff0303;
+ cursor: pointer;
+ height: 28px;
+ width: 28px;
+ margin-top: 4.5px;
+ pointer-events: all;
+ position: relative;
+}